@cmdoss/memwal-sdk 0.8.0 → 1.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 (209) hide show
  1. package/README.md +522 -160
  2. package/dist/client/ClientMemoryManager.d.ts.map +1 -1
  3. package/dist/client/ClientMemoryManager.js +25 -8
  4. package/dist/client/ClientMemoryManager.js.map +1 -1
  5. package/dist/client/PersonalDataWallet.d.ts.map +1 -1
  6. package/dist/client/SimplePDWClient.d.ts +62 -2
  7. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  8. package/dist/client/SimplePDWClient.js +96 -11
  9. package/dist/client/SimplePDWClient.js.map +1 -1
  10. package/dist/client/namespaces/IndexNamespace.d.ts +1 -1
  11. package/dist/client/namespaces/IndexNamespace.d.ts.map +1 -1
  12. package/dist/client/namespaces/IndexNamespace.js +7 -4
  13. package/dist/client/namespaces/IndexNamespace.js.map +1 -1
  14. package/dist/client/namespaces/MemoryNamespace.d.ts +47 -0
  15. package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
  16. package/dist/client/namespaces/MemoryNamespace.js +257 -27
  17. package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
  18. package/dist/client/namespaces/consolidated/AdvancedNamespace.d.ts +215 -0
  19. package/dist/client/namespaces/consolidated/AdvancedNamespace.d.ts.map +1 -0
  20. package/dist/client/namespaces/consolidated/AdvancedNamespace.js +214 -0
  21. package/dist/client/namespaces/consolidated/AdvancedNamespace.js.map +1 -0
  22. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +3 -1
  23. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
  24. package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
  25. package/dist/client/namespaces/consolidated/index.d.ts +1 -0
  26. package/dist/client/namespaces/consolidated/index.d.ts.map +1 -1
  27. package/dist/client/namespaces/consolidated/index.js +1 -0
  28. package/dist/client/namespaces/consolidated/index.js.map +1 -1
  29. package/dist/config/ConfigurationHelper.js +61 -61
  30. package/dist/config/defaults.d.ts.map +1 -1
  31. package/dist/config/defaults.js +9 -4
  32. package/dist/config/defaults.js.map +1 -1
  33. package/dist/config/index.d.ts +1 -0
  34. package/dist/config/index.d.ts.map +1 -1
  35. package/dist/config/index.js +2 -0
  36. package/dist/config/index.js.map +1 -1
  37. package/dist/config/modelDefaults.d.ts +67 -0
  38. package/dist/config/modelDefaults.d.ts.map +1 -0
  39. package/dist/config/modelDefaults.js +91 -0
  40. package/dist/config/modelDefaults.js.map +1 -0
  41. package/dist/core/types/index.d.ts +4 -0
  42. package/dist/core/types/index.d.ts.map +1 -1
  43. package/dist/core/types/index.js.map +1 -1
  44. package/dist/graph/GraphService.d.ts.map +1 -1
  45. package/dist/graph/GraphService.js +22 -21
  46. package/dist/graph/GraphService.js.map +1 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/infrastructure/walrus/WalrusStorageService.d.ts +6 -0
  52. package/dist/infrastructure/walrus/WalrusStorageService.d.ts.map +1 -1
  53. package/dist/infrastructure/walrus/WalrusStorageService.js +23 -4
  54. package/dist/infrastructure/walrus/WalrusStorageService.js.map +1 -1
  55. package/dist/langchain/createPDWRAG.js +30 -30
  56. package/dist/pipeline/MemoryPipeline.d.ts.map +1 -1
  57. package/dist/pipeline/MemoryPipeline.js +2 -1
  58. package/dist/pipeline/MemoryPipeline.js.map +1 -1
  59. package/dist/services/EmbeddingService.d.ts +9 -0
  60. package/dist/services/EmbeddingService.d.ts.map +1 -1
  61. package/dist/services/EmbeddingService.js +31 -10
  62. package/dist/services/EmbeddingService.js.map +1 -1
  63. package/dist/services/GeminiAIService.d.ts.map +1 -1
  64. package/dist/services/GeminiAIService.js +311 -310
  65. package/dist/services/GeminiAIService.js.map +1 -1
  66. package/dist/services/MemoryIndexService.d.ts +2 -0
  67. package/dist/services/MemoryIndexService.d.ts.map +1 -1
  68. package/dist/services/MemoryIndexService.js +11 -4
  69. package/dist/services/MemoryIndexService.js.map +1 -1
  70. package/dist/services/StorageService.d.ts +4 -1
  71. package/dist/services/StorageService.d.ts.map +1 -1
  72. package/dist/services/StorageService.js.map +1 -1
  73. package/dist/services/VectorService.js +1 -1
  74. package/dist/services/VectorService.js.map +1 -1
  75. package/dist/services/storage/QuiltBatchManager.d.ts +7 -0
  76. package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
  77. package/dist/services/storage/QuiltBatchManager.js +24 -5
  78. package/dist/services/storage/QuiltBatchManager.js.map +1 -1
  79. package/dist/services/storage/WalrusStorageManager.d.ts +10 -1
  80. package/dist/services/storage/WalrusStorageManager.d.ts.map +1 -1
  81. package/dist/services/storage/WalrusStorageManager.js +53 -12
  82. package/dist/services/storage/WalrusStorageManager.js.map +1 -1
  83. package/dist/vector/BrowserHnswIndexService.js +3 -3
  84. package/dist/vector/BrowserHnswIndexService.js.map +1 -1
  85. package/dist/vector/HnswWasmService.js +1 -1
  86. package/dist/vector/HnswWasmService.js.map +1 -1
  87. package/dist/vector/NodeHnswService.js +5 -5
  88. package/dist/vector/NodeHnswService.js.map +1 -1
  89. package/dist/vector/createHnswService.d.ts +4 -0
  90. package/dist/vector/createHnswService.d.ts.map +1 -1
  91. package/dist/vector/createHnswService.js +15 -3
  92. package/dist/vector/createHnswService.js.map +1 -1
  93. package/package.json +1 -1
  94. package/src/access/PermissionService.ts +635 -635
  95. package/src/aggregation/AggregationService.ts +389 -389
  96. package/src/ai-sdk/PDWVectorStore.ts +715 -715
  97. package/src/ai-sdk/index.ts +65 -65
  98. package/src/ai-sdk/tools.ts +460 -460
  99. package/src/ai-sdk/types.ts +404 -404
  100. package/src/batch/BatchManager.ts +597 -597
  101. package/src/batch/BatchingService.ts +429 -429
  102. package/src/batch/MemoryProcessingCache.ts +492 -492
  103. package/src/batch/index.ts +30 -30
  104. package/src/browser.ts +200 -200
  105. package/src/client/ClientMemoryManager.ts +1004 -987
  106. package/src/client/PersonalDataWallet.ts +345 -345
  107. package/src/client/SimplePDWClient.ts +1387 -1289
  108. package/src/client/factory.ts +154 -154
  109. package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
  110. package/src/client/namespaces/BatchNamespace.ts +356 -356
  111. package/src/client/namespaces/CacheNamespace.ts +123 -123
  112. package/src/client/namespaces/CapabilityNamespace.ts +217 -217
  113. package/src/client/namespaces/ClassifyNamespace.ts +169 -169
  114. package/src/client/namespaces/ContextNamespace.ts +297 -297
  115. package/src/client/namespaces/EncryptionNamespace.ts +221 -221
  116. package/src/client/namespaces/GraphNamespace.ts +468 -468
  117. package/src/client/namespaces/IndexNamespace.ts +364 -361
  118. package/src/client/namespaces/MemoryNamespace.ts +1704 -1422
  119. package/src/client/namespaces/PermissionsNamespace.ts +254 -254
  120. package/src/client/namespaces/PipelineNamespace.ts +220 -220
  121. package/src/client/namespaces/StorageNamespace.ts +458 -458
  122. package/src/client/namespaces/TxNamespace.ts +260 -260
  123. package/src/client/namespaces/WalletNamespace.ts +243 -243
  124. package/src/client/namespaces/consolidated/AdvancedNamespace.ts +264 -0
  125. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -607
  126. package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
  127. package/src/client/namespaces/consolidated/StorageNamespace.ts +1143 -1141
  128. package/src/client/namespaces/consolidated/index.ts +41 -39
  129. package/src/client/signers/KeypairSigner.ts +108 -108
  130. package/src/client/signers/UnifiedSigner.ts +110 -110
  131. package/src/client/signers/WalletAdapterSigner.ts +159 -159
  132. package/src/client/signers/index.ts +26 -26
  133. package/src/config/ConfigurationHelper.ts +412 -412
  134. package/src/config/defaults.ts +56 -51
  135. package/src/config/index.ts +16 -9
  136. package/src/config/modelDefaults.ts +103 -0
  137. package/src/config/validation.ts +70 -70
  138. package/src/core/index.ts +14 -14
  139. package/src/core/interfaces/IService.ts +307 -307
  140. package/src/core/interfaces/index.ts +8 -8
  141. package/src/core/types/capability.ts +297 -297
  142. package/src/core/types/index.ts +874 -870
  143. package/src/core/types/wallet.ts +270 -270
  144. package/src/core/types.ts +9 -9
  145. package/src/core/wallet.ts +222 -222
  146. package/src/embedding/index.ts +19 -19
  147. package/src/embedding/types.ts +357 -357
  148. package/src/errors/index.ts +602 -602
  149. package/src/errors/recovery.ts +461 -461
  150. package/src/errors/validation.ts +567 -567
  151. package/src/generated/pdw/capability.ts +319 -319
  152. package/src/graph/GraphService.ts +888 -887
  153. package/src/graph/KnowledgeGraphManager.ts +728 -728
  154. package/src/graph/index.ts +25 -25
  155. package/src/index.ts +498 -498
  156. package/src/infrastructure/index.ts +22 -22
  157. package/src/infrastructure/seal/EncryptionService.ts +628 -628
  158. package/src/infrastructure/seal/SealService.ts +613 -613
  159. package/src/infrastructure/seal/index.ts +9 -9
  160. package/src/infrastructure/sui/BlockchainManager.ts +627 -627
  161. package/src/infrastructure/sui/SuiService.ts +888 -888
  162. package/src/infrastructure/sui/index.ts +9 -9
  163. package/src/infrastructure/walrus/StorageManager.ts +604 -604
  164. package/src/infrastructure/walrus/WalrusStorageService.ts +637 -612
  165. package/src/infrastructure/walrus/index.ts +9 -9
  166. package/src/langchain/createPDWRAG.ts +303 -303
  167. package/src/langchain/index.ts +47 -47
  168. package/src/permissions/ConsentRepository.browser.ts +249 -249
  169. package/src/permissions/ConsentRepository.ts +364 -364
  170. package/src/pipeline/MemoryPipeline.ts +863 -862
  171. package/src/pipeline/PipelineManager.ts +683 -683
  172. package/src/pipeline/index.ts +26 -26
  173. package/src/retrieval/AdvancedSearchService.ts +629 -629
  174. package/src/retrieval/MemoryAnalyticsService.ts +711 -711
  175. package/src/retrieval/MemoryDecryptionPipeline.ts +825 -825
  176. package/src/retrieval/index.ts +42 -42
  177. package/src/services/BatchService.ts +352 -352
  178. package/src/services/CapabilityService.ts +464 -464
  179. package/src/services/ClassifierService.ts +465 -465
  180. package/src/services/CrossContextPermissionService.ts +486 -486
  181. package/src/services/EmbeddingService.ts +796 -771
  182. package/src/services/EncryptionService.ts +712 -712
  183. package/src/services/GeminiAIService.ts +754 -753
  184. package/src/services/MemoryIndexService.ts +1009 -1003
  185. package/src/services/MemoryService.ts +369 -369
  186. package/src/services/QueryService.ts +890 -890
  187. package/src/services/StorageService.ts +1185 -1182
  188. package/src/services/TransactionService.ts +838 -838
  189. package/src/services/VectorService.ts +462 -462
  190. package/src/services/ViewService.ts +484 -484
  191. package/src/services/index.ts +25 -25
  192. package/src/services/storage/BlobAttributesManager.ts +333 -333
  193. package/src/services/storage/KnowledgeGraphManager.ts +425 -425
  194. package/src/services/storage/MemorySearchManager.ts +387 -387
  195. package/src/services/storage/QuiltBatchManager.ts +1157 -1130
  196. package/src/services/storage/WalrusMetadataManager.ts +268 -268
  197. package/src/services/storage/WalrusStorageManager.ts +333 -287
  198. package/src/services/storage/index.ts +57 -57
  199. package/src/types/index.ts +13 -13
  200. package/src/utils/index.ts +76 -76
  201. package/src/utils/memoryIndexOnChain.ts +507 -507
  202. package/src/vector/BrowserHnswIndexService.ts +758 -758
  203. package/src/vector/HnswWasmService.ts +731 -731
  204. package/src/vector/IHnswService.ts +233 -233
  205. package/src/vector/NodeHnswService.ts +833 -833
  206. package/src/vector/createHnswService.ts +147 -135
  207. package/src/vector/index.ts +56 -56
  208. package/src/wallet/ContextWalletService.ts +656 -656
  209. package/src/wallet/MainWalletService.ts +317 -317
