@sip-protocol/sdk 0.7.3 → 0.7.4

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 (264) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -0
  3. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  4. package/dist/browser.d.mts +10 -4
  5. package/dist/browser.d.ts +10 -4
  6. package/dist/browser.js +47556 -19603
  7. package/dist/browser.mjs +628 -48
  8. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  9. package/dist/chunk-5D7A3L3W.mjs +717 -0
  10. package/dist/chunk-64AYA5F5.mjs +7834 -0
  11. package/dist/chunk-GMDGB22A.mjs +379 -0
  12. package/dist/chunk-I534WKN7.mjs +328 -0
  13. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  14. package/dist/chunk-PRRZAWJE.mjs +223 -0
  15. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  16. package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  17. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  18. package/dist/constants-LHAAUC2T.mjs +51 -0
  19. package/dist/dist-2OGQ7FED.mjs +3957 -0
  20. package/dist/dist-IFHPYLDX.mjs +254 -0
  21. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  22. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  23. package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  24. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  25. package/dist/index.d.mts +9 -3
  26. package/dist/index.d.ts +9 -3
  27. package/dist/index.js +48396 -19623
  28. package/dist/index.mjs +537 -19
  29. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  30. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  31. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  32. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  33. package/dist/proofs/halo2.d.mts +151 -0
  34. package/dist/proofs/halo2.d.ts +151 -0
  35. package/dist/proofs/halo2.js +350 -0
  36. package/dist/proofs/halo2.mjs +11 -0
  37. package/dist/proofs/kimchi.d.mts +160 -0
  38. package/dist/proofs/kimchi.d.ts +160 -0
  39. package/dist/proofs/kimchi.js +431 -0
  40. package/dist/proofs/kimchi.mjs +13 -0
  41. package/dist/proofs/noir.d.mts +1 -1
  42. package/dist/proofs/noir.d.ts +1 -1
  43. package/dist/proofs/noir.js +74 -18
  44. package/dist/proofs/noir.mjs +84 -24
  45. package/dist/solana-U3MEGU7W.mjs +280 -0
  46. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  47. package/package.json +54 -21
  48. package/src/adapters/index.ts +41 -0
  49. package/src/adapters/jupiter.ts +571 -0
  50. package/src/adapters/near-intents.ts +135 -0
  51. package/src/advisor/advisor.ts +653 -0
  52. package/src/advisor/index.ts +54 -0
  53. package/src/advisor/tools.ts +303 -0
  54. package/src/advisor/types.ts +164 -0
  55. package/src/chains/ethereum/announcement.ts +536 -0
  56. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  57. package/src/chains/ethereum/commitment.ts +522 -0
  58. package/src/chains/ethereum/constants.ts +462 -0
  59. package/src/chains/ethereum/deployment.ts +596 -0
  60. package/src/chains/ethereum/gas-estimation.ts +538 -0
  61. package/src/chains/ethereum/index.ts +268 -0
  62. package/src/chains/ethereum/optimizations.ts +614 -0
  63. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  64. package/src/chains/ethereum/registry.ts +584 -0
  65. package/src/chains/ethereum/rpc.ts +905 -0
  66. package/src/chains/ethereum/stealth.ts +491 -0
  67. package/src/chains/ethereum/token.ts +790 -0
  68. package/src/chains/ethereum/transfer.ts +637 -0
  69. package/src/chains/ethereum/types.ts +456 -0
  70. package/src/chains/ethereum/viewing-key.ts +455 -0
  71. package/src/chains/near/commitment.ts +608 -0
  72. package/src/chains/near/constants.ts +284 -0
  73. package/src/chains/near/function-call.ts +871 -0
  74. package/src/chains/near/history.ts +654 -0
  75. package/src/chains/near/implicit-account.ts +840 -0
  76. package/src/chains/near/index.ts +393 -0
  77. package/src/chains/near/native-transfer.ts +658 -0
  78. package/src/chains/near/nep141.ts +775 -0
  79. package/src/chains/near/privacy-adapter.ts +889 -0
  80. package/src/chains/near/resolver.ts +971 -0
  81. package/src/chains/near/rpc.ts +1016 -0
  82. package/src/chains/near/stealth.ts +419 -0
  83. package/src/chains/near/types.ts +317 -0
  84. package/src/chains/near/viewing-key.ts +876 -0
  85. package/src/chains/solana/anchor-transfer.ts +386 -0
  86. package/src/chains/solana/commitment.ts +577 -0
  87. package/src/chains/solana/constants.ts +126 -12
  88. package/src/chains/solana/ephemeral-keys.ts +543 -0
  89. package/src/chains/solana/index.ts +252 -1
  90. package/src/chains/solana/key-derivation.ts +418 -0
  91. package/src/chains/solana/kit-compat.ts +334 -0
  92. package/src/chains/solana/optimizations.ts +560 -0
  93. package/src/chains/solana/privacy-adapter.ts +605 -0
  94. package/src/chains/solana/providers/generic.ts +47 -6
  95. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  96. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  97. package/src/chains/solana/providers/helius.ts +186 -33
  98. package/src/chains/solana/providers/index.ts +31 -0
  99. package/src/chains/solana/providers/interface.ts +61 -18
  100. package/src/chains/solana/providers/quicknode.ts +409 -0
  101. package/src/chains/solana/providers/triton.ts +426 -0
  102. package/src/chains/solana/providers/webhook.ts +338 -67
  103. package/src/chains/solana/rpc-client.ts +1150 -0
  104. package/src/chains/solana/scan.ts +83 -66
  105. package/src/chains/solana/sol-transfer.ts +732 -0
  106. package/src/chains/solana/spl-transfer.ts +886 -0
  107. package/src/chains/solana/stealth-scanner.ts +703 -0
  108. package/src/chains/solana/sunspot-verifier.ts +453 -0
  109. package/src/chains/solana/transaction-builder.ts +755 -0
  110. package/src/chains/solana/transfer.ts +74 -5
  111. package/src/chains/solana/types.ts +57 -6
  112. package/src/chains/solana/utils.ts +110 -0
  113. package/src/chains/solana/viewing-key.ts +807 -0
  114. package/src/compliance/fireblocks.ts +921 -0
  115. package/src/compliance/index.ts +23 -0
  116. package/src/compliance/range-sas.ts +398 -33
  117. package/src/config/endpoints.ts +100 -0
  118. package/src/crypto.ts +11 -8
  119. package/src/errors.ts +82 -0
  120. package/src/evm/erc4337-relayer.ts +830 -0
  121. package/src/evm/index.ts +47 -0
  122. package/src/fees/calculator.ts +396 -0
  123. package/src/fees/index.ts +87 -0
  124. package/src/fees/near-contract.ts +429 -0
  125. package/src/fees/types.ts +268 -0
  126. package/src/index.ts +686 -1
  127. package/src/intent.ts +6 -3
  128. package/src/logger.ts +324 -0
  129. package/src/network/index.ts +80 -0
  130. package/src/network/proxy.ts +691 -0
  131. package/src/optimizations/index.ts +541 -0
  132. package/src/oracle/types.ts +1 -0
  133. package/src/privacy-backends/arcium-types.ts +727 -0
  134. package/src/privacy-backends/arcium.ts +719 -0
  135. package/src/privacy-backends/combined-privacy.ts +866 -0
  136. package/src/privacy-backends/cspl-token.ts +595 -0
  137. package/src/privacy-backends/cspl-types.ts +512 -0
  138. package/src/privacy-backends/cspl.ts +907 -0
  139. package/src/privacy-backends/health.ts +488 -0
  140. package/src/privacy-backends/inco-types.ts +323 -0
  141. package/src/privacy-backends/inco.ts +616 -0
  142. package/src/privacy-backends/index.ts +254 -4
  143. package/src/privacy-backends/interface.ts +649 -6
  144. package/src/privacy-backends/lru-cache.ts +343 -0
  145. package/src/privacy-backends/magicblock.ts +458 -0
  146. package/src/privacy-backends/mock.ts +258 -0
  147. package/src/privacy-backends/privacycash.ts +13 -17
  148. package/src/privacy-backends/private-swap.ts +570 -0
  149. package/src/privacy-backends/rate-limiter.ts +683 -0
  150. package/src/privacy-backends/registry.ts +414 -2
  151. package/src/privacy-backends/router.ts +283 -3
  152. package/src/privacy-backends/shadowwire.ts +449 -0
  153. package/src/privacy-backends/sip-native.ts +3 -0
  154. package/src/privacy-logger.ts +191 -0
  155. package/src/production-safety.ts +373 -0
  156. package/src/proofs/aggregator.ts +1029 -0
  157. package/src/proofs/browser-composer.ts +1150 -0
  158. package/src/proofs/browser.ts +113 -25
  159. package/src/proofs/cache/index.ts +127 -0
  160. package/src/proofs/cache/interface.ts +545 -0
  161. package/src/proofs/cache/key-generator.ts +188 -0
  162. package/src/proofs/cache/lru-cache.ts +481 -0
  163. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  164. package/src/proofs/cache/persistent-cache.ts +788 -0
  165. package/src/proofs/compliance-proof.ts +872 -0
  166. package/src/proofs/composer/base.ts +923 -0
  167. package/src/proofs/composer/index.ts +25 -0
  168. package/src/proofs/composer/interface.ts +518 -0
  169. package/src/proofs/composer/types.ts +383 -0
  170. package/src/proofs/converters/halo2.ts +452 -0
  171. package/src/proofs/converters/index.ts +208 -0
  172. package/src/proofs/converters/interface.ts +363 -0
  173. package/src/proofs/converters/kimchi.ts +462 -0
  174. package/src/proofs/converters/noir.ts +451 -0
  175. package/src/proofs/fallback.ts +888 -0
  176. package/src/proofs/halo2.ts +42 -0
  177. package/src/proofs/index.ts +471 -0
  178. package/src/proofs/interface.ts +13 -0
  179. package/src/proofs/kimchi.ts +42 -0
  180. package/src/proofs/lazy.ts +1004 -0
  181. package/src/proofs/mock.ts +25 -1
  182. package/src/proofs/noir.ts +110 -29
  183. package/src/proofs/orchestrator.ts +960 -0
  184. package/src/proofs/parallel/concurrency.ts +297 -0
  185. package/src/proofs/parallel/dependency-graph.ts +602 -0
  186. package/src/proofs/parallel/executor.ts +420 -0
  187. package/src/proofs/parallel/index.ts +131 -0
  188. package/src/proofs/parallel/interface.ts +685 -0
  189. package/src/proofs/parallel/worker-pool.ts +644 -0
  190. package/src/proofs/providers/halo2.ts +560 -0
  191. package/src/proofs/providers/index.ts +34 -0
  192. package/src/proofs/providers/kimchi.ts +641 -0
  193. package/src/proofs/validator.ts +881 -0
  194. package/src/proofs/verifier.ts +867 -0
  195. package/src/quantum/index.ts +112 -0
  196. package/src/quantum/winternitz-vault.ts +639 -0
  197. package/src/quantum/wots.ts +611 -0
  198. package/src/settlement/backends/direct-chain.ts +1 -0
  199. package/src/settlement/index.ts +9 -0
  200. package/src/settlement/router.ts +732 -46
  201. package/src/solana/index.ts +72 -0
  202. package/src/solana/jito-relayer.ts +687 -0
  203. package/src/solana/noir-verifier-types.ts +430 -0
  204. package/src/solana/noir-verifier.ts +816 -0
  205. package/src/stealth/address-derivation.ts +193 -0
  206. package/src/stealth/ed25519.ts +431 -0
  207. package/src/stealth/index.ts +233 -0
  208. package/src/stealth/meta-address.ts +221 -0
  209. package/src/stealth/secp256k1.ts +368 -0
  210. package/src/stealth/utils.ts +194 -0
  211. package/src/stealth.ts +50 -1504
  212. package/src/sync/index.ts +106 -0
  213. package/src/sync/manager.ts +504 -0
  214. package/src/sync/mock-provider.ts +318 -0
  215. package/src/sync/oblivious.ts +625 -0
  216. package/src/tokens/index.ts +15 -0
  217. package/src/tokens/registry.ts +301 -0
  218. package/src/utils/deprecation.ts +94 -0
  219. package/src/utils/index.ts +9 -0
  220. package/src/wallet/ethereum/index.ts +68 -0
  221. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  222. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  223. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  224. package/src/wallet/ethereum/types.ts +3 -1
  225. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  226. package/src/wallet/hardware/index.ts +10 -0
  227. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  228. package/src/wallet/index.ts +71 -0
  229. package/src/wallet/near/adapter.ts +626 -0
  230. package/src/wallet/near/index.ts +86 -0
  231. package/src/wallet/near/meteor-wallet.ts +1153 -0
  232. package/src/wallet/near/my-near-wallet.ts +790 -0
  233. package/src/wallet/near/wallet-selector.ts +702 -0
  234. package/src/wallet/solana/adapter.ts +6 -4
  235. package/src/wallet/solana/index.ts +13 -0
  236. package/src/wallet/solana/privacy-adapter.ts +567 -0
  237. package/src/wallet/sui/types.ts +6 -4
  238. package/src/zcash/rpc-client.ts +13 -6
  239. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  240. package/dist/chunk-3INS3PR5.mjs +0 -884
  241. package/dist/chunk-3OVABDRH.mjs +0 -17096
  242. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  243. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  244. package/dist/chunk-E6SZWREQ.mjs +0 -57
  245. package/dist/chunk-F6F73W35.mjs +0 -16166
  246. package/dist/chunk-G33LB27A.mjs +0 -16166
  247. package/dist/chunk-HGU6HZRC.mjs +0 -231
  248. package/dist/chunk-L2K34JCU.mjs +0 -1496
  249. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  250. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  251. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  252. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  253. package/dist/constants-VOI7BSLK.mjs +0 -27
  254. package/dist/index-B71aXVzk.d.ts +0 -13264
  255. package/dist/index-BYZbDjal.d.ts +0 -11390
  256. package/dist/index-CHB3KuOB.d.mts +0 -11859
  257. package/dist/index-CzWPI6Le.d.ts +0 -11859
  258. package/dist/index-pOIIuwfV.d.mts +0 -13264
  259. package/dist/index-xbWjohNq.d.mts +0 -11390
  260. package/dist/solana-4O4K45VU.mjs +0 -46
  261. package/dist/solana-5EMCTPTS.mjs +0 -46
  262. package/dist/solana-NDABAZ6P.mjs +0 -56
  263. package/dist/solana-Q4NAVBTS.mjs +0 -46
  264. package/dist/solana-ZYO63LY5.mjs +0 -46
@@ -0,0 +1,788 @@
1
+ /**
2
+ * Persistent Cache Implementation for Proof Caching
3
+ *
4
+ * @module proofs/cache/persistent-cache
5
+ * @description IndexedDB (browser) and file-based (Node.js) persistent caching
6
+ *
7
+ * M20-13: Implement proof caching layer (#313)
8
+ */
9
+
10
+ import type { SingleProof } from '@sip-protocol/types'
11
+ import type {
12
+ CacheKey,
13
+ CacheEntryMetadata,
14
+ CacheLookupResult,
15
+ ProofCacheStats,
16
+ CacheEvent,
17
+ CacheEventListener,
18
+ PersistentCacheConfig,
19
+ IPersistentCache,
20
+ } from './interface'
21
+ import { DEFAULT_PERSISTENT_CONFIG, INITIAL_PROOF_CACHE_STATS } from './interface'
22
+
23
+ // ─── Environment Detection ───────────────────────────────────────────────────
24
+
25
+ function isBrowser(): boolean {
26
+ return typeof window !== 'undefined' && typeof window.indexedDB !== 'undefined'
27
+ }
28
+
29
+ function isNode(): boolean {
30
+ return typeof process !== 'undefined' && process.versions?.node !== undefined
31
+ }
32
+
33
+ // ─── IndexedDB Cache (Browser) ───────────────────────────────────────────────
34
+
35
+ const DB_VERSION = 1
36
+ const STORE_NAME = 'proofs'
37
+
38
+ /**
39
+ * IndexedDB-based persistent cache for browsers
40
+ */
41
+ export class IndexedDBCache<T = SingleProof> implements IPersistentCache<T> {
42
+ private readonly config: PersistentCacheConfig
43
+ private db: IDBDatabase | null = null
44
+ private readonly listeners: Set<CacheEventListener> = new Set()
45
+ private initialized = false
46
+
47
+ // Statistics
48
+ private totalLookups = 0
49
+ private hits = 0
50
+ private misses = 0
51
+ private totalLookupTimeMs = 0
52
+
53
+ constructor(config: Partial<PersistentCacheConfig> = {}) {
54
+ this.config = { ...DEFAULT_PERSISTENT_CONFIG, ...config }
55
+ }
56
+
57
+ /**
58
+ * Initialize the IndexedDB storage
59
+ */
60
+ async initialize(): Promise<void> {
61
+ if (this.initialized) return
62
+ if (!isBrowser()) {
63
+ throw new Error('IndexedDB is only available in browser environments')
64
+ }
65
+
66
+ return new Promise((resolve, reject) => {
67
+ const request = indexedDB.open(this.config.storageName, DB_VERSION)
68
+
69
+ request.onerror = () => {
70
+ reject(new Error(`Failed to open IndexedDB: ${request.error?.message}`))
71
+ }
72
+
73
+ request.onsuccess = () => {
74
+ this.db = request.result
75
+ this.initialized = true
76
+ resolve()
77
+ }
78
+
79
+ request.onupgradeneeded = (event) => {
80
+ const db = (event.target as IDBOpenDBRequest).result
81
+
82
+ // Create object store if it doesn't exist
83
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
84
+ const store = db.createObjectStore(STORE_NAME, { keyPath: 'key' })
85
+ store.createIndex('expiresAt', 'metadata.expiresAt', { unique: false })
86
+ store.createIndex('createdAt', 'metadata.createdAt', { unique: false })
87
+ }
88
+ }
89
+ })
90
+ }
91
+
92
+ /**
93
+ * Close the IndexedDB connection
94
+ */
95
+ async close(): Promise<void> {
96
+ if (this.db) {
97
+ this.db.close()
98
+ this.db = null
99
+ this.initialized = false
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Check if IndexedDB is available
105
+ */
106
+ isAvailable(): boolean {
107
+ return isBrowser() && typeof indexedDB !== 'undefined'
108
+ }
109
+
110
+ /**
111
+ * Get storage usage information
112
+ */
113
+ async getStorageInfo(): Promise<{ used: number; available: number; quota: number }> {
114
+ if (!isBrowser() || !navigator.storage?.estimate) {
115
+ return { used: 0, available: this.config.maxSizeBytes, quota: this.config.maxSizeBytes }
116
+ }
117
+
118
+ try {
119
+ const estimate = await navigator.storage.estimate()
120
+ return {
121
+ used: estimate.usage ?? 0,
122
+ available: (estimate.quota ?? this.config.maxSizeBytes) - (estimate.usage ?? 0),
123
+ quota: estimate.quota ?? this.config.maxSizeBytes,
124
+ }
125
+ } catch {
126
+ return { used: 0, available: this.config.maxSizeBytes, quota: this.config.maxSizeBytes }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Compact the storage (remove expired entries)
132
+ */
133
+ async compact(): Promise<void> {
134
+ if (!this.db) return
135
+
136
+ const now = Date.now()
137
+ const transaction = this.db.transaction(STORE_NAME, 'readwrite')
138
+ const store = transaction.objectStore(STORE_NAME)
139
+ const index = store.index('expiresAt')
140
+
141
+ return new Promise((resolve, reject) => {
142
+ const range = IDBKeyRange.bound(1, now) // Entries with expiresAt between 1 and now
143
+ const request = index.openCursor(range)
144
+
145
+ request.onsuccess = (event) => {
146
+ const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result
147
+ if (cursor) {
148
+ cursor.delete()
149
+ cursor.continue()
150
+ }
151
+ }
152
+
153
+ transaction.oncomplete = () => resolve()
154
+ transaction.onerror = () => reject(transaction.error)
155
+ })
156
+ }
157
+
158
+ /**
159
+ * Get an entry from the cache
160
+ */
161
+ async get(key: CacheKey | string): Promise<CacheLookupResult<T>> {
162
+ const startTime = Date.now()
163
+ const keyStr = typeof key === 'string' ? key : key.key
164
+
165
+ this.totalLookups++
166
+
167
+ if (!this.db) {
168
+ await this.initialize()
169
+ }
170
+
171
+ return new Promise((resolve) => {
172
+ const transaction = this.db!.transaction(STORE_NAME, 'readonly')
173
+ const store = transaction.objectStore(STORE_NAME)
174
+ const request = store.get(keyStr)
175
+
176
+ request.onsuccess = () => {
177
+ const entry = request.result as StoredEntry<T> | undefined
178
+
179
+ if (!entry) {
180
+ this.misses++
181
+ this.updateLookupTime(startTime)
182
+ this.emitEvent({ type: 'miss', key: keyStr, timestamp: Date.now() })
183
+ resolve({
184
+ hit: false,
185
+ missReason: 'not_found',
186
+ lookupTimeMs: Date.now() - startTime,
187
+ })
188
+ return
189
+ }
190
+
191
+ // Check expiration
192
+ if (entry.metadata.expiresAt > 0 && Date.now() > entry.metadata.expiresAt) {
193
+ this.delete(keyStr) // Async delete, don't wait
194
+ this.misses++
195
+ this.updateLookupTime(startTime)
196
+ this.emitEvent({ type: 'expire', key: keyStr, timestamp: Date.now() })
197
+ resolve({
198
+ hit: false,
199
+ missReason: 'expired',
200
+ lookupTimeMs: Date.now() - startTime,
201
+ })
202
+ return
203
+ }
204
+
205
+ this.hits++
206
+ this.updateLookupTime(startTime)
207
+ this.emitEvent({ type: 'hit', key: keyStr, timestamp: Date.now() })
208
+
209
+ resolve({
210
+ hit: true,
211
+ entry: {
212
+ key: typeof key === 'string'
213
+ ? { key, components: {} as CacheKey['components'], generatedAt: 0 }
214
+ : key,
215
+ value: entry.value,
216
+ metadata: entry.metadata,
217
+ },
218
+ lookupTimeMs: Date.now() - startTime,
219
+ })
220
+ }
221
+
222
+ request.onerror = () => {
223
+ this.misses++
224
+ this.updateLookupTime(startTime)
225
+ resolve({
226
+ hit: false,
227
+ missReason: 'invalid',
228
+ lookupTimeMs: Date.now() - startTime,
229
+ })
230
+ }
231
+ })
232
+ }
233
+
234
+ /**
235
+ * Set an entry in the cache
236
+ */
237
+ async set(key: CacheKey | string, value: T, ttlMs?: number): Promise<boolean> {
238
+ const keyStr = typeof key === 'string' ? key : key.key
239
+ const effectiveTtl = ttlMs ?? this.config.defaultTtlMs
240
+
241
+ if (!this.db) {
242
+ await this.initialize()
243
+ }
244
+
245
+ const metadata: CacheEntryMetadata = {
246
+ createdAt: Date.now(),
247
+ lastAccessedAt: Date.now(),
248
+ accessCount: 0,
249
+ sizeBytes: this.estimateSize(value),
250
+ ttlMs: effectiveTtl,
251
+ expiresAt: effectiveTtl > 0 ? Date.now() + effectiveTtl : 0,
252
+ source: 'generation',
253
+ }
254
+
255
+ const entry: StoredEntry<T> = {
256
+ key: keyStr,
257
+ value,
258
+ metadata,
259
+ }
260
+
261
+ return new Promise((resolve) => {
262
+ const transaction = this.db!.transaction(STORE_NAME, 'readwrite')
263
+ const store = transaction.objectStore(STORE_NAME)
264
+ const request = store.put(entry)
265
+
266
+ request.onsuccess = () => {
267
+ this.emitEvent({ type: 'set', key: keyStr, timestamp: Date.now() })
268
+ resolve(true)
269
+ }
270
+
271
+ request.onerror = () => {
272
+ resolve(false)
273
+ }
274
+ })
275
+ }
276
+
277
+ /**
278
+ * Delete an entry from the cache
279
+ */
280
+ async delete(key: CacheKey | string): Promise<boolean> {
281
+ const keyStr = typeof key === 'string' ? key : key.key
282
+
283
+ if (!this.db) return false
284
+
285
+ return new Promise((resolve) => {
286
+ const transaction = this.db!.transaction(STORE_NAME, 'readwrite')
287
+ const store = transaction.objectStore(STORE_NAME)
288
+ const request = store.delete(keyStr)
289
+
290
+ request.onsuccess = () => {
291
+ this.emitEvent({ type: 'delete', key: keyStr, timestamp: Date.now() })
292
+ resolve(true)
293
+ }
294
+
295
+ request.onerror = () => {
296
+ resolve(false)
297
+ }
298
+ })
299
+ }
300
+
301
+ /**
302
+ * Check if an entry exists
303
+ */
304
+ async has(key: CacheKey | string): Promise<boolean> {
305
+ const result = await this.get(key)
306
+ return result.hit
307
+ }
308
+
309
+ /**
310
+ * Clear all entries
311
+ */
312
+ async clear(): Promise<void> {
313
+ if (!this.db) return
314
+
315
+ return new Promise((resolve, reject) => {
316
+ const transaction = this.db!.transaction(STORE_NAME, 'readwrite')
317
+ const store = transaction.objectStore(STORE_NAME)
318
+ const request = store.clear()
319
+
320
+ request.onsuccess = () => {
321
+ this.emitEvent({ type: 'clear', timestamp: Date.now() })
322
+ resolve()
323
+ }
324
+
325
+ request.onerror = () => {
326
+ reject(request.error)
327
+ }
328
+ })
329
+ }
330
+
331
+ /**
332
+ * Get cache statistics
333
+ */
334
+ getStats(): ProofCacheStats {
335
+ return {
336
+ ...INITIAL_PROOF_CACHE_STATS,
337
+ totalLookups: this.totalLookups,
338
+ hits: this.hits,
339
+ misses: this.misses,
340
+ hitRate: this.totalLookups > 0 ? this.hits / this.totalLookups : 0,
341
+ maxSizeBytes: this.config.maxSizeBytes,
342
+ avgLookupTimeMs: this.totalLookups > 0 ? this.totalLookupTimeMs / this.totalLookups : 0,
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Get all keys matching a pattern
348
+ */
349
+ async keys(pattern?: string): Promise<string[]> {
350
+ if (!this.db) {
351
+ await this.initialize()
352
+ }
353
+
354
+ return new Promise((resolve, reject) => {
355
+ const transaction = this.db!.transaction(STORE_NAME, 'readonly')
356
+ const store = transaction.objectStore(STORE_NAME)
357
+ const request = store.getAllKeys()
358
+
359
+ request.onsuccess = () => {
360
+ let keys = request.result as string[]
361
+
362
+ if (pattern) {
363
+ const regexPattern = pattern
364
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
365
+ .replace(/\*/g, '.*')
366
+ .replace(/\?/g, '.')
367
+ const regex = new RegExp(`^${regexPattern}$`)
368
+ keys = keys.filter((key) => regex.test(key))
369
+ }
370
+
371
+ resolve(keys)
372
+ }
373
+
374
+ request.onerror = () => {
375
+ reject(request.error)
376
+ }
377
+ })
378
+ }
379
+
380
+ addEventListener(listener: CacheEventListener): void {
381
+ this.listeners.add(listener)
382
+ }
383
+
384
+ removeEventListener(listener: CacheEventListener): void {
385
+ this.listeners.delete(listener)
386
+ }
387
+
388
+ private estimateSize(value: T): number {
389
+ try {
390
+ const json = JSON.stringify(value, (_, v) => {
391
+ if (typeof v === 'bigint') return v.toString()
392
+ return v
393
+ })
394
+ return new TextEncoder().encode(json).length
395
+ } catch {
396
+ return 1024
397
+ }
398
+ }
399
+
400
+ private updateLookupTime(startTime: number): void {
401
+ this.totalLookupTimeMs += Date.now() - startTime
402
+ }
403
+
404
+ private emitEvent(event: CacheEvent): void {
405
+ for (const listener of this.listeners) {
406
+ try {
407
+ listener(event)
408
+ } catch {
409
+ // Ignore listener errors
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ // ─── File-based Cache (Node.js) ──────────────────────────────────────────────
416
+
417
+ interface StoredEntry<T> {
418
+ key: string
419
+ value: T
420
+ metadata: CacheEntryMetadata
421
+ }
422
+
423
+ /**
424
+ * File-based persistent cache for Node.js
425
+ */
426
+ export class FileCache<T = SingleProof> implements IPersistentCache<T> {
427
+ private readonly config: PersistentCacheConfig
428
+ private readonly listeners: Set<CacheEventListener> = new Set()
429
+ private initialized = false
430
+ private fs: typeof import('fs/promises') | null = null
431
+ private path: typeof import('path') | null = null
432
+ private cachePath = ''
433
+
434
+ // Statistics
435
+ private totalLookups = 0
436
+ private hits = 0
437
+ private misses = 0
438
+ private totalLookupTimeMs = 0
439
+
440
+ constructor(config: Partial<PersistentCacheConfig> = {}) {
441
+ this.config = { ...DEFAULT_PERSISTENT_CONFIG, ...config }
442
+ }
443
+
444
+ /**
445
+ * Initialize the file-based storage
446
+ */
447
+ async initialize(): Promise<void> {
448
+ if (this.initialized) return
449
+ if (!isNode()) {
450
+ throw new Error('File-based cache is only available in Node.js environments')
451
+ }
452
+
453
+ // Dynamic imports for Node.js modules
454
+ this.fs = await import('fs/promises')
455
+ this.path = await import('path')
456
+
457
+ // Determine cache path
458
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '/tmp'
459
+ this.cachePath = this.path.join(homeDir, '.cache', this.config.storageName)
460
+
461
+ // Ensure cache directory exists
462
+ await this.fs.mkdir(this.cachePath, { recursive: true })
463
+
464
+ this.initialized = true
465
+ }
466
+
467
+ /**
468
+ * Close the file cache (no-op for files)
469
+ */
470
+ async close(): Promise<void> {
471
+ this.initialized = false
472
+ }
473
+
474
+ /**
475
+ * Check if file system is available
476
+ */
477
+ isAvailable(): boolean {
478
+ return isNode()
479
+ }
480
+
481
+ /**
482
+ * Get storage usage information
483
+ */
484
+ async getStorageInfo(): Promise<{ used: number; available: number; quota: number }> {
485
+ if (!this.fs || !this.initialized) {
486
+ return { used: 0, available: this.config.maxSizeBytes, quota: this.config.maxSizeBytes }
487
+ }
488
+
489
+ try {
490
+ let totalSize = 0
491
+ const files = await this.fs.readdir(this.cachePath)
492
+
493
+ for (const file of files) {
494
+ if (file.endsWith('.json')) {
495
+ const stat = await this.fs.stat(this.path!.join(this.cachePath, file))
496
+ totalSize += stat.size
497
+ }
498
+ }
499
+
500
+ return {
501
+ used: totalSize,
502
+ available: this.config.maxSizeBytes - totalSize,
503
+ quota: this.config.maxSizeBytes,
504
+ }
505
+ } catch {
506
+ return { used: 0, available: this.config.maxSizeBytes, quota: this.config.maxSizeBytes }
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Compact the storage (remove expired entries)
512
+ */
513
+ async compact(): Promise<void> {
514
+ if (!this.fs || !this.initialized) return
515
+
516
+ const now = Date.now()
517
+ const files = await this.fs.readdir(this.cachePath)
518
+
519
+ for (const file of files) {
520
+ if (!file.endsWith('.json')) continue
521
+
522
+ try {
523
+ const filePath = this.path!.join(this.cachePath, file)
524
+ const content = await this.fs.readFile(filePath, 'utf-8')
525
+ const entry = JSON.parse(content) as StoredEntry<T>
526
+
527
+ if (entry.metadata.expiresAt > 0 && now > entry.metadata.expiresAt) {
528
+ await this.fs.unlink(filePath)
529
+ }
530
+ } catch {
531
+ // Ignore errors, file might be corrupted
532
+ }
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Get an entry from the cache
538
+ */
539
+ async get(key: CacheKey | string): Promise<CacheLookupResult<T>> {
540
+ const startTime = Date.now()
541
+ const keyStr = typeof key === 'string' ? key : key.key
542
+
543
+ this.totalLookups++
544
+
545
+ if (!this.initialized) {
546
+ await this.initialize()
547
+ }
548
+
549
+ const filePath = this.getFilePath(keyStr)
550
+
551
+ try {
552
+ const content = await this.fs!.readFile(filePath, 'utf-8')
553
+ const entry = JSON.parse(content) as StoredEntry<T>
554
+
555
+ // Check expiration
556
+ if (entry.metadata.expiresAt > 0 && Date.now() > entry.metadata.expiresAt) {
557
+ await this.fs!.unlink(filePath).catch(() => {})
558
+ this.misses++
559
+ this.updateLookupTime(startTime)
560
+ this.emitEvent({ type: 'expire', key: keyStr, timestamp: Date.now() })
561
+ return {
562
+ hit: false,
563
+ missReason: 'expired',
564
+ lookupTimeMs: Date.now() - startTime,
565
+ }
566
+ }
567
+
568
+ this.hits++
569
+ this.updateLookupTime(startTime)
570
+ this.emitEvent({ type: 'hit', key: keyStr, timestamp: Date.now() })
571
+
572
+ return {
573
+ hit: true,
574
+ entry: {
575
+ key: typeof key === 'string'
576
+ ? { key, components: {} as CacheKey['components'], generatedAt: 0 }
577
+ : key,
578
+ value: entry.value,
579
+ metadata: entry.metadata,
580
+ },
581
+ lookupTimeMs: Date.now() - startTime,
582
+ }
583
+ } catch {
584
+ this.misses++
585
+ this.updateLookupTime(startTime)
586
+ this.emitEvent({ type: 'miss', key: keyStr, timestamp: Date.now() })
587
+ return {
588
+ hit: false,
589
+ missReason: 'not_found',
590
+ lookupTimeMs: Date.now() - startTime,
591
+ }
592
+ }
593
+ }
594
+
595
+ /**
596
+ * Set an entry in the cache
597
+ */
598
+ async set(key: CacheKey | string, value: T, ttlMs?: number): Promise<boolean> {
599
+ const keyStr = typeof key === 'string' ? key : key.key
600
+ const effectiveTtl = ttlMs ?? this.config.defaultTtlMs
601
+
602
+ if (!this.initialized) {
603
+ await this.initialize()
604
+ }
605
+
606
+ const metadata: CacheEntryMetadata = {
607
+ createdAt: Date.now(),
608
+ lastAccessedAt: Date.now(),
609
+ accessCount: 0,
610
+ sizeBytes: 0, // Will be calculated after serialization
611
+ ttlMs: effectiveTtl,
612
+ expiresAt: effectiveTtl > 0 ? Date.now() + effectiveTtl : 0,
613
+ source: 'generation',
614
+ }
615
+
616
+ const entry: StoredEntry<T> = {
617
+ key: keyStr,
618
+ value,
619
+ metadata,
620
+ }
621
+
622
+ try {
623
+ const content = JSON.stringify(entry, (_, v) => {
624
+ if (typeof v === 'bigint') return `bigint:${v.toString()}`
625
+ return v
626
+ })
627
+
628
+ const filePath = this.getFilePath(keyStr)
629
+ await this.fs!.writeFile(filePath, content, 'utf-8')
630
+
631
+ this.emitEvent({ type: 'set', key: keyStr, timestamp: Date.now() })
632
+ return true
633
+ } catch {
634
+ return false
635
+ }
636
+ }
637
+
638
+ /**
639
+ * Delete an entry from the cache
640
+ */
641
+ async delete(key: CacheKey | string): Promise<boolean> {
642
+ const keyStr = typeof key === 'string' ? key : key.key
643
+
644
+ if (!this.initialized) return false
645
+
646
+ try {
647
+ const filePath = this.getFilePath(keyStr)
648
+ await this.fs!.unlink(filePath)
649
+ this.emitEvent({ type: 'delete', key: keyStr, timestamp: Date.now() })
650
+ return true
651
+ } catch {
652
+ return false
653
+ }
654
+ }
655
+
656
+ /**
657
+ * Check if an entry exists
658
+ */
659
+ async has(key: CacheKey | string): Promise<boolean> {
660
+ const result = await this.get(key)
661
+ return result.hit
662
+ }
663
+
664
+ /**
665
+ * Clear all entries
666
+ */
667
+ async clear(): Promise<void> {
668
+ if (!this.fs || !this.initialized) return
669
+
670
+ const files = await this.fs.readdir(this.cachePath)
671
+
672
+ for (const file of files) {
673
+ if (file.endsWith('.json')) {
674
+ await this.fs.unlink(this.path!.join(this.cachePath, file)).catch(() => {})
675
+ }
676
+ }
677
+
678
+ this.emitEvent({ type: 'clear', timestamp: Date.now() })
679
+ }
680
+
681
+ /**
682
+ * Get cache statistics
683
+ */
684
+ getStats(): ProofCacheStats {
685
+ return {
686
+ ...INITIAL_PROOF_CACHE_STATS,
687
+ totalLookups: this.totalLookups,
688
+ hits: this.hits,
689
+ misses: this.misses,
690
+ hitRate: this.totalLookups > 0 ? this.hits / this.totalLookups : 0,
691
+ maxSizeBytes: this.config.maxSizeBytes,
692
+ avgLookupTimeMs: this.totalLookups > 0 ? this.totalLookupTimeMs / this.totalLookups : 0,
693
+ }
694
+ }
695
+
696
+ /**
697
+ * Get all keys matching a pattern
698
+ */
699
+ async keys(pattern?: string): Promise<string[]> {
700
+ if (!this.initialized) {
701
+ await this.initialize()
702
+ }
703
+
704
+ const files = await this.fs!.readdir(this.cachePath)
705
+ let keys = files
706
+ .filter((f) => f.endsWith('.json'))
707
+ .map((f) => this.fileNameToKey(f))
708
+
709
+ if (pattern) {
710
+ const regexPattern = pattern
711
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
712
+ .replace(/\*/g, '.*')
713
+ .replace(/\?/g, '.')
714
+ const regex = new RegExp(`^${regexPattern}$`)
715
+ keys = keys.filter((key) => regex.test(key))
716
+ }
717
+
718
+ return keys
719
+ }
720
+
721
+ addEventListener(listener: CacheEventListener): void {
722
+ this.listeners.add(listener)
723
+ }
724
+
725
+ removeEventListener(listener: CacheEventListener): void {
726
+ this.listeners.delete(listener)
727
+ }
728
+
729
+ private getFilePath(key: string): string {
730
+ // Sanitize key for file system
731
+ const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, '_')
732
+ return this.path!.join(this.cachePath, `${safeKey}.json`)
733
+ }
734
+
735
+ private fileNameToKey(fileName: string): string {
736
+ // This is a simplified reverse - in practice, you'd need a mapping
737
+ return fileName.replace('.json', '')
738
+ }
739
+
740
+ private updateLookupTime(startTime: number): void {
741
+ this.totalLookupTimeMs += Date.now() - startTime
742
+ }
743
+
744
+ private emitEvent(event: CacheEvent): void {
745
+ for (const listener of this.listeners) {
746
+ try {
747
+ listener(event)
748
+ } catch {
749
+ // Ignore listener errors
750
+ }
751
+ }
752
+ }
753
+ }
754
+
755
+ // ─── Factory Functions ───────────────────────────────────────────────────────
756
+
757
+ /**
758
+ * Create the appropriate persistent cache for the current environment
759
+ */
760
+ export function createPersistentCache<T = SingleProof>(
761
+ config?: Partial<PersistentCacheConfig>
762
+ ): IPersistentCache<T> {
763
+ if (isBrowser()) {
764
+ return new IndexedDBCache<T>(config)
765
+ }
766
+ if (isNode()) {
767
+ return new FileCache<T>(config)
768
+ }
769
+ throw new Error('No persistent storage available in this environment')
770
+ }
771
+
772
+ /**
773
+ * Create an IndexedDB cache (browser only)
774
+ */
775
+ export function createIndexedDBCache<T = SingleProof>(
776
+ config?: Partial<PersistentCacheConfig>
777
+ ): IPersistentCache<T> {
778
+ return new IndexedDBCache<T>(config)
779
+ }
780
+
781
+ /**
782
+ * Create a file-based cache (Node.js only)
783
+ */
784
+ export function createFileCache<T = SingleProof>(
785
+ config?: Partial<PersistentCacheConfig>
786
+ ): IPersistentCache<T> {
787
+ return new FileCache<T>(config)
788
+ }