@@ -1,1141 +1,1143 @@
1
- /**
2
- * Storage Namespace - Consolidated Storage Operations
3
- *
4
- * Merges functionality from:
5
- * - StorageService: Walrus blob storage operations
6
- * - CacheNamespace: LRU in-memory caching
7
- *
8
- * Provides unified interface for data persistence (Walrus) and caching.
9
- *
10
- * @module client/namespaces/consolidated
11
- */
12
-
13
- import type { ServiceContainer } from '../../SimplePDWClient';
14
-
15
- // ============================================================================
16
- // Types
17
- // ============================================================================
18
-
19
- /**
20
- * Cache statistics
21
- */
22
- export interface CacheStats {
23
- size: number;
24
- totalAccess: number;
25
- hitRate: number;
26
- oldestItem?: Date;
27
- newestItem?: Date;
28
- }
29
-
30
- /**
31
- * Upload result from Walrus
32
- */
33
- export interface UploadResult {
34
- blobId: string;
35
- size: number;
36
- contentType?: string;
37
- }
38
-
39
- /**
40
- * Memory package structure
41
- */
42
- export interface MemoryPackage {
43
- content: string;
44
- contentType: string;
45
- metadata?: Record<string, any>;
46
- embedding?: number[];
47
- createdAt?: number;
48
- }
49
-
50
- /**
51
- * Upload options
52
- */
53
- export interface UploadOptions {
54
- contentType?: string;
55
- encrypt?: boolean;
56
- epochs?: number;
57
- }
58
-
59
- // ============================================================================
60
- // Sub-Namespaces
61
- // ============================================================================
62
-
63
- /**
64
- * Cache sub-namespace for LRU caching operations
65
- */
66
- class CacheSubNamespace {
67
- constructor(private services: ServiceContainer) {}
68
-
69
- /**
70
- * Get cached value
71
- *
72
- * @param key - Cache key
73
- * @returns Cached value or null if not found/expired
74
- *
75
- * @example
76
- * ```typescript
77
- * const cached = pdw.storage.cache.get<User>('user:123');
78
- * if (cached) {
79
- * console.log('Cache hit:', cached);
80
- * }
81
- * ```
82
- */
83
- get<T = any>(key: string): T | null {
84
- if (!this.services.batchService) {
85
- throw new Error('Batch service (cache) not configured.');
86
- }
87
- return this.services.batchService.getCache<T>(key);
88
- }
89
-
90
- /**
91
- * Set cache value
92
- *
93
- * @param key - Cache key
94
- * @param value - Value to cache
95
- * @param ttl - Time-to-live in milliseconds (optional)
96
- *
97
- * @example
98
- * ```typescript
99
- * pdw.storage.cache.set('user:123', userData, 60000); // 1 minute TTL
100
- * ```
101
- */
102
- set<T = any>(key: string, value: T, ttl?: number): void {
103
- if (!this.services.batchService) {
104
- throw new Error('Batch service (cache) not configured.');
105
- }
106
- this.services.batchService.setCache(key, value, ttl);
107
- }
108
-
109
- /**
110
- * Check if key exists in cache
111
- *
112
- * @param key - Cache key
113
- * @returns True if key exists and not expired
114
- */
115
- has(key: string): boolean {
116
- if (!this.services.batchService) {
117
- throw new Error('Batch service (cache) not configured.');
118
- }
119
- return this.services.batchService.hasCache(key);
120
- }
121
-
122
- /**
123
- * Delete cache entry
124
- *
125
- * @param key - Cache key
126
- * @returns True if deleted, false if not found
127
- */
128
- delete(key: string): boolean {
129
- if (!this.services.batchService) {
130
- throw new Error('Batch service (cache) not configured.');
131
- }
132
- return this.services.batchService.deleteCache(key);
133
- }
134
-
135
- /**
136
- * Clear all cache entries
137
- */
138
- clear(): void {
139
- if (!this.services.batchService) {
140
- throw new Error('Batch service (cache) not configured.');
141
- }
142
- this.services.batchService.clearCache();
143
- }
144
-
145
- /**
146
- * Get cache statistics
147
- *
148
- * @returns Cache statistics
149
- */
150
- stats(): CacheStats {
151
- if (!this.services.batchService) {
152
- throw new Error('Batch service (cache) not configured.');
153
- }
154
- return this.services.batchService.getCacheStats();
155
- }
156
- }
157
-
158
- // ============================================================================
159
- // Storage Namespace
160
- // ============================================================================
161
-
162
- /**
163
- * Storage Namespace - Unified Storage Operations
164
- *
165
- * Consolidates Walrus blob storage and in-memory caching.
166
- *
167
- * @example
168
- * ```typescript
169
- * // Upload to Walrus
170
- * const result = await pdw.storage.upload(data);
171
- * console.log('Blob ID:', result.blobId);
172
- *
173
- * // Download from Walrus
174
- * const data = await pdw.storage.download(blobId);
175
- *
176
- * // Use cache
177
- * pdw.storage.cache.set('key', value, 60000);
178
- * const cached = pdw.storage.cache.get('key');
179
- * ```
180
- */
181
- export class StorageNamespace {
182
- private _cache: CacheSubNamespace;
183
-
184
- constructor(private services: ServiceContainer) {
185
- this._cache = new CacheSubNamespace(services);
186
- }
187
-
188
- /**
189
- * Cache operations
190
- */
191
- get cache(): CacheSubNamespace {
192
- return this._cache;
193
- }
194
-
195
- // ==========================================================================
196
- // Walrus Storage Operations
197
- // ==========================================================================
198
-
199
- /**
200
- * Upload data to Walrus
201
- *
202
- * @param data - Data to upload (string, Uint8Array, or object)
203
- * @param options - Upload options
204
- * @returns Upload result with blob ID
205
- *
206
- * @example
207
- * ```typescript
208
- * // Upload raw bytes
209
- * const result = await pdw.storage.upload(new Uint8Array([1, 2, 3]));
210
- *
211
- * // Upload JSON
212
- * const result = await pdw.storage.upload({ name: 'test' });
213
- *
214
- * // Upload with options
215
- * const result = await pdw.storage.upload(data, { encrypt: true });
216
- * ```
217
- */
218
- async upload(data: string | Uint8Array | object, options: UploadOptions = {}): Promise<UploadResult> {
219
- if (!this.services.storage) {
220
- throw new Error('Storage service not configured.');
221
- }
222
-
223
- // Convert data to Uint8Array
224
- let bytes: Uint8Array;
225
- let contentType = options.contentType || 'application/octet-stream';
226
-
227
- if (typeof data === 'string') {
228
- bytes = new TextEncoder().encode(data);
229
- contentType = options.contentType || 'text/plain';
230
- } else if (data instanceof Uint8Array) {
231
- bytes = data;
232
- } else {
233
- bytes = new TextEncoder().encode(JSON.stringify(data));
234
- contentType = options.contentType || 'application/json';
235
- }
236
-
237
- // Encrypt if requested
238
- if (options.encrypt && this.services.encryption) {
239
- const encryptResult = await this.services.encryption.encrypt(
240
- bytes,
241
- this.services.config.userAddress,
242
- 2
243
- );
244
- bytes = encryptResult.encryptedObject;
245
- }
246
-
247
- // Upload to Walrus using uploadBlob method
248
- const result = await this.services.storage.uploadBlob(bytes, {
249
- signer: this.services.config.signer,
250
- epochs: options.epochs,
251
- deletable: true
252
- });
253
-
254
- return {
255
- blobId: result.blobId,
256
- size: bytes.length,
257
- contentType
258
- };
259
- }
260
-
261
- /**
262
- * Download data from Walrus
263
- *
264
- * @param blobId - Blob ID to download
265
- * @returns Raw data as Uint8Array
266
- *
267
- * @example
268
- * ```typescript
269
- * const data = await pdw.storage.download('blobId123');
270
- * const text = new TextDecoder().decode(data);
271
- * ```
272
- */
273
- async download(blobId: string): Promise<Uint8Array> {
274
- if (!this.services.storage) {
275
- throw new Error('Storage service not configured.');
276
- }
277
-
278
- // Use retrieveFromWalrusOnly which returns { content, metadata }
279
- const result = await this.services.storage.retrieveFromWalrusOnly(blobId);
280
- return result.content;
281
- }
282
-
283
- /**
284
- * Download and parse JSON from Walrus
285
- *
286
- * @param blobId - Blob ID to download
287
- * @returns Parsed JSON object
288
- *
289
- * @example
290
- * ```typescript
291
- * const data = await pdw.storage.downloadJson<MyType>('blobId123');
292
- * ```
293
- */
294
- async downloadJson<T = any>(blobId: string): Promise<T> {
295
- const bytes = await this.download(blobId);
296
- const text = new TextDecoder().decode(bytes);
297
- return JSON.parse(text);
298
- }
299
-
300
- /**
301
- * Download and decrypt data from Walrus
302
- *
303
- * @param blobId - Blob ID to download
304
- * @param options - Decryption options
305
- * @returns Decrypted data
306
- *
307
- * @example
308
- * ```typescript
309
- * const data = await pdw.storage.downloadDecrypted('blobId123');
310
- * ```
311
- */
312
- async downloadDecrypted(blobId: string, options?: {
313
- memoryCapId?: string;
314
- keyId?: Uint8Array;
315
- }): Promise<Uint8Array> {
316
- if (!this.services.storage) {
317
- throw new Error('Storage service not configured.');
318
- }
319
- if (!this.services.encryption) {
320
- throw new Error('Encryption service not configured.');
321
- }
322
-
323
- const encryptedData = await this.download(blobId);
324
-
325
- return await this.services.encryption.decrypt({
326
- encryptedContent: encryptedData,
327
- userAddress: this.services.config.userAddress,
328
- requestingWallet: this.services.config.userAddress,
329
- memoryCapId: options?.memoryCapId,
330
- keyId: options?.keyId
331
- });
332
- }
333
-
334
- /**
335
- * Store memory package to Walrus
336
- *
337
- * Higher-level method that stores a complete memory package.
338
- * Automatically encrypts content if encryption is enabled in config.
339
- *
340
- * @param memoryPackage - Memory package to store
341
- * @returns Upload result
342
- */
343
- async storeMemoryPackage(memoryPackage: MemoryPackage): Promise<UploadResult> {
344
- if (!this.services.storage) {
345
- throw new Error('Storage service not configured.');
346
- }
347
-
348
- // Check if encryption is enabled
349
- const encryptionEnabled = this.services.config.features?.enableEncryption ?? true;
350
- let encryptedContent: Uint8Array | undefined;
351
- let encryptedEmbedding: Uint8Array | undefined; // Option A v2: encrypted embedding
352
- let encryptionType: string | undefined;
353
- let memoryCapId: string | undefined;
354
- let keyId: string | undefined;
355
-
356
- console.log('🔍 Encryption check:', {
357
- encryptionEnabled,
358
- hasEncryptionService: !!this.services.encryption,
359
- hasCapabilityService: !!this.services.capability,
360
- configFeatures: this.services.config.features
361
- });
362
-
363
- if (encryptionEnabled && this.services.encryption && this.services.capability) {
364
- console.log('🔒 Encrypting memory package with SEAL (capability-based)...');
365
-
366
- try {
367
- // Step 1: Get or create capability for this app context
368
- const category = memoryPackage.metadata?.category || 'general';
369
- console.log(`🔐 Getting/creating capability for category: ${category}`);
370
-
371
- const cap = await this.services.capability.getOrCreate(
372
- {
373
- appId: category,
374
- userAddress: this.services.config.userAddress
375
- },
376
- this.services.config.signer
377
- );
378
-
379
- memoryCapId = cap.id;
380
- console.log(`✅ Capability ready: ${memoryCapId}`);
381
-
382
- // Step 2: Compute key_id from capability (keccak256(owner || nonce))
383
- keyId = this.services.capability.computeKeyId(cap);
384
- console.log(`🔑 Key ID computed: ${keyId.substring(0, 20)}...`);
385
-
386
- // Step 3: Encrypt the content using key_id as SEAL identity
387
- const contentBytes = new TextEncoder().encode(memoryPackage.content);
388
- const encryptResult = await this.services.encryption.encrypt(
389
- contentBytes,
390
- keyId, // Use key_id from capability as SEAL identity!
391
- 2 // threshold: 2 of 2 key servers
392
- );
393
-
394
- encryptedContent = encryptResult.encryptedObject;
395
- encryptionType = 'seal-capability';
396
-
397
- console.log(`✅ Content encrypted: ${contentBytes.length} bytes → ${encryptedContent?.length || 0} bytes`);
398
-
399
- // Step 4: Also encrypt the embedding for fast index rebuild (Option A v2)
400
- const embeddingToEncrypt = memoryPackage.embedding && memoryPackage.embedding.length > 0
401
- ? memoryPackage.embedding
402
- : (memoryPackage.metadata?.embedding || []);
403
-
404
- if (embeddingToEncrypt.length > 0) {
405
- const embeddingBytes = new TextEncoder().encode(JSON.stringify(embeddingToEncrypt));
406
- const encryptEmbeddingResult = await this.services.encryption.encrypt(
407
- embeddingBytes,
408
- keyId,
409
- 2
410
- );
411
- encryptedEmbedding = encryptEmbeddingResult.encryptedObject;
412
- console.log(`✅ Embedding encrypted: ${embeddingToEncrypt.length}D → ${encryptedEmbedding?.length || 0} bytes`);
413
- }
414
-
415
- console.log(` Using capability: ${memoryCapId}`);
416
- console.log(` Key ID: ${keyId.substring(0, 20)}...`);
417
- } catch (encryptError) {
418
- console.error('❌ Encryption failed:', encryptError);
419
- console.warn('⚠️ Falling back to plaintext storage');
420
- // Fall back to plaintext if encryption fails
421
- }
422
- } else {
423
- console.log('📝 Encryption disabled or services not available - storing plaintext');
424
- }
425
-
426
- // Include capability metadata for decryption
427
- const metadata = {
428
- ...memoryPackage.metadata,
429
- // Add capability info for decryption (CRITICAL for Option A!)
430
- ...(memoryCapId && keyId ? {
431
- memoryCapId,
432
- keyId,
433
- encryptionVersion: 'v2-capability' // Mark as new capability-based encryption
434
- } : {})
435
- };
436
-
437
- // Get embedding from root level (correct API) or metadata (legacy fallback)
438
- const rootEmbedding = memoryPackage.embedding && memoryPackage.embedding.length > 0;
439
- const metadataEmbedding = memoryPackage.metadata?.embedding && memoryPackage.metadata.embedding.length > 0;
440
- const embedding = rootEmbedding
441
- ? memoryPackage.embedding
442
- : (memoryPackage.metadata?.embedding || []);
443
-
444
- if (metadataEmbedding && !rootEmbedding) {
445
- console.warn('⚠️ Embedding in metadata (legacy) - should be at root level');
446
- }
447
- console.log(`📊 Embedding: ${embedding.length}D vector`);
448
-
449
- // Use uploadMemoryPackage method
450
- const result = await this.services.storage.uploadMemoryPackage(
451
- {
452
- content: memoryPackage.content,
453
- embedding,
454
- metadata,
455
- identity: this.services.config.userAddress,
456
- encryptedContent, // Pass encrypted content if available
457
- encryptedEmbedding, // Pass encrypted embedding for Option A v2
458
- encryptionType
459
- },
460
- {
461
- signer: this.services.config.signer,
462
- epochs: 3,
463
- deletable: true
464
- }
465
- );
466
-
467
- return {
468
- blobId: result.blobId,
469
- size: 0, // Size not returned by uploadMemoryPackage
470
- contentType: 'application/json',
471
- memoryCapId, // Return capability ID for reference
472
- keyId // Return key ID for reference
473
- } as UploadResult;
474
- }
475
-
476
- /**
477
- * Retrieve memory package from Walrus with optional decryption
478
- *
479
- * @param blobId - Blob ID of the memory package
480
- * @param decryptionContext - Optional context for decrypting SEAL-encrypted content
481
- * @returns Retrieved memory package with decrypted content
482
- *
483
- * @example
484
- * ```typescript
485
- * // Without decryption (returns encrypted data info)
486
- * const result = await pdw.storage.retrieveMemoryPackage(blobId);
487
- *
488
- * // With decryption
489
- * const result = await pdw.storage.retrieveMemoryPackage(blobId, {
490
- * sessionKey,
491
- * memoryCapId,
492
- * keyId
493
- * });
494
- * ```
495
- */
496
- async retrieveMemoryPackage(
497
- blobId: string,
498
- decryptionContext?: {
499
- sessionKey: any;
500
- memoryCapId: string;
501
- keyId: Uint8Array;
502
- }
503
- ): Promise<{
504
- memoryPackage: MemoryPackage | null;
505
- decryptionStatus: 'success' | 'failed' | 'not_encrypted';
506
- error?: string;
507
- }> {
508
- if (!this.services.storage) {
509
- throw new Error('Storage service not configured.');
510
- }
511
-
512
- const result = await this.services.storage.retrieveMemoryPackage(blobId);
513
-
514
- // If not encrypted, return as-is
515
- if (!result.isEncrypted && result.memoryPackage) {
516
- return {
517
- memoryPackage: {
518
- content: result.memoryPackage.content,
519
- contentType: result.memoryPackage.contentType || 'text/plain',
520
- metadata: result.memoryPackage.metadata,
521
- embedding: result.memoryPackage.embedding,
522
- createdAt: result.memoryPackage.timestamp
523
- },
524
- decryptionStatus: 'not_encrypted'
525
- };
526
- }
527
-
528
- // v2.2 JSON package with encrypted content + encrypted embedding
529
- if (result.memoryPackage?.version === '2.2' && result.memoryPackage?.encryptedContent) {
530
- console.log('🔐 Detected v2.2 JSON package (Full Encryption - Content + Embedding)');
531
-
532
- const embeddingDimension = result.memoryPackage.metadata?.embeddingDimension || 0;
533
- console.log(` embeddingDimension: ${embeddingDimension}D (encrypted on Walrus)`);
534
-
535
- // Return base package (content encrypted, embedding encrypted)
536
- const basePackage: MemoryPackage = {
537
- content: '[ENCRYPTED - requires decryption]',
538
- contentType: 'text/plain',
539
- metadata: result.memoryPackage.metadata,
540
- embedding: [], // Embedding is encrypted, needs decryption
541
- createdAt: result.memoryPackage.timestamp
542
- };
543
-
544
- if (decryptionContext && this.services.encryption) {
545
- try {
546
- console.log('🔐 Decrypting v2.2 content...');
547
-
548
- // Decrypt content
549
- const encryptedContentBase64 = result.memoryPackage.encryptedContent;
550
- const contentBinaryString = atob(encryptedContentBase64);
551
- const encryptedContentBytes = new Uint8Array(contentBinaryString.length);
552
- for (let i = 0; i < contentBinaryString.length; i++) {
553
- encryptedContentBytes[i] = contentBinaryString.charCodeAt(i);
554
- }
555
-
556
- const decryptedContentData = await this.services.encryption.decrypt({
557
- encryptedContent: encryptedContentBytes,
558
- userAddress: this.services.config.userAddress,
559
- sessionKey: decryptionContext.sessionKey,
560
- memoryCapId: decryptionContext.memoryCapId,
561
- keyId: decryptionContext.keyId
562
- });
563
-
564
- const decryptedContent = new TextDecoder().decode(decryptedContentData);
565
- console.log(`✅ v2.2 content decrypted: "${decryptedContent.substring(0, 50)}..."`);
566
-
567
- // Decrypt embedding if available
568
- let decryptedEmbedding: number[] = [];
569
- if (result.memoryPackage.encryptedEmbedding) {
570
- console.log('🔐 Decrypting v2.2 embedding...');
571
- const encryptedEmbeddingBase64 = result.memoryPackage.encryptedEmbedding;
572
- const embeddingBinaryString = atob(encryptedEmbeddingBase64);
573
- const encryptedEmbeddingBytes = new Uint8Array(embeddingBinaryString.length);
574
- for (let i = 0; i < embeddingBinaryString.length; i++) {
575
- encryptedEmbeddingBytes[i] = embeddingBinaryString.charCodeAt(i);
576
- }
577
-
578
- const decryptedEmbeddingData = await this.services.encryption.decrypt({
579
- encryptedContent: encryptedEmbeddingBytes,
580
- userAddress: this.services.config.userAddress,
581
- sessionKey: decryptionContext.sessionKey,
582
- memoryCapId: decryptionContext.memoryCapId,
583
- keyId: decryptionContext.keyId
584
- });
585
-
586
- const embeddingJson = new TextDecoder().decode(decryptedEmbeddingData);
587
- decryptedEmbedding = JSON.parse(embeddingJson);
588
- console.log(`✅ v2.2 embedding decrypted: ${decryptedEmbedding.length}D vector`);
589
- }
590
-
591
- return {
592
- memoryPackage: {
593
- ...basePackage,
594
- content: decryptedContent,
595
- embedding: decryptedEmbedding
596
- },
597
- decryptionStatus: 'success'
598
- };
599
- } catch (decryptError: any) {
600
- console.error('❌ v2.2 decryption failed:', decryptError.message);
601
- return {
602
- memoryPackage: basePackage,
603
- decryptionStatus: 'failed',
604
- error: decryptError.message
605
- };
606
- }
607
- }
608
-
609
- // No decryption context
610
- return {
611
- memoryPackage: basePackage,
612
- decryptionStatus: 'failed',
613
- error: 'No decryption context provided for v2.2 encrypted package'
614
- };
615
- }
616
-
617
- // v2.1 JSON package with encrypted content only (no embedding on Walrus)
618
- if (result.memoryPackage?.version === '2.1' && result.memoryPackage?.encryptedContent) {
619
- console.log('🔐 Detected v2.1 JSON package (Full Encryption - Content only)');
620
-
621
- const embeddingDimension = result.memoryPackage.metadata?.embeddingDimension || 0;
622
- console.log(` embeddingDimension: ${embeddingDimension}D (stored locally, not on Walrus)`);
623
-
624
- const basePackage: MemoryPackage = {
625
- content: '[ENCRYPTED - requires decryption]',
626
- contentType: 'text/plain',
627
- metadata: result.memoryPackage.metadata,
628
- embedding: [], // v2.1 has no embedding on Walrus
629
- createdAt: result.memoryPackage.timestamp
630
- };
631
-
632
- if (decryptionContext && this.services.encryption) {
633
- try {
634
- console.log('🔐 Decrypting v2.1 content...');
635
-
636
- const encryptedBase64 = result.memoryPackage.encryptedContent;
637
- const binaryString = atob(encryptedBase64);
638
- const encryptedBytes = new Uint8Array(binaryString.length);
639
- for (let i = 0; i < binaryString.length; i++) {
640
- encryptedBytes[i] = binaryString.charCodeAt(i);
641
- }
642
-
643
- const decryptedData = await this.services.encryption.decrypt({
644
- encryptedContent: encryptedBytes,
645
- userAddress: this.services.config.userAddress,
646
- sessionKey: decryptionContext.sessionKey,
647
- memoryCapId: decryptionContext.memoryCapId,
648
- keyId: decryptionContext.keyId
649
- });
650
-
651
- const decryptedContent = new TextDecoder().decode(decryptedData);
652
- console.log(`✅ v2.1 decryption successful: "${decryptedContent.substring(0, 50)}..."`);
653
-
654
- return {
655
- memoryPackage: {
656
- ...basePackage,
657
- content: decryptedContent
658
- },
659
- decryptionStatus: 'success'
660
- };
661
- } catch (decryptError: any) {
662
- console.error('❌ v2.1 decryption failed:', decryptError.message);
663
- return {
664
- memoryPackage: basePackage,
665
- decryptionStatus: 'failed',
666
- error: decryptError.message
667
- };
668
- }
669
- }
670
-
671
- return {
672
- memoryPackage: basePackage,
673
- decryptionStatus: 'failed',
674
- error: 'No decryption context provided for v2.1 encrypted package'
675
- };
676
- }
677
-
678
- // v2.0 JSON package with encrypted content + plaintext embedding (legacy)
679
- if (result.memoryPackage?.version === '2.0' && result.memoryPackage?.encryptedContent) {
680
- console.log('📦 Detected v2.0 JSON package with encrypted content (legacy)');
681
-
682
- // Get embedding from root level OR metadata.embedding (fallback for encryption service bug)
683
- const embeddingArray = result.memoryPackage.embedding?.length > 0
684
- ? result.memoryPackage.embedding
685
- : result.memoryPackage.metadata?.embedding;
686
-
687
- const embeddingSource = result.memoryPackage.embedding?.length > 0 ? 'root' : 'metadata';
688
- console.log(` embedding: ${embeddingArray?.length || 0}D (from ${embeddingSource})`);
689
-
690
- // Return embedding even without decryption (for index rebuilding)
691
- const basePackage: MemoryPackage = {
692
- content: '[ENCRYPTED - requires decryption]',
693
- contentType: 'text/plain',
694
- metadata: result.memoryPackage.metadata,
695
- embedding: embeddingArray, // Plaintext embedding available!
696
- createdAt: result.memoryPackage.timestamp
697
- };
698
-
699
- if (decryptionContext && this.services.encryption) {
700
- try {
701
- console.log('🔐 Decrypting v2.0 encryptedContent...');
702
-
703
- // Convert base64 back to Uint8Array
704
- const encryptedBase64 = result.memoryPackage.encryptedContent;
705
- const binaryString = atob(encryptedBase64);
706
- const encryptedBytes = new Uint8Array(binaryString.length);
707
- for (let i = 0; i < binaryString.length; i++) {
708
- encryptedBytes[i] = binaryString.charCodeAt(i);
709
- }
710
-
711
- const decryptedData = await this.services.encryption.decrypt({
712
- encryptedContent: encryptedBytes,
713
- userAddress: this.services.config.userAddress,
714
- sessionKey: decryptionContext.sessionKey,
715
- memoryCapId: decryptionContext.memoryCapId,
716
- keyId: decryptionContext.keyId
717
- });
718
-
719
- const decryptedContent = new TextDecoder().decode(decryptedData);
720
- console.log(`✅ v2.0 decryption successful, content: "${decryptedContent.substring(0, 50)}..."`);
721
-
722
- return {
723
- memoryPackage: {
724
- ...basePackage,
725
- content: decryptedContent
726
- },
727
- decryptionStatus: 'success'
728
- };
729
- } catch (decryptError: any) {
730
- console.error('❌ v2.0 decryption failed:', decryptError.message);
731
- return {
732
- memoryPackage: basePackage, // Return with embedding but encrypted content
733
- decryptionStatus: 'failed',
734
- error: decryptError.message
735
- };
736
- }
737
- }
738
-
739
- // No decryption context - return with embedding only
740
- return {
741
- memoryPackage: basePackage,
742
- decryptionStatus: 'failed',
743
- error: 'No decryption context provided (embedding still available)'
744
- };
745
- }
746
-
747
- // Legacy binary format (v0) - try to decrypt if context provided
748
- if (decryptionContext && this.services.encryption) {
749
- try {
750
- console.log('🔐 Decrypting legacy binary format...');
751
-
752
- const decryptedData = await this.services.encryption.decrypt({
753
- encryptedContent: result.content,
754
- userAddress: this.services.config.userAddress,
755
- sessionKey: decryptionContext.sessionKey,
756
- memoryCapId: decryptionContext.memoryCapId,
757
- keyId: decryptionContext.keyId
758
- });
759
-
760
- const decryptedContent = new TextDecoder().decode(decryptedData);
761
- console.log(`✅ Legacy decryption successful, content length: ${decryptedContent.length}`);
762
-
763
- return {
764
- memoryPackage: {
765
- content: decryptedContent,
766
- contentType: 'text/plain',
767
- metadata: result.metadata,
768
- createdAt: result.metadata.createdTimestamp
769
- },
770
- decryptionStatus: 'success'
771
- };
772
- } catch (decryptError: any) {
773
- console.error('❌ Legacy decryption failed:', decryptError.message);
774
- return {
775
- memoryPackage: null,
776
- decryptionStatus: 'failed',
777
- error: decryptError.message
778
- };
779
- }
780
- }
781
-
782
- // Encrypted but no decryption context provided
783
- return {
784
- memoryPackage: null,
785
- decryptionStatus: 'failed',
786
- error: 'Content is encrypted but no decryption context provided'
787
- };
788
- }
789
-
790
- /**
791
- * Check if blob exists on Walrus
792
- *
793
- * @param blobId - Blob ID to check
794
- * @returns True if blob exists
795
- */
796
- async exists(blobId: string): Promise<boolean> {
797
- try {
798
- await this.download(blobId);
799
- return true;
800
- } catch {
801
- return false;
802
- }
803
- }
804
-
805
- /**
806
- * Get metadata for a blob
807
- *
808
- * @param blobId - Blob ID
809
- * @returns Blob metadata or null
810
- */
811
- async getMetadata(blobId: string): Promise<{
812
- blobId: string;
813
- size?: number;
814
- exists: boolean;
815
- }> {
816
- try {
817
- const data = await this.download(blobId);
818
- return {
819
- blobId,
820
- size: data.length,
821
- exists: true
822
- };
823
- } catch {
824
- return {
825
- blobId,
826
- exists: false
827
- };
828
- }
829
- }
830
-
831
- // ==========================================================================
832
- // High-Level Decrypt API
833
- // ==========================================================================
834
-
835
- /**
836
- * Retrieve and decrypt a memory package with minimal boilerplate.
837
- * SDK handles all version detection, format conversion, and decryption internally.
838
- *
839
- * @param blobId - Blob ID on Walrus
840
- * @param options - Decryption options
841
- * @returns Decrypted content, embedding, and metadata
842
- *
843
- * @example
844
- * ```typescript
845
- * // With sign function (SDK creates session key)
846
- * const result = await pdw.storage.retrieveAndDecrypt(blobId, {
847
- * signFn: async (message) => {
848
- * const sig = await signPersonalMessage({ message: new TextEncoder().encode(message) });
849
- * return { signature: sig.signature };
850
- * }
851
- * });
852
- *
853
- * // With existing session key
854
- * const result = await pdw.storage.retrieveAndDecrypt(blobId, { sessionKey });
855
- *
856
- * console.log(result.content); // "my name is Aaron"
857
- * console.log(result.embedding); // [0.12, -0.34, ...] (3072D)
858
- * console.log(result.version); // "2.2"
859
- * ```
860
- */
861
- async retrieveAndDecrypt(
862
- blobId: string,
863
- options: {
864
- /** Function to sign personal message (SDK will create session key) */
865
- signFn?: (message: string) => Promise<{ signature: string }>;
866
- /** Existing session key (skip signing if provided) */
867
- sessionKey?: any;
868
- /** Override memoryCapId (auto-detected from metadata if not provided) */
869
- memoryCapId?: string;
870
- /** Override keyId hex string (auto-detected from metadata if not provided) */
871
- keyId?: string;
872
- } = {}
873
- ): Promise<{
874
- content: string;
875
- embedding: number[];
876
- version: '2.2' | '2.1' | '2.0' | 'legacy' | 'plaintext';
877
- isEncrypted: boolean;
878
- metadata: Record<string, any>;
879
- blobId: string;
880
- }> {
881
- if (!this.services.storage) {
882
- throw new Error('Storage service not configured.');
883
- }
884
-
885
- console.log(`🔐 retrieveAndDecrypt: Downloading blob ${blobId}...`);
886
-
887
- // Step 1: Download blob from Walrus
888
- const blobData = await this.download(blobId);
889
- console.log(` Downloaded ${blobData.length} bytes`);
890
-
891
- // Step 2: Detect format and parse
892
- let version: '2.2' | '2.1' | '2.0' | 'legacy' | 'plaintext' = 'legacy';
893
- let isEncrypted = false;
894
- let encryptedContentBase64: string | null = null;
895
- let encryptedEmbeddingBase64: string | null = null;
896
- let metadata: Record<string, any> = {};
897
- let plainEmbedding: number[] = [];
898
-
899
- try {
900
- const blobText = new TextDecoder().decode(blobData);
901
- const parsed = JSON.parse(blobText);
902
-
903
- // v2.2: Full encryption (content + embedding both encrypted)
904
- if (parsed.version === '2.2' && parsed.encryptedContent) {
905
- version = '2.2';
906
- isEncrypted = true;
907
- encryptedContentBase64 = parsed.encryptedContent;
908
- encryptedEmbeddingBase64 = parsed.encryptedEmbedding || null;
909
- metadata = parsed.metadata || {};
910
- console.log(` Detected v2.2: encrypted content + encrypted embedding`);
911
- }
912
- // v2.1: Encrypted content only (no embedding on Walrus)
913
- else if (parsed.version === '2.1' && parsed.encryptedContent) {
914
- version = '2.1';
915
- isEncrypted = true;
916
- encryptedContentBase64 = parsed.encryptedContent;
917
- metadata = parsed.metadata || {};
918
- console.log(` Detected v2.1: encrypted content only`);
919
- }
920
- // v2.0: Encrypted content + plaintext embedding
921
- else if (parsed.version === '2.0' && parsed.encryptedContent) {
922
- version = '2.0';
923
- isEncrypted = true;
924
- encryptedContentBase64 = parsed.encryptedContent;
925
- plainEmbedding = parsed.embedding || [];
926
- metadata = parsed.metadata || {};
927
- console.log(` Detected v2.0: encrypted content + ${plainEmbedding.length}D plaintext embedding`);
928
- }
929
- // v1.0: Plaintext JSON package
930
- else if (parsed.version && parsed.content) {
931
- version = 'plaintext';
932
- isEncrypted = false;
933
- metadata = parsed.metadata || {};
934
- plainEmbedding = parsed.embedding || [];
935
- console.log(` Detected plaintext JSON package`);
936
- return {
937
- content: parsed.content,
938
- embedding: plainEmbedding,
939
- version,
940
- isEncrypted,
941
- metadata,
942
- blobId
943
- };
944
- }
945
- } catch {
946
- // Not JSON - check if binary SEAL data
947
- const isBinary = blobData.some(byte => byte < 32 && byte !== 9 && byte !== 10 && byte !== 13);
948
- if (isBinary || blobData.some(byte => byte > 127)) {
949
- version = 'legacy';
950
- isEncrypted = true;
951
- console.log(` Detected legacy binary format`);
952
- } else {
953
- // Plain text content
954
- version = 'plaintext';
955
- isEncrypted = false;
956
- console.log(` Detected plaintext content`);
957
- return {
958
- content: new TextDecoder().decode(blobData),
959
- embedding: [],
960
- version,
961
- isEncrypted,
962
- metadata: {},
963
- blobId
964
- };
965
- }
966
- }
967
-
968
- // Step 3: If not encrypted, return as-is
969
- if (!isEncrypted) {
970
- return {
971
- content: new TextDecoder().decode(blobData),
972
- embedding: plainEmbedding,
973
- version,
974
- isEncrypted,
975
- metadata,
976
- blobId
977
- };
978
- }
979
-
980
- // Step 4: Get decryption parameters (from options or metadata)
981
- const memoryCapId = options.memoryCapId || metadata.memoryCapId;
982
- const keyIdHex = options.keyId || metadata.keyId;
983
-
984
- if (!memoryCapId || !keyIdHex) {
985
- throw new Error(
986
- `Missing decryption parameters. memoryCapId=${!!memoryCapId}, keyId=${!!keyIdHex}. ` +
987
- `Provide via options or ensure metadata contains these values.`
988
- );
989
- }
990
-
991
- // Step 5: Convert keyId hex string to Uint8Array (SDK handles this!)
992
- const keyIdBytes = new Uint8Array(
993
- (keyIdHex.startsWith('0x') ? keyIdHex.slice(2) : keyIdHex)
994
- .match(/.{1,2}/g)!
995
- .map((byte: string) => parseInt(byte, 16))
996
- );
997
-
998
- // Step 6: Get or create session key
999
- let sessionKey = options.sessionKey;
1000
- if (!sessionKey) {
1001
- if (!options.signFn) {
1002
- throw new Error(
1003
- 'Decryption requires either sessionKey or signFn. ' +
1004
- 'Provide signFn to create session key automatically.'
1005
- );
1006
- }
1007
- if (!this.services.encryption) {
1008
- throw new Error('Encryption service not configured.');
1009
- }
1010
-
1011
- console.log(` Creating session key (will prompt for signature)...`);
1012
- sessionKey = await this.services.encryption.createSessionKey(
1013
- this.services.config.userAddress,
1014
- {
1015
- signPersonalMessageFn: async (message: string) => {
1016
- return options.signFn!(message);
1017
- }
1018
- }
1019
- );
1020
- console.log(` Session key created`);
1021
- }
1022
-
1023
- // Step 7: Decrypt content
1024
- console.log(` Decrypting content...`);
1025
-
1026
- // Convert base64 to Uint8Array if needed
1027
- let dataToDecrypt: Uint8Array;
1028
- if (encryptedContentBase64) {
1029
- const binaryString = atob(encryptedContentBase64);
1030
- dataToDecrypt = new Uint8Array(binaryString.length);
1031
- for (let i = 0; i < binaryString.length; i++) {
1032
- dataToDecrypt[i] = binaryString.charCodeAt(i);
1033
- }
1034
- } else {
1035
- dataToDecrypt = blobData;
1036
- }
1037
-
1038
- const decryptedContentData = await this.services.encryption!.decrypt({
1039
- encryptedContent: dataToDecrypt,
1040
- userAddress: this.services.config.userAddress,
1041
- sessionKey,
1042
- memoryCapId,
1043
- keyId: keyIdBytes
1044
- });
1045
-
1046
- const content = new TextDecoder().decode(decryptedContentData);
1047
- console.log(` Content decrypted: "${content.substring(0, 50)}${content.length > 50 ? '...' : ''}"`);
1048
-
1049
- // Step 8: Decrypt embedding if v2.2
1050
- let embedding: number[] = plainEmbedding;
1051
-
1052
- if (version === '2.2' && encryptedEmbeddingBase64) {
1053
- console.log(` Decrypting embedding...`);
1054
- const embeddingBinaryString = atob(encryptedEmbeddingBase64);
1055
- const encryptedEmbeddingBytes = new Uint8Array(embeddingBinaryString.length);
1056
- for (let i = 0; i < embeddingBinaryString.length; i++) {
1057
- encryptedEmbeddingBytes[i] = embeddingBinaryString.charCodeAt(i);
1058
- }
1059
-
1060
- const decryptedEmbeddingData = await this.services.encryption!.decrypt({
1061
- encryptedContent: encryptedEmbeddingBytes,
1062
- userAddress: this.services.config.userAddress,
1063
- sessionKey,
1064
- memoryCapId,
1065
- keyId: keyIdBytes
1066
- });
1067
-
1068
- const embeddingJson = new TextDecoder().decode(decryptedEmbeddingData);
1069
- embedding = JSON.parse(embeddingJson);
1070
- console.log(` Embedding decrypted: ${embedding.length}D vector`);
1071
- }
1072
-
1073
- console.log(`✅ retrieveAndDecrypt complete: ${content.length} chars, ${embedding.length}D embedding`);
1074
-
1075
- return {
1076
- content,
1077
- embedding,
1078
- version,
1079
- isEncrypted,
1080
- metadata,
1081
- blobId
1082
- };
1083
- }
1084
-
1085
- // ==========================================================================
1086
- // Batch Operations (Quilt)
1087
- // ==========================================================================
1088
-
1089
- /**
1090
- * Upload multiple memories as a Quilt (batch upload)
1091
- *
1092
- * Uses Walrus Quilt for ~90% gas savings compared to individual uploads.
1093
- * Requires 2 user signatures:
1094
- * - Transaction 1: Register blob on-chain
1095
- * - Transaction 2: Certify upload on-chain
1096
- *
1097
- * @param memories - Array of memories to upload
1098
- * @param options - Upload options including signer
1099
- * @returns Quilt result with file mappings
1100
- *
1101
- * @example
1102
- * ```typescript
1103
- * const result = await pdw.storage.uploadMemoryBatch(
1104
- * memories,
1105
- * {
1106
- * signer: pdw.getConfig().signer,
1107
- * epochs: 3,
1108
- * userAddress: pdw.getConfig().userAddress
1109
- * }
1110
- * );
1111
- * console.log(`Uploaded ${result.files.length} files`);
1112
- * ```
1113
- */
1114
- async uploadMemoryBatch(
1115
- memories: Array<{
1116
- content: string;
1117
- category: string;
1118
- importance: number;
1119
- topic: string;
1120
- embedding: number[];
1121
- encryptedContent: Uint8Array;
1122
- summary?: string;
1123
- id?: string;
1124
- }>,
1125
- options: {
1126
- signer: any; // UnifiedSigner
1127
- epochs?: number;
1128
- userAddress: string;
1129
- }
1130
- ): Promise<{
1131
- quiltId: string;
1132
- files: Array<{ identifier: string; blobId: string }>;
1133
- uploadTimeMs: number;
1134
- }> {
1135
- if (!this.services.storage) {
1136
- throw new Error('Storage service not configured.');
1137
- }
1138
-
1139
- return this.services.storage.uploadMemoryBatch(memories, options);
1140
- }
1141
- }
1
+ /**
2
+ * Storage Namespace - Consolidated Storage Operations
3
+ *
4
+ * Merges functionality from:
5
+ * - StorageService: Walrus blob storage operations
6
+ * - CacheNamespace: LRU in-memory caching
7
+ *
8
+ * Provides unified interface for data persistence (Walrus) and caching.
9
+ *
10
+ * @module client/namespaces/consolidated
11
+ */
12
+
13
+ import type { ServiceContainer } from '../../SimplePDWClient';
14
+
15
+ // ============================================================================
16
+ // Types
17
+ // ============================================================================
18
+
19
+ /**
20
+ * Cache statistics
21
+ */
22
+ export interface CacheStats {
23
+ size: number;
24
+ totalAccess: number;
25
+ hitRate: number;
26
+ oldestItem?: Date;
27
+ newestItem?: Date;
28
+ }
29
+
30
+ /**
31
+ * Upload result from Walrus
32
+ */
33
+ export interface UploadResult {
34
+ blobId: string;
35
+ size: number;
36
+ contentType?: string;
37
+ }
38
+
39
+ /**
40
+ * Memory package structure
41
+ */
42
+ export interface MemoryPackage {
43
+ content: string;
44
+ contentType: string;
45
+ metadata?: Record<string, any>;
46
+ embedding?: number[];
47
+ createdAt?: number;
48
+ }
49
+
50
+ /**
51
+ * Upload options
52
+ */
53
+ export interface UploadOptions {
54
+ contentType?: string;
55
+ encrypt?: boolean;
56
+ epochs?: number;
57
+ }
58
+
59
+ // ============================================================================
60
+ // Sub-Namespaces
61
+ // ============================================================================
62
+
63
+ /**
64
+ * Cache sub-namespace for LRU caching operations
65
+ */
66
+ class CacheSubNamespace {
67
+ constructor(private services: ServiceContainer) {}
68
+
69
+ /**
70
+ * Get cached value
71
+ *
72
+ * @param key - Cache key
73
+ * @returns Cached value or null if not found/expired
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const cached = pdw.storage.cache.get<User>('user:123');
78
+ * if (cached) {
79
+ * console.log('Cache hit:', cached);
80
+ * }
81
+ * ```
82
+ */
83
+ get<T = any>(key: string): T | null {
84
+ if (!this.services.batchService) {
85
+ throw new Error('Batch service (cache) not configured.');
86
+ }
87
+ return this.services.batchService.getCache<T>(key);
88
+ }
89
+
90
+ /**
91
+ * Set cache value
92
+ *
93
+ * @param key - Cache key
94
+ * @param value - Value to cache
95
+ * @param ttl - Time-to-live in milliseconds (optional)
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * pdw.storage.cache.set('user:123', userData, 60000); // 1 minute TTL
100
+ * ```
101
+ */
102
+ set<T = any>(key: string, value: T, ttl?: number): void {
103
+ if (!this.services.batchService) {
104
+ throw new Error('Batch service (cache) not configured.');
105
+ }
106
+ this.services.batchService.setCache(key, value, ttl);
107
+ }
108
+
109
+ /**
110
+ * Check if key exists in cache
111
+ *
112
+ * @param key - Cache key
113
+ * @returns True if key exists and not expired
114
+ */
115
+ has(key: string): boolean {
116
+ if (!this.services.batchService) {
117
+ throw new Error('Batch service (cache) not configured.');
118
+ }
119
+ return this.services.batchService.hasCache(key);
120
+ }
121
+
122
+ /**
123
+ * Delete cache entry
124
+ *
125
+ * @param key - Cache key
126
+ * @returns True if deleted, false if not found
127
+ */
128
+ delete(key: string): boolean {
129
+ if (!this.services.batchService) {
130
+ throw new Error('Batch service (cache) not configured.');
131
+ }
132
+ return this.services.batchService.deleteCache(key);
133
+ }
134
+
135
+ /**
136
+ * Clear all cache entries
137
+ */
138
+ clear(): void {
139
+ if (!this.services.batchService) {
140
+ throw new Error('Batch service (cache) not configured.');
141
+ }
142
+ this.services.batchService.clearCache();
143
+ }
144
+
145
+ /**
146
+ * Get cache statistics
147
+ *
148
+ * @returns Cache statistics
149
+ */
150
+ stats(): CacheStats {
151
+ if (!this.services.batchService) {
152
+ throw new Error('Batch service (cache) not configured.');
153
+ }
154
+ return this.services.batchService.getCacheStats();
155
+ }
156
+ }
157
+
158
+ // ============================================================================
159
+ // Storage Namespace
160
+ // ============================================================================
161
+
162
+ /**
163
+ * Storage Namespace - Unified Storage Operations
164
+ *
165
+ * Consolidates Walrus blob storage and in-memory caching.
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * // Upload to Walrus
170
+ * const result = await pdw.storage.upload(data);
171
+ * console.log('Blob ID:', result.blobId);
172
+ *
173
+ * // Download from Walrus
174
+ * const data = await pdw.storage.download(blobId);
175
+ *
176
+ * // Use cache
177
+ * pdw.storage.cache.set('key', value, 60000);
178
+ * const cached = pdw.storage.cache.get('key');
179
+ * ```
180
+ */
181
+ export class StorageNamespace {
182
+ private _cache: CacheSubNamespace;
183
+
184
+ constructor(private services: ServiceContainer) {
185
+ this._cache = new CacheSubNamespace(services);
186
+ }
187
+
188
+ /**
189
+ * Cache operations
190
+ */
191
+ get cache(): CacheSubNamespace {
192
+ return this._cache;
193
+ }
194
+
195
+ // ==========================================================================
196
+ // Walrus Storage Operations
197
+ // ==========================================================================
198
+
199
+ /**
200
+ * Upload data to Walrus
201
+ *
202
+ * @param data - Data to upload (string, Uint8Array, or object)
203
+ * @param options - Upload options
204
+ * @returns Upload result with blob ID
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * // Upload raw bytes
209
+ * const result = await pdw.storage.upload(new Uint8Array([1, 2, 3]));
210
+ *
211
+ * // Upload JSON
212
+ * const result = await pdw.storage.upload({ name: 'test' });
213
+ *
214
+ * // Upload with options
215
+ * const result = await pdw.storage.upload(data, { encrypt: true });
216
+ * ```
217
+ */
218
+ async upload(data: string | Uint8Array | object, options: UploadOptions = {}): Promise<UploadResult> {
219
+ if (!this.services.storage) {
220
+ throw new Error('Storage service not configured.');
221
+ }
222
+
223
+ // Convert data to Uint8Array
224
+ let bytes: Uint8Array;
225
+ let contentType = options.contentType || 'application/octet-stream';
226
+
227
+ if (typeof data === 'string') {
228
+ bytes = new TextEncoder().encode(data);
229
+ contentType = options.contentType || 'text/plain';
230
+ } else if (data instanceof Uint8Array) {
231
+ bytes = data;
232
+ } else {
233
+ bytes = new TextEncoder().encode(JSON.stringify(data));
234
+ contentType = options.contentType || 'application/json';
235
+ }
236
+
237
+ // Encrypt if requested
238
+ if (options.encrypt && this.services.encryption) {
239
+ const encryptResult = await this.services.encryption.encrypt(
240
+ bytes,
241
+ this.services.config.userAddress,
242
+ 2
243
+ );
244
+ bytes = encryptResult.encryptedObject;
245
+ }
246
+
247
+ // Upload to Walrus using uploadBlob method
248
+ const result = await this.services.storage.uploadBlob(bytes, {
249
+ signer: this.services.config.signer,
250
+ epochs: options.epochs,
251
+ deletable: true
252
+ });
253
+
254
+ return {
255
+ blobId: result.blobId,
256
+ size: bytes.length,
257
+ contentType
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Download data from Walrus
263
+ *
264
+ * @param blobId - Blob ID to download
265
+ * @returns Raw data as Uint8Array
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * const data = await pdw.storage.download('blobId123');
270
+ * const text = new TextDecoder().decode(data);
271
+ * ```
272
+ */
273
+ async download(blobId: string): Promise<Uint8Array> {
274
+ if (!this.services.storage) {
275
+ throw new Error('Storage service not configured.');
276
+ }
277
+
278
+ // Use retrieveFromWalrusOnly which returns { content, metadata }
279
+ const result = await this.services.storage.retrieveFromWalrusOnly(blobId);
280
+ return result.content;
281
+ }
282
+
283
+ /**
284
+ * Download and parse JSON from Walrus
285
+ *
286
+ * @param blobId - Blob ID to download
287
+ * @returns Parsed JSON object
288
+ *
289
+ * @example
290
+ * ```typescript
291
+ * const data = await pdw.storage.downloadJson<MyType>('blobId123');
292
+ * ```
293
+ */
294
+ async downloadJson<T = any>(blobId: string): Promise<T> {
295
+ const bytes = await this.download(blobId);
296
+ const text = new TextDecoder().decode(bytes);
297
+ return JSON.parse(text);
298
+ }
299
+
300
+ /**
301
+ * Download and decrypt data from Walrus
302
+ *
303
+ * @param blobId - Blob ID to download
304
+ * @param options - Decryption options
305
+ * @returns Decrypted data
306
+ *
307
+ * @example
308
+ * ```typescript
309
+ * const data = await pdw.storage.downloadDecrypted('blobId123');
310
+ * ```
311
+ */
312
+ async downloadDecrypted(blobId: string, options?: {
313
+ memoryCapId?: string;
314
+ keyId?: Uint8Array;
315
+ }): Promise<Uint8Array> {
316
+ if (!this.services.storage) {
317
+ throw new Error('Storage service not configured.');
318
+ }
319
+ if (!this.services.encryption) {
320
+ throw new Error('Encryption service not configured.');
321
+ }
322
+
323
+ const encryptedData = await this.download(blobId);
324
+
325
+ return await this.services.encryption.decrypt({
326
+ encryptedContent: encryptedData,
327
+ userAddress: this.services.config.userAddress,
328
+ requestingWallet: this.services.config.userAddress,
329
+ memoryCapId: options?.memoryCapId,
330
+ keyId: options?.keyId
331
+ });
332
+ }
333
+
334
+ /**
335
+ * Store memory package to Walrus
336
+ *
337
+ * Higher-level method that stores a complete memory package.
338
+ * Automatically encrypts content if encryption is enabled in config.
339
+ *
340
+ * @param memoryPackage - Memory package to store
341
+ * @returns Upload result
342
+ */
343
+ async storeMemoryPackage(memoryPackage: MemoryPackage): Promise<UploadResult> {
344
+ if (!this.services.storage) {
345
+ throw new Error('Storage service not configured.');
346
+ }
347
+
348
+ // Check if encryption is enabled
349
+ const encryptionEnabled = this.services.config.features?.enableEncryption ?? true;
350
+ let encryptedContent: Uint8Array | undefined;
351
+ let encryptedEmbedding: Uint8Array | undefined; // Option A v2: encrypted embedding
352
+ let encryptionType: string | undefined;
353
+ let memoryCapId: string | undefined;
354
+ let keyId: string | undefined;
355
+
356
+ console.log('🔍 Encryption check:', {
357
+ encryptionEnabled,
358
+ hasEncryptionService: !!this.services.encryption,
359
+ hasCapabilityService: !!this.services.capability,
360
+ configFeatures: this.services.config.features
361
+ });
362
+
363
+ if (encryptionEnabled && this.services.encryption && this.services.capability) {
364
+ console.log('🔒 Encrypting memory package with SEAL (capability-based)...');
365
+
366
+ try {
367
+ // Step 1: Get or create capability for this app context
368
+ const category = memoryPackage.metadata?.category || 'general';
369
+ console.log(`🔐 Getting/creating capability for category: ${category}`);
370
+
371
+ const cap = await this.services.capability.getOrCreate(
372
+ {
373
+ appId: category,
374
+ userAddress: this.services.config.userAddress
375
+ },
376
+ this.services.config.signer
377
+ );
378
+
379
+ memoryCapId = cap.id;
380
+ console.log(`✅ Capability ready: ${memoryCapId}`);
381
+
382
+ // Step 2: Compute key_id from capability (keccak256(owner || nonce))
383
+ keyId = this.services.capability.computeKeyId(cap);
384
+ console.log(`🔑 Key ID computed: ${keyId.substring(0, 20)}...`);
385
+
386
+ // Step 3: Encrypt the content using key_id as SEAL identity
387
+ const contentBytes = new TextEncoder().encode(memoryPackage.content);
388
+ const encryptResult = await this.services.encryption.encrypt(
389
+ contentBytes,
390
+ keyId, // Use key_id from capability as SEAL identity!
391
+ 2 // threshold: 2 of 2 key servers
392
+ );
393
+
394
+ encryptedContent = encryptResult.encryptedObject;
395
+ encryptionType = 'seal-capability';
396
+
397
+ console.log(`✅ Content encrypted: ${contentBytes.length} bytes → ${encryptedContent?.length || 0} bytes`);
398
+
399
+ // Step 4: Also encrypt the embedding for fast index rebuild (Option A v2)
400
+ const embeddingToEncrypt = memoryPackage.embedding && memoryPackage.embedding.length > 0
401
+ ? memoryPackage.embedding
402
+ : (memoryPackage.metadata?.embedding || []);
403
+
404
+ if (embeddingToEncrypt.length > 0) {
405
+ const embeddingBytes = new TextEncoder().encode(JSON.stringify(embeddingToEncrypt));
406
+ const encryptEmbeddingResult = await this.services.encryption.encrypt(
407
+ embeddingBytes,
408
+ keyId,
409
+ 2
410
+ );
411
+ encryptedEmbedding = encryptEmbeddingResult.encryptedObject;
412
+ console.log(`✅ Embedding encrypted: ${embeddingToEncrypt.length}D → ${encryptedEmbedding?.length || 0} bytes`);
413
+ }
414
+
415
+ console.log(` Using capability: ${memoryCapId}`);
416
+ console.log(` Key ID: ${keyId.substring(0, 20)}...`);
417
+ } catch (encryptError) {
418
+ console.error('❌ Encryption failed:', encryptError);
419
+ console.warn('⚠️ Falling back to plaintext storage');
420
+ // Fall back to plaintext if encryption fails
421
+ }
422
+ } else {
423
+ console.log('📝 Encryption disabled or services not available - storing plaintext');
424
+ }
425
+
426
+ // Include capability metadata for decryption
427
+ const metadata = {
428
+ ...memoryPackage.metadata,
429
+ // Add capability info for decryption (CRITICAL for Option A!)
430
+ ...(memoryCapId && keyId ? {
431
+ memoryCapId,
432
+ keyId,
433
+ encryptionVersion: 'v2-capability' // Mark as new capability-based encryption
434
+ } : {})
435
+ };
436
+
437
+ // Get embedding from root level (correct API) or metadata (legacy fallback)
438
+ const rootEmbedding = memoryPackage.embedding && memoryPackage.embedding.length > 0;
439
+ const metadataEmbedding = memoryPackage.metadata?.embedding && memoryPackage.metadata.embedding.length > 0;
440
+ const embedding = rootEmbedding
441
+ ? memoryPackage.embedding
442
+ : (memoryPackage.metadata?.embedding || []);
443
+
444
+ if (metadataEmbedding && !rootEmbedding) {
445
+ console.warn('⚠️ Embedding in metadata (legacy) - should be at root level');
446
+ }
447
+ console.log(`📊 Embedding: ${embedding.length}D vector`);
448
+
449
+ // Use uploadMemoryPackage method
450
+ const result = await this.services.storage.uploadMemoryPackage(
451
+ {
452
+ content: memoryPackage.content,
453
+ embedding,
454
+ metadata,
455
+ identity: this.services.config.userAddress,
456
+ encryptedContent, // Pass encrypted content if available
457
+ encryptedEmbedding, // Pass encrypted embedding for Option A v2
458
+ encryptionType
459
+ },
460
+ {
461
+ signer: this.services.config.signer,
462
+ epochs: 3,
463
+ deletable: true
464
+ }
465
+ );
466
+
467
+ return {
468
+ blobId: result.blobId,
469
+ size: 0, // Size not returned by uploadMemoryPackage
470
+ contentType: 'application/json',
471
+ memoryCapId, // Return capability ID for reference
472
+ keyId // Return key ID for reference
473
+ } as UploadResult;
474
+ }
475
+
476
+ /**
477
+ * Retrieve memory package from Walrus with optional decryption
478
+ *
479
+ * @param blobId - Blob ID of the memory package
480
+ * @param decryptionContext - Optional context for decrypting SEAL-encrypted content
481
+ * @returns Retrieved memory package with decrypted content
482
+ *
483
+ * @example
484
+ * ```typescript
485
+ * // Without decryption (returns encrypted data info)
486
+ * const result = await pdw.storage.retrieveMemoryPackage(blobId);
487
+ *
488
+ * // With decryption
489
+ * const result = await pdw.storage.retrieveMemoryPackage(blobId, {
490
+ * sessionKey,
491
+ * memoryCapId,
492
+ * keyId
493
+ * });
494
+ * ```
495
+ */
496
+ async retrieveMemoryPackage(
497
+ blobId: string,
498
+ decryptionContext?: {
499
+ sessionKey: any;
500
+ memoryCapId: string;
501
+ keyId: Uint8Array;
502
+ }
503
+ ): Promise<{
504
+ memoryPackage: MemoryPackage | null;
505
+ decryptionStatus: 'success' | 'failed' | 'not_encrypted';
506
+ error?: string;
507
+ }> {
508
+ if (!this.services.storage) {
509
+ throw new Error('Storage service not configured.');
510
+ }
511
+
512
+ const result = await this.services.storage.retrieveMemoryPackage(blobId);
513
+
514
+ // If not encrypted, return as-is
515
+ if (!result.isEncrypted && result.memoryPackage) {
516
+ return {
517
+ memoryPackage: {
518
+ content: result.memoryPackage.content,
519
+ contentType: result.memoryPackage.contentType || 'text/plain',
520
+ metadata: result.memoryPackage.metadata,
521
+ embedding: result.memoryPackage.embedding,
522
+ createdAt: result.memoryPackage.timestamp
523
+ },
524
+ decryptionStatus: 'not_encrypted'
525
+ };
526
+ }
527
+
528
+ // v2.2 JSON package with encrypted content + encrypted embedding
529
+ if (result.memoryPackage?.version === '2.2' && result.memoryPackage?.encryptedContent) {
530
+ console.log('🔐 Detected v2.2 JSON package (Full Encryption - Content + Embedding)');
531
+
532
+ const embeddingDimension = result.memoryPackage.metadata?.embeddingDimension || 0;
533
+ console.log(` embeddingDimension: ${embeddingDimension}D (encrypted on Walrus)`);
534
+
535
+ // Return base package (content encrypted, embedding encrypted)
536
+ const basePackage: MemoryPackage = {
537
+ content: '[ENCRYPTED - requires decryption]',
538
+ contentType: 'text/plain',
539
+ metadata: result.memoryPackage.metadata,
540
+ embedding: [], // Embedding is encrypted, needs decryption
541
+ createdAt: result.memoryPackage.timestamp
542
+ };
543
+
544
+ if (decryptionContext && this.services.encryption) {
545
+ try {
546
+ console.log('🔐 Decrypting v2.2 content...');
547
+
548
+ // Decrypt content
549
+ const encryptedContentBase64 = result.memoryPackage.encryptedContent;
550
+ const contentBinaryString = atob(encryptedContentBase64);
551
+ const encryptedContentBytes = new Uint8Array(contentBinaryString.length);
552
+ for (let i = 0; i < contentBinaryString.length; i++) {
553
+ encryptedContentBytes[i] = contentBinaryString.charCodeAt(i);
554
+ }
555
+
556
+ const decryptedContentData = await this.services.encryption.decrypt({
557
+ encryptedContent: encryptedContentBytes,
558
+ userAddress: this.services.config.userAddress,
559
+ sessionKey: decryptionContext.sessionKey,
560
+ memoryCapId: decryptionContext.memoryCapId,
561
+ keyId: decryptionContext.keyId
562
+ });
563
+
564
+ const decryptedContent = new TextDecoder().decode(decryptedContentData);
565
+ console.log(`✅ v2.2 content decrypted: "${decryptedContent.substring(0, 50)}..."`);
566
+
567
+ // Decrypt embedding if available
568
+ let decryptedEmbedding: number[] = [];
569
+ if (result.memoryPackage.encryptedEmbedding) {
570
+ console.log('🔐 Decrypting v2.2 embedding...');
571
+ const encryptedEmbeddingBase64 = result.memoryPackage.encryptedEmbedding;
572
+ const embeddingBinaryString = atob(encryptedEmbeddingBase64);
573
+ const encryptedEmbeddingBytes = new Uint8Array(embeddingBinaryString.length);
574
+ for (let i = 0; i < embeddingBinaryString.length; i++) {
575
+ encryptedEmbeddingBytes[i] = embeddingBinaryString.charCodeAt(i);
576
+ }
577
+
578
+ const decryptedEmbeddingData = await this.services.encryption.decrypt({
579
+ encryptedContent: encryptedEmbeddingBytes,
580
+ userAddress: this.services.config.userAddress,
581
+ sessionKey: decryptionContext.sessionKey,
582
+ memoryCapId: decryptionContext.memoryCapId,
583
+ keyId: decryptionContext.keyId
584
+ });
585
+
586
+ const embeddingJson = new TextDecoder().decode(decryptedEmbeddingData);
587
+ decryptedEmbedding = JSON.parse(embeddingJson);
588
+ console.log(`✅ v2.2 embedding decrypted: ${decryptedEmbedding.length}D vector`);
589
+ }
590
+
591
+ return {
592
+ memoryPackage: {
593
+ ...basePackage,
594
+ content: decryptedContent,
595
+ embedding: decryptedEmbedding
596
+ },
597
+ decryptionStatus: 'success'
598
+ };
599
+ } catch (decryptError: any) {
600
+ console.error('❌ v2.2 decryption failed:', decryptError.message);
601
+ return {
602
+ memoryPackage: basePackage,
603
+ decryptionStatus: 'failed',
604
+ error: decryptError.message
605
+ };
606
+ }
607
+ }
608
+
609
+ // No decryption context
610
+ return {
611
+ memoryPackage: basePackage,
612
+ decryptionStatus: 'failed',
613
+ error: 'No decryption context provided for v2.2 encrypted package'
614
+ };
615
+ }
616
+
617
+ // v2.1 JSON package with encrypted content only (no embedding on Walrus)
618
+ if (result.memoryPackage?.version === '2.1' && result.memoryPackage?.encryptedContent) {
619
+ console.log('🔐 Detected v2.1 JSON package (Full Encryption - Content only)');
620
+
621
+ const embeddingDimension = result.memoryPackage.metadata?.embeddingDimension || 0;
622
+ console.log(` embeddingDimension: ${embeddingDimension}D (stored locally, not on Walrus)`);
623
+
624
+ const basePackage: MemoryPackage = {
625
+ content: '[ENCRYPTED - requires decryption]',
626
+ contentType: 'text/plain',
627
+ metadata: result.memoryPackage.metadata,
628
+ embedding: [], // v2.1 has no embedding on Walrus
629
+ createdAt: result.memoryPackage.timestamp
630
+ };
631
+
632
+ if (decryptionContext && this.services.encryption) {
633
+ try {
634
+ console.log('🔐 Decrypting v2.1 content...');
635
+
636
+ const encryptedBase64 = result.memoryPackage.encryptedContent;
637
+ const binaryString = atob(encryptedBase64);
638
+ const encryptedBytes = new Uint8Array(binaryString.length);
639
+ for (let i = 0; i < binaryString.length; i++) {
640
+ encryptedBytes[i] = binaryString.charCodeAt(i);
641
+ }
642
+
643
+ const decryptedData = await this.services.encryption.decrypt({
644
+ encryptedContent: encryptedBytes,
645
+ userAddress: this.services.config.userAddress,
646
+ sessionKey: decryptionContext.sessionKey,
647
+ memoryCapId: decryptionContext.memoryCapId,
648
+ keyId: decryptionContext.keyId
649
+ });
650
+
651
+ const decryptedContent = new TextDecoder().decode(decryptedData);
652
+ console.log(`✅ v2.1 decryption successful: "${decryptedContent.substring(0, 50)}..."`);
653
+
654
+ return {
655
+ memoryPackage: {
656
+ ...basePackage,
657
+ content: decryptedContent
658
+ },
659
+ decryptionStatus: 'success'
660
+ };
661
+ } catch (decryptError: any) {
662
+ console.error('❌ v2.1 decryption failed:', decryptError.message);
663
+ return {
664
+ memoryPackage: basePackage,
665
+ decryptionStatus: 'failed',
666
+ error: decryptError.message
667
+ };
668
+ }
669
+ }
670
+
671
+ return {
672
+ memoryPackage: basePackage,
673
+ decryptionStatus: 'failed',
674
+ error: 'No decryption context provided for v2.1 encrypted package'
675
+ };
676
+ }
677
+
678
+ // v2.0 JSON package with encrypted content + plaintext embedding (legacy)
679
+ if (result.memoryPackage?.version === '2.0' && result.memoryPackage?.encryptedContent) {
680
+ console.log('📦 Detected v2.0 JSON package with encrypted content (legacy)');
681
+
682
+ // Get embedding from root level OR metadata.embedding (fallback for encryption service bug)
683
+ const embeddingArray = result.memoryPackage.embedding?.length > 0
684
+ ? result.memoryPackage.embedding
685
+ : result.memoryPackage.metadata?.embedding;
686
+
687
+ const embeddingSource = result.memoryPackage.embedding?.length > 0 ? 'root' : 'metadata';
688
+ console.log(` embedding: ${embeddingArray?.length || 0}D (from ${embeddingSource})`);
689
+
690
+ // Return embedding even without decryption (for index rebuilding)
691
+ const basePackage: MemoryPackage = {
692
+ content: '[ENCRYPTED - requires decryption]',
693
+ contentType: 'text/plain',
694
+ metadata: result.memoryPackage.metadata,
695
+ embedding: embeddingArray, // Plaintext embedding available!
696
+ createdAt: result.memoryPackage.timestamp
697
+ };
698
+
699
+ if (decryptionContext && this.services.encryption) {
700
+ try {
701
+ console.log('🔐 Decrypting v2.0 encryptedContent...');
702
+
703
+ // Convert base64 back to Uint8Array
704
+ const encryptedBase64 = result.memoryPackage.encryptedContent;
705
+ const binaryString = atob(encryptedBase64);
706
+ const encryptedBytes = new Uint8Array(binaryString.length);
707
+ for (let i = 0; i < binaryString.length; i++) {
708
+ encryptedBytes[i] = binaryString.charCodeAt(i);
709
+ }
710
+
711
+ const decryptedData = await this.services.encryption.decrypt({
712
+ encryptedContent: encryptedBytes,
713
+ userAddress: this.services.config.userAddress,
714
+ sessionKey: decryptionContext.sessionKey,
715
+ memoryCapId: decryptionContext.memoryCapId,
716
+ keyId: decryptionContext.keyId
717
+ });
718
+
719
+ const decryptedContent = new TextDecoder().decode(decryptedData);
720
+ console.log(`✅ v2.0 decryption successful, content: "${decryptedContent.substring(0, 50)}..."`);
721
+
722
+ return {
723
+ memoryPackage: {
724
+ ...basePackage,
725
+ content: decryptedContent
726
+ },
727
+ decryptionStatus: 'success'
728
+ };
729
+ } catch (decryptError: any) {
730
+ console.error('❌ v2.0 decryption failed:', decryptError.message);
731
+ return {
732
+ memoryPackage: basePackage, // Return with embedding but encrypted content
733
+ decryptionStatus: 'failed',
734
+ error: decryptError.message
735
+ };
736
+ }
737
+ }
738
+
739
+ // No decryption context - return with embedding only
740
+ return {
741
+ memoryPackage: basePackage,
742
+ decryptionStatus: 'failed',
743
+ error: 'No decryption context provided (embedding still available)'
744
+ };
745
+ }
746
+
747
+ // Legacy binary format (v0) - try to decrypt if context provided
748
+ if (decryptionContext && this.services.encryption) {
749
+ try {
750
+ console.log('🔐 Decrypting legacy binary format...');
751
+
752
+ const decryptedData = await this.services.encryption.decrypt({
753
+ encryptedContent: result.content,
754
+ userAddress: this.services.config.userAddress,
755
+ sessionKey: decryptionContext.sessionKey,
756
+ memoryCapId: decryptionContext.memoryCapId,
757
+ keyId: decryptionContext.keyId
758
+ });
759
+
760
+ const decryptedContent = new TextDecoder().decode(decryptedData);
761
+ console.log(`✅ Legacy decryption successful, content length: ${decryptedContent.length}`);
762
+
763
+ return {
764
+ memoryPackage: {
765
+ content: decryptedContent,
766
+ contentType: 'text/plain',
767
+ metadata: result.metadata,
768
+ createdAt: result.metadata.createdTimestamp
769
+ },
770
+ decryptionStatus: 'success'
771
+ };
772
+ } catch (decryptError: any) {
773
+ console.error('❌ Legacy decryption failed:', decryptError.message);
774
+ return {
775
+ memoryPackage: null,
776
+ decryptionStatus: 'failed',
777
+ error: decryptError.message
778
+ };
779
+ }
780
+ }
781
+
782
+ // Encrypted but no decryption context provided
783
+ return {
784
+ memoryPackage: null,
785
+ decryptionStatus: 'failed',
786
+ error: 'Content is encrypted but no decryption context provided'
787
+ };
788
+ }
789
+
790
+ /**
791
+ * Check if blob exists on Walrus
792
+ *
793
+ * @param blobId - Blob ID to check
794
+ * @returns True if blob exists
795
+ */
796
+ async exists(blobId: string): Promise<boolean> {
797
+ try {
798
+ await this.download(blobId);
799
+ return true;
800
+ } catch {
801
+ return false;
802
+ }
803
+ }
804
+
805
+ /**
806
+ * Get metadata for a blob
807
+ *
808
+ * @param blobId - Blob ID
809
+ * @returns Blob metadata or null
810
+ */
811
+ async getMetadata(blobId: string): Promise<{
812
+ blobId: string;
813
+ size?: number;
814
+ exists: boolean;
815
+ }> {
816
+ try {
817
+ const data = await this.download(blobId);
818
+ return {
819
+ blobId,
820
+ size: data.length,
821
+ exists: true
822
+ };
823
+ } catch {
824
+ return {
825
+ blobId,
826
+ exists: false
827
+ };
828
+ }
829
+ }
830
+
831
+ // ==========================================================================
832
+ // High-Level Decrypt API
833
+ // ==========================================================================
834
+
835
+ /**
836
+ * Retrieve and decrypt a memory package with minimal boilerplate.
837
+ * SDK handles all version detection, format conversion, and decryption internally.
838
+ *
839
+ * @param blobId - Blob ID on Walrus
840
+ * @param options - Decryption options
841
+ * @returns Decrypted content, embedding, and metadata
842
+ *
843
+ * @example
844
+ * ```typescript
845
+ * // With sign function (SDK creates session key)
846
+ * const result = await pdw.storage.retrieveAndDecrypt(blobId, {
847
+ * signFn: async (message) => {
848
+ * const sig = await signPersonalMessage({ message: new TextEncoder().encode(message) });
849
+ * return { signature: sig.signature };
850
+ * }
851
+ * });
852
+ *
853
+ * // With existing session key
854
+ * const result = await pdw.storage.retrieveAndDecrypt(blobId, { sessionKey });
855
+ *
856
+ * console.log(result.content); // "my name is Aaron"
857
+ * console.log(result.embedding); // [0.12, -0.34, ...] (3072D)
858
+ * console.log(result.version); // "2.2"
859
+ * ```
860
+ */
861
+ async retrieveAndDecrypt(
862
+ blobId: string,
863
+ options: {
864
+ /** Function to sign personal message (SDK will create session key) */
865
+ signFn?: (message: string) => Promise<{ signature: string }>;
866
+ /** Existing session key (skip signing if provided) */
867
+ sessionKey?: any;
868
+ /** Override memoryCapId (auto-detected from metadata if not provided) */
869
+ memoryCapId?: string;
870
+ /** Override keyId hex string (auto-detected from metadata if not provided) */
871
+ keyId?: string;
872
+ } = {}
873
+ ): Promise<{
874
+ content: string;
875
+ embedding: number[];
876
+ version: '2.2' | '2.1' | '2.0' | 'legacy' | 'plaintext';
877
+ isEncrypted: boolean;
878
+ metadata: Record<string, any>;
879
+ blobId: string;
880
+ }> {
881
+ if (!this.services.storage) {
882
+ throw new Error('Storage service not configured.');
883
+ }
884
+
885
+ console.log(`🔐 retrieveAndDecrypt: Downloading blob ${blobId}...`);
886
+
887
+ // Step 1: Download blob from Walrus
888
+ const blobData = await this.download(blobId);
889
+ console.log(` Downloaded ${blobData.length} bytes`);
890
+
891
+ // Step 2: Detect format and parse
892
+ let version: '2.2' | '2.1' | '2.0' | 'legacy' | 'plaintext' = 'legacy';
893
+ let isEncrypted = false;
894
+ let encryptedContentBase64: string | null = null;
895
+ let encryptedEmbeddingBase64: string | null = null;
896
+ let metadata: Record<string, any> = {};
897
+ let plainEmbedding: number[] = [];
898
+
899
+ try {
900
+ const blobText = new TextDecoder().decode(blobData);
901
+ const parsed = JSON.parse(blobText);
902
+
903
+ // v2.2: Full encryption (content + embedding both encrypted)
904
+ if (parsed.version === '2.2' && parsed.encryptedContent) {
905
+ version = '2.2';
906
+ isEncrypted = true;
907
+ encryptedContentBase64 = parsed.encryptedContent;
908
+ encryptedEmbeddingBase64 = parsed.encryptedEmbedding || null;
909
+ metadata = parsed.metadata || {};
910
+ console.log(` Detected v2.2: encrypted content + encrypted embedding`);
911
+ }
912
+ // v2.1: Encrypted content only (no embedding on Walrus)
913
+ else if (parsed.version === '2.1' && parsed.encryptedContent) {
914
+ version = '2.1';
915
+ isEncrypted = true;
916
+ encryptedContentBase64 = parsed.encryptedContent;
917
+ metadata = parsed.metadata || {};
918
+ console.log(` Detected v2.1: encrypted content only`);
919
+ }
920
+ // v2.0: Encrypted content + plaintext embedding
921
+ else if (parsed.version === '2.0' && parsed.encryptedContent) {
922
+ version = '2.0';
923
+ isEncrypted = true;
924
+ encryptedContentBase64 = parsed.encryptedContent;
925
+ plainEmbedding = parsed.embedding || [];
926
+ metadata = parsed.metadata || {};
927
+ console.log(` Detected v2.0: encrypted content + ${plainEmbedding.length}D plaintext embedding`);
928
+ }
929
+ // v1.0: Plaintext JSON package
930
+ else if (parsed.version && parsed.content) {
931
+ version = 'plaintext';
932
+ isEncrypted = false;
933
+ metadata = parsed.metadata || {};
934
+ plainEmbedding = parsed.embedding || [];
935
+ console.log(` Detected plaintext JSON package`);
936
+ return {
937
+ content: parsed.content,
938
+ embedding: plainEmbedding,
939
+ version,
940
+ isEncrypted,
941
+ metadata,
942
+ blobId
943
+ };
944
+ }
945
+ } catch {
946
+ // Not JSON - check if binary SEAL data
947
+ const isBinary = blobData.some(byte => byte < 32 && byte !== 9 && byte !== 10 && byte !== 13);
948
+ if (isBinary || blobData.some(byte => byte > 127)) {
949
+ version = 'legacy';
950
+ isEncrypted = true;
951
+ console.log(` Detected legacy binary format`);
952
+ } else {
953
+ // Plain text content
954
+ version = 'plaintext';
955
+ isEncrypted = false;
956
+ console.log(` Detected plaintext content`);
957
+ return {
958
+ content: new TextDecoder().decode(blobData),
959
+ embedding: [],
960
+ version,
961
+ isEncrypted,
962
+ metadata: {},
963
+ blobId
964
+ };
965
+ }
966
+ }
967
+
968
+ // Step 3: If not encrypted, return as-is
969
+ if (!isEncrypted) {
970
+ return {
971
+ content: new TextDecoder().decode(blobData),
972
+ embedding: plainEmbedding,
973
+ version,
974
+ isEncrypted,
975
+ metadata,
976
+ blobId
977
+ };
978
+ }
979
+
980
+ // Step 4: Get decryption parameters (from options or metadata)
981
+ const memoryCapId = options.memoryCapId || metadata.memoryCapId;
982
+ const keyIdHex = options.keyId || metadata.keyId;
983
+
984
+ if (!memoryCapId || !keyIdHex) {
985
+ throw new Error(
986
+ `Missing decryption parameters. memoryCapId=${!!memoryCapId}, keyId=${!!keyIdHex}. ` +
987
+ `Provide via options or ensure metadata contains these values.`
988
+ );
989
+ }
990
+
991
+ // Step 5: Convert keyId hex string to Uint8Array (SDK handles this!)
992
+ const keyIdBytes = new Uint8Array(
993
+ (keyIdHex.startsWith('0x') ? keyIdHex.slice(2) : keyIdHex)
994
+ .match(/.{1,2}/g)!
995
+ .map((byte: string) => parseInt(byte, 16))
996
+ );
997
+
998
+ // Step 6: Get or create session key
999
+ let sessionKey = options.sessionKey;
1000
+ if (!sessionKey) {
1001
+ if (!options.signFn) {
1002
+ throw new Error(
1003
+ 'Decryption requires either sessionKey or signFn. ' +
1004
+ 'Provide signFn to create session key automatically.'
1005
+ );
1006
+ }
1007
+ if (!this.services.encryption) {
1008
+ throw new Error('Encryption service not configured.');
1009
+ }
1010
+
1011
+ console.log(` Creating session key (will prompt for signature)...`);
1012
+ sessionKey = await this.services.encryption.createSessionKey(
1013
+ this.services.config.userAddress,
1014
+ {
1015
+ signPersonalMessageFn: async (message: string) => {
1016
+ return options.signFn!(message);
1017
+ }
1018
+ }
1019
+ );
1020
+ console.log(` Session key created`);
1021
+ }
1022
+
1023
+ // Step 7: Decrypt content
1024
+ console.log(` Decrypting content...`);
1025
+
1026
+ // Convert base64 to Uint8Array if needed
1027
+ let dataToDecrypt: Uint8Array;
1028
+ if (encryptedContentBase64) {
1029
+ const binaryString = atob(encryptedContentBase64);
1030
+ dataToDecrypt = new Uint8Array(binaryString.length);
1031
+ for (let i = 0; i < binaryString.length; i++) {
1032
+ dataToDecrypt[i] = binaryString.charCodeAt(i);
1033
+ }
1034
+ } else {
1035
+ dataToDecrypt = blobData;
1036
+ }
1037
+
1038
+ const decryptedContentData = await this.services.encryption!.decrypt({
1039
+ encryptedContent: dataToDecrypt,
1040
+ userAddress: this.services.config.userAddress,
1041
+ sessionKey,
1042
+ memoryCapId,
1043
+ keyId: keyIdBytes
1044
+ });
1045
+
1046
+ const content = new TextDecoder().decode(decryptedContentData);
1047
+ console.log(` Content decrypted: "${content.substring(0, 50)}${content.length > 50 ? '...' : ''}"`);
1048
+
1049
+ // Step 8: Decrypt embedding if v2.2
1050
+ let embedding: number[] = plainEmbedding;
1051
+
1052
+ if (version === '2.2' && encryptedEmbeddingBase64) {
1053
+ console.log(` Decrypting embedding...`);
1054
+ const embeddingBinaryString = atob(encryptedEmbeddingBase64);
1055
+ const encryptedEmbeddingBytes = new Uint8Array(embeddingBinaryString.length);
1056
+ for (let i = 0; i < embeddingBinaryString.length; i++) {
1057
+ encryptedEmbeddingBytes[i] = embeddingBinaryString.charCodeAt(i);
1058
+ }
1059
+
1060
+ const decryptedEmbeddingData = await this.services.encryption!.decrypt({
1061
+ encryptedContent: encryptedEmbeddingBytes,
1062
+ userAddress: this.services.config.userAddress,
1063
+ sessionKey,
1064
+ memoryCapId,
1065
+ keyId: keyIdBytes
1066
+ });
1067
+
1068
+ const embeddingJson = new TextDecoder().decode(decryptedEmbeddingData);
1069
+ embedding = JSON.parse(embeddingJson);
1070
+ console.log(` Embedding decrypted: ${embedding.length}D vector`);
1071
+ }
1072
+
1073
+ console.log(`✅ retrieveAndDecrypt complete: ${content.length} chars, ${embedding.length}D embedding`);
1074
+
1075
+ return {
1076
+ content,
1077
+ embedding,
1078
+ version,
1079
+ isEncrypted,
1080
+ metadata,
1081
+ blobId
1082
+ };
1083
+ }
1084
+
1085
+ // ==========================================================================
1086
+ // Batch Operations (Quilt)
1087
+ // ==========================================================================
1088
+
1089
+ /**
1090
+ * Upload multiple memories as a Quilt (batch upload)
1091
+ *
1092
+ * Uses Walrus Quilt for ~90% gas savings compared to individual uploads.
1093
+ * Requires 2 user signatures:
1094
+ * - Transaction 1: Register blob on-chain
1095
+ * - Transaction 2: Certify upload on-chain
1096
+ *
1097
+ * @param memories - Array of memories to upload
1098
+ * @param options - Upload options including signer
1099
+ * @returns Quilt result with file mappings
1100
+ *
1101
+ * @example
1102
+ * ```typescript
1103
+ * const result = await pdw.storage.uploadMemoryBatch(
1104
+ * memories,
1105
+ * {
1106
+ * signer: pdw.getConfig().signer,
1107
+ * epochs: 3,
1108
+ * userAddress: pdw.getConfig().userAddress
1109
+ * }
1110
+ * );
1111
+ * console.log(`Uploaded ${result.files.length} files`);
1112
+ * ```
1113
+ */
1114
+ async uploadMemoryBatch(
1115
+ memories: Array<{
1116
+ content: string;
1117
+ category: string;
1118
+ importance: number;
1119
+ topic: string;
1120
+ embedding: number[];
1121
+ encryptedContent?: Uint8Array; // Optional - only when encryption enabled
1122
+ encryptedEmbedding?: Uint8Array; // Optional - encrypted embedding for v2.2
1123
+ embeddingDimensions?: number; // Original embedding dimensions (when encrypted, embedding is [])
1124
+ summary?: string;
1125
+ id?: string;
1126
+ }>,
1127
+ options: {
1128
+ signer: any; // UnifiedSigner
1129
+ epochs?: number;
1130
+ userAddress: string;
1131
+ }
1132
+ ): Promise<{
1133
+ quiltId: string;
1134
+ files: Array<{ identifier: string; blobId: string }>;
1135
+ uploadTimeMs: number;
1136
+ }> {
1137
+ if (!this.services.storage) {
1138
+ throw new Error('Storage service not configured.');
1139
+ }
1140
+
1141
+ return this.services.storage.uploadMemoryBatch(memories, options);
1142
+ }
1143
+ }