@cmdoss/memwal-sdk 0.7.0 → 0.8.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 (192) hide show
  1. package/README.md +129 -0
  2. package/dist/client/ClientMemoryManager.js +2 -2
  3. package/dist/client/ClientMemoryManager.js.map +1 -1
  4. package/dist/client/PersonalDataWallet.d.ts.map +1 -1
  5. package/dist/client/SimplePDWClient.d.ts +28 -0
  6. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  7. package/dist/client/SimplePDWClient.js +29 -6
  8. package/dist/client/SimplePDWClient.js.map +1 -1
  9. package/dist/client/namespaces/MemoryNamespace.d.ts +4 -0
  10. package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
  11. package/dist/client/namespaces/MemoryNamespace.js +168 -39
  12. package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
  13. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts +12 -2
  14. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
  15. package/dist/client/namespaces/consolidated/BlockchainNamespace.js +40 -2
  16. package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
  17. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
  18. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
  19. package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
  20. package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
  21. package/dist/config/ConfigurationHelper.js +61 -61
  22. package/dist/config/defaults.js +2 -2
  23. package/dist/config/defaults.js.map +1 -1
  24. package/dist/graph/GraphService.js +20 -20
  25. package/dist/infrastructure/seal/EncryptionService.d.ts +9 -5
  26. package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
  27. package/dist/infrastructure/seal/EncryptionService.js +37 -15
  28. package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
  29. package/dist/infrastructure/seal/SealService.d.ts +13 -5
  30. package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
  31. package/dist/infrastructure/seal/SealService.js +36 -34
  32. package/dist/infrastructure/seal/SealService.js.map +1 -1
  33. package/dist/langchain/createPDWRAG.js +30 -30
  34. package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
  35. package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
  36. package/dist/retrieval/MemoryDecryptionPipeline.js.map +1 -1
  37. package/dist/services/CapabilityService.d.ts.map +1 -1
  38. package/dist/services/CapabilityService.js +30 -14
  39. package/dist/services/CapabilityService.js.map +1 -1
  40. package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
  41. package/dist/services/CrossContextPermissionService.js +9 -7
  42. package/dist/services/CrossContextPermissionService.js.map +1 -1
  43. package/dist/services/EncryptionService.d.ts.map +1 -1
  44. package/dist/services/EncryptionService.js +6 -5
  45. package/dist/services/EncryptionService.js.map +1 -1
  46. package/dist/services/GeminiAIService.js +309 -309
  47. package/dist/services/StorageService.d.ts +1 -0
  48. package/dist/services/StorageService.d.ts.map +1 -1
  49. package/dist/services/StorageService.js +60 -10
  50. package/dist/services/StorageService.js.map +1 -1
  51. package/dist/services/TransactionService.d.ts +20 -0
  52. package/dist/services/TransactionService.d.ts.map +1 -1
  53. package/dist/services/TransactionService.js +43 -0
  54. package/dist/services/TransactionService.js.map +1 -1
  55. package/dist/services/ViewService.js +2 -2
  56. package/dist/services/ViewService.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/access/PermissionService.ts +635 -635
  59. package/src/access/index.ts +8 -8
  60. package/src/aggregation/AggregationService.ts +389 -389
  61. package/src/aggregation/index.ts +8 -8
  62. package/src/ai-sdk/PDWVectorStore.ts +715 -715
  63. package/src/ai-sdk/index.ts +65 -65
  64. package/src/ai-sdk/tools.ts +460 -460
  65. package/src/ai-sdk/types.ts +404 -404
  66. package/src/batch/BatchManager.ts +597 -597
  67. package/src/batch/BatchingService.ts +429 -429
  68. package/src/batch/MemoryProcessingCache.ts +492 -492
  69. package/src/batch/index.ts +30 -30
  70. package/src/browser.ts +200 -200
  71. package/src/client/ClientMemoryManager.ts +987 -987
  72. package/src/client/PersonalDataWallet.ts +345 -345
  73. package/src/client/SimplePDWClient.ts +1289 -1237
  74. package/src/client/factory.ts +154 -154
  75. package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
  76. package/src/client/namespaces/BatchNamespace.ts +356 -356
  77. package/src/client/namespaces/CacheNamespace.ts +123 -123
  78. package/src/client/namespaces/CapabilityNamespace.ts +217 -217
  79. package/src/client/namespaces/ClassifyNamespace.ts +169 -169
  80. package/src/client/namespaces/ContextNamespace.ts +297 -297
  81. package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
  82. package/src/client/namespaces/EncryptionNamespace.ts +221 -221
  83. package/src/client/namespaces/GraphNamespace.ts +468 -468
  84. package/src/client/namespaces/IndexNamespace.ts +361 -361
  85. package/src/client/namespaces/MemoryNamespace.ts +1422 -1272
  86. package/src/client/namespaces/PermissionsNamespace.ts +254 -254
  87. package/src/client/namespaces/PipelineNamespace.ts +220 -220
  88. package/src/client/namespaces/SearchNamespace.ts +1049 -1049
  89. package/src/client/namespaces/StorageNamespace.ts +458 -458
  90. package/src/client/namespaces/TxNamespace.ts +260 -260
  91. package/src/client/namespaces/WalletNamespace.ts +243 -243
  92. package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
  93. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -564
  94. package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
  95. package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
  96. package/src/client/namespaces/consolidated/index.ts +39 -39
  97. package/src/client/signers/DappKitSigner.ts +207 -207
  98. package/src/client/signers/KeypairSigner.ts +108 -108
  99. package/src/client/signers/UnifiedSigner.ts +110 -110
  100. package/src/client/signers/WalletAdapterSigner.ts +159 -159
  101. package/src/client/signers/index.ts +26 -26
  102. package/src/config/ConfigurationHelper.ts +412 -412
  103. package/src/config/defaults.ts +51 -51
  104. package/src/config/index.ts +8 -8
  105. package/src/config/validation.ts +70 -70
  106. package/src/core/index.ts +14 -14
  107. package/src/core/interfaces/IService.ts +307 -307
  108. package/src/core/interfaces/index.ts +8 -8
  109. package/src/core/types/capability.ts +297 -297
  110. package/src/core/types/index.ts +870 -870
  111. package/src/core/types/wallet.ts +270 -270
  112. package/src/core/types.ts +9 -9
  113. package/src/core/wallet.ts +222 -222
  114. package/src/embedding/index.ts +19 -19
  115. package/src/embedding/types.ts +357 -357
  116. package/src/errors/index.ts +602 -602
  117. package/src/errors/recovery.ts +461 -461
  118. package/src/errors/validation.ts +567 -567
  119. package/src/generated/pdw/capability.ts +319 -319
  120. package/src/generated/pdw/deps/sui/object.ts +12 -12
  121. package/src/generated/pdw/deps/sui/vec_map.ts +32 -32
  122. package/src/generated/pdw/memory.ts +1087 -1087
  123. package/src/generated/pdw/wallet.ts +123 -123
  124. package/src/generated/utils/index.ts +159 -159
  125. package/src/graph/GraphService.ts +887 -887
  126. package/src/graph/KnowledgeGraphManager.ts +728 -728
  127. package/src/graph/index.ts +25 -25
  128. package/src/index.ts +498 -498
  129. package/src/infrastructure/index.ts +22 -22
  130. package/src/infrastructure/seal/EncryptionService.ts +628 -603
  131. package/src/infrastructure/seal/SealService.ts +613 -615
  132. package/src/infrastructure/seal/index.ts +9 -9
  133. package/src/infrastructure/sui/BlockchainManager.ts +627 -627
  134. package/src/infrastructure/sui/SuiService.ts +888 -888
  135. package/src/infrastructure/sui/index.ts +9 -9
  136. package/src/infrastructure/walrus/StorageManager.ts +604 -604
  137. package/src/infrastructure/walrus/WalrusStorageService.ts +612 -612
  138. package/src/infrastructure/walrus/index.ts +9 -9
  139. package/src/langchain/PDWEmbeddings.ts +145 -145
  140. package/src/langchain/PDWVectorStore.ts +456 -456
  141. package/src/langchain/createPDWRAG.ts +303 -303
  142. package/src/langchain/index.ts +47 -47
  143. package/src/permissions/ConsentRepository.browser.ts +249 -249
  144. package/src/permissions/ConsentRepository.ts +364 -364
  145. package/src/permissions/index.ts +9 -9
  146. package/src/pipeline/MemoryPipeline.ts +862 -862
  147. package/src/pipeline/PipelineManager.ts +683 -683
  148. package/src/pipeline/index.ts +26 -26
  149. package/src/retrieval/AdvancedSearchService.ts +629 -629
  150. package/src/retrieval/MemoryAnalyticsService.ts +711 -711
  151. package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
  152. package/src/retrieval/MemoryRetrievalService.ts +904 -904
  153. package/src/retrieval/index.ts +42 -42
  154. package/src/services/BatchService.ts +352 -352
  155. package/src/services/CapabilityService.ts +464 -448
  156. package/src/services/ClassifierService.ts +465 -465
  157. package/src/services/CrossContextPermissionService.ts +486 -484
  158. package/src/services/EmbeddingService.ts +771 -771
  159. package/src/services/EncryptionService.ts +712 -711
  160. package/src/services/GeminiAIService.ts +753 -753
  161. package/src/services/IndexManager.ts +977 -977
  162. package/src/services/MemoryIndexService.ts +1003 -1003
  163. package/src/services/MemoryService.ts +369 -369
  164. package/src/services/QueryService.ts +890 -890
  165. package/src/services/StorageService.ts +1182 -1126
  166. package/src/services/TransactionService.ts +838 -790
  167. package/src/services/VectorService.ts +462 -462
  168. package/src/services/ViewService.ts +484 -484
  169. package/src/services/index.ts +25 -25
  170. package/src/services/storage/BlobAttributesManager.ts +333 -333
  171. package/src/services/storage/KnowledgeGraphManager.ts +425 -425
  172. package/src/services/storage/MemorySearchManager.ts +387 -387
  173. package/src/services/storage/QuiltBatchManager.ts +1130 -1130
  174. package/src/services/storage/WalrusMetadataManager.ts +268 -268
  175. package/src/services/storage/WalrusStorageManager.ts +287 -287
  176. package/src/services/storage/index.ts +57 -57
  177. package/src/types/index.ts +13 -13
  178. package/src/utils/LRUCache.ts +378 -378
  179. package/src/utils/index.ts +76 -76
  180. package/src/utils/memoryIndexOnChain.ts +507 -507
  181. package/src/utils/rebuildIndex.ts +290 -290
  182. package/src/utils/rebuildIndexNode.ts +771 -771
  183. package/src/vector/BrowserHnswIndexService.ts +758 -758
  184. package/src/vector/HnswWasmService.ts +731 -731
  185. package/src/vector/IHnswService.ts +233 -233
  186. package/src/vector/NodeHnswService.ts +833 -833
  187. package/src/vector/VectorManager.ts +478 -478
  188. package/src/vector/createHnswService.ts +135 -135
  189. package/src/vector/index.ts +56 -56
  190. package/src/wallet/ContextWalletService.ts +656 -656
  191. package/src/wallet/MainWalletService.ts +317 -317
  192. package/src/wallet/index.ts +17 -17
@@ -1,888 +1,888 @@
1
- /**
2
- * SuiService - Blockchain Integration for Memory Records
3
- *
4
- * Comprehensive Sui blockchain integration for memory ownership records,
5
- * transaction batching, and decentralized metadata management.
6
- */
7
-
8
- import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
9
- import { Transaction } from '@mysten/sui/transactions';
10
- import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
11
- import { fromB64, toB64 } from '@mysten/bcs';
12
-
13
- export interface SuiConfig {
14
- network?: 'testnet' | 'mainnet' | 'devnet' | 'localnet';
15
- packageId?: string;
16
- adminPrivateKey?: string;
17
- rpcUrl?: string;
18
- enableBatching?: boolean;
19
- batchSize?: number;
20
- batchDelayMs?: number;
21
- gasObjectId?: string;
22
- }
23
-
24
- export interface MemoryRecord {
25
- id: string;
26
- owner: string;
27
- category: string;
28
- vectorId: number;
29
- blobId: string;
30
- metadata: MemoryMetadata;
31
- createdAt: Date;
32
- version: number;
33
- }
34
-
35
- export interface MemoryIndex {
36
- id: string;
37
- owner: string;
38
- version: number;
39
- indexBlobId: string;
40
- graphBlobId: string;
41
- lastUpdated: Date;
42
- }
43
-
44
- export interface MemoryMetadata {
45
- contentType: string;
46
- contentSize: number;
47
- contentHash: string;
48
- category: string;
49
- topic: string;
50
- importance: number; // 1-10 scale
51
- embeddingBlobId: string;
52
- embeddingDimension: number;
53
- createdTimestamp: number;
54
- updatedTimestamp: number;
55
- customMetadata: Record<string, string>;
56
- }
57
-
58
- export interface TransactionResult {
59
- digest: string;
60
- objectId?: string;
61
- effects?: any;
62
- events?: any[];
63
- success: boolean;
64
- error?: string;
65
- gasUsed?: number;
66
- }
67
-
68
- export interface BatchTransaction {
69
- id: string;
70
- userId: string;
71
- operation: 'create_memory' | 'create_index' | 'update_index';
72
- parameters: any;
73
- priority: number;
74
- timestamp: Date;
75
- }
76
-
77
- export interface SuiStats {
78
- totalTransactions: number;
79
- successfulTransactions: number;
80
- failedTransactions: number;
81
- averageGasUsed: number;
82
- batchedTransactions: number;
83
- totalGasCost: number;
84
- networkHealth: 'healthy' | 'degraded' | 'offline';
85
- lastSuccessfulTransaction?: Date;
86
- }
87
-
88
- /**
89
- * Sui blockchain service for memory ownership and metadata management
90
- */
91
- export class SuiService {
92
- private client!: SuiClient;
93
- private adminKeypair?: Ed25519Keypair;
94
- private readonly config: Required<SuiConfig>;
95
- private batchQueue: BatchTransaction[] = [];
96
- private batchTimer?: NodeJS.Timeout;
97
- private pendingTransactions = new Map<string, Promise<TransactionResult>>();
98
-
99
- private stats: SuiStats = {
100
- totalTransactions: 0,
101
- successfulTransactions: 0,
102
- failedTransactions: 0,
103
- averageGasUsed: 0,
104
- batchedTransactions: 0,
105
- totalGasCost: 0,
106
- networkHealth: 'healthy'
107
- };
108
-
109
- constructor(config: Partial<SuiConfig> = {}) {
110
- this.config = {
111
- network: config.network || 'testnet',
112
- packageId: config.packageId || '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
113
- adminPrivateKey: config.adminPrivateKey || '',
114
- rpcUrl: config.rpcUrl || '',
115
- enableBatching: config.enableBatching !== false,
116
- batchSize: config.batchSize || 10,
117
- batchDelayMs: config.batchDelayMs || 5000,
118
- gasObjectId: config.gasObjectId || ''
119
- };
120
-
121
- this.initializeSuiClient();
122
- this.initializeAdminKeypair();
123
- }
124
-
125
- // ==================== MEMORY RECORD OPERATIONS ====================
126
-
127
- /**
128
- * Create memory record on Sui blockchain
129
- */
130
- async createMemoryRecord(
131
- userAddress: string,
132
- category: string,
133
- vectorId: number,
134
- blobId: string,
135
- metadata: MemoryMetadata,
136
- options: {
137
- enableBatching?: boolean;
138
- priority?: number;
139
- } = {}
140
- ): Promise<TransactionResult> {
141
- if (options.enableBatching && this.config.enableBatching) {
142
- return this.addToBatch({
143
- id: this.generateTransactionId(),
144
- userId: userAddress,
145
- operation: 'create_memory',
146
- parameters: { category, vectorId, blobId, metadata },
147
- priority: options.priority || 1,
148
- timestamp: new Date()
149
- });
150
- }
151
-
152
- return await this.executeCreateMemoryRecord(userAddress, category, vectorId, blobId, metadata);
153
- }
154
-
155
- /**
156
- * Create memory index on Sui blockchain
157
- */
158
- async createMemoryIndex(
159
- userAddress: string,
160
- indexBlobId: string,
161
- graphBlobId: string,
162
- options: {
163
- enableBatching?: boolean;
164
- priority?: number;
165
- } = {}
166
- ): Promise<TransactionResult> {
167
- if (options.enableBatching && this.config.enableBatching) {
168
- return this.addToBatch({
169
- id: this.generateTransactionId(),
170
- userId: userAddress,
171
- operation: 'create_index',
172
- parameters: { indexBlobId, graphBlobId },
173
- priority: options.priority || 1,
174
- timestamp: new Date()
175
- });
176
- }
177
-
178
- return await this.executeCreateMemoryIndex(userAddress, indexBlobId, graphBlobId);
179
- }
180
-
181
- /**
182
- * Update memory index on Sui blockchain
183
- */
184
- async updateMemoryIndex(
185
- indexId: string,
186
- userAddress: string,
187
- expectedVersion: number,
188
- newIndexBlobId: string,
189
- newGraphBlobId: string,
190
- options: {
191
- enableBatching?: boolean;
192
- priority?: number;
193
- } = {}
194
- ): Promise<TransactionResult> {
195
- if (options.enableBatching && this.config.enableBatching) {
196
- return this.addToBatch({
197
- id: this.generateTransactionId(),
198
- userId: userAddress,
199
- operation: 'update_index',
200
- parameters: { indexId, expectedVersion, newIndexBlobId, newGraphBlobId },
201
- priority: options.priority || 1,
202
- timestamp: new Date()
203
- });
204
- }
205
-
206
- return await this.executeUpdateMemoryIndex(
207
- indexId,
208
- userAddress,
209
- expectedVersion,
210
- newIndexBlobId,
211
- newGraphBlobId
212
- );
213
- }
214
-
215
- /**
216
- * Get memory record by ID
217
- */
218
- async getMemoryRecord(objectId: string): Promise<MemoryRecord | null> {
219
- try {
220
- const response = await this.client.getObject({
221
- id: objectId,
222
- options: {
223
- showContent: true,
224
- showOwner: true,
225
- showType: true
226
- }
227
- });
228
-
229
- if (!response.data) {
230
- return null;
231
- }
232
-
233
- const content = response.data.content as any;
234
- if (!content || content.dataType !== 'moveObject') {
235
- return null;
236
- }
237
-
238
- const fields = content.fields;
239
- return {
240
- id: objectId,
241
- owner: fields.owner,
242
- category: fields.category,
243
- vectorId: parseInt(fields.vector_id),
244
- blobId: fields.blob_id,
245
- metadata: this.parseMetadata(fields.metadata),
246
- createdAt: new Date(parseInt(fields.metadata.created_timestamp)),
247
- version: fields.version || 1
248
- };
249
-
250
- } catch (error) {
251
- console.error('Error getting memory record:', error);
252
- return null;
253
- }
254
- }
255
-
256
- /**
257
- * Get memory index by ID
258
- */
259
- async getMemoryIndex(indexId: string): Promise<MemoryIndex | null> {
260
- try {
261
- const response = await this.client.getObject({
262
- id: indexId,
263
- options: {
264
- showContent: true,
265
- showOwner: true
266
- }
267
- });
268
-
269
- if (!response.data) {
270
- return null;
271
- }
272
-
273
- const content = response.data.content as any;
274
- if (!content || content.dataType !== 'moveObject') {
275
- return null;
276
- }
277
-
278
- const fields = content.fields;
279
- return {
280
- id: indexId,
281
- owner: fields.owner,
282
- version: parseInt(fields.version),
283
- indexBlobId: fields.index_blob_id,
284
- graphBlobId: fields.graph_blob_id,
285
- lastUpdated: new Date() // TODO: Get from blockchain timestamp
286
- };
287
-
288
- } catch (error) {
289
- console.error('Error getting memory index:', error);
290
- return null;
291
- }
292
- }
293
-
294
- /**
295
- * Get user's memory records
296
- */
297
- async getUserMemoryRecords(userAddress: string, limit: number = 100): Promise<MemoryRecord[]> {
298
- try {
299
- const response = await this.client.getOwnedObjects({
300
- owner: userAddress,
301
- filter: {
302
- StructType: `${this.config.packageId}::memory::Memory`
303
- },
304
- options: {
305
- showContent: true,
306
- showType: true
307
- },
308
- limit
309
- });
310
-
311
- const memoryRecords: MemoryRecord[] = [];
312
-
313
- for (const item of response.data) {
314
- if (item.data && item.data.content) {
315
- const content = item.data.content as any;
316
- if (content.dataType === 'moveObject' && content.fields) {
317
- const fields = content.fields;
318
- memoryRecords.push({
319
- id: item.data.objectId,
320
- owner: fields.owner,
321
- category: fields.category,
322
- vectorId: parseInt(fields.vector_id),
323
- blobId: fields.blob_id,
324
- metadata: this.parseMetadata(fields.metadata),
325
- createdAt: new Date(parseInt(fields.metadata.created_timestamp)),
326
- version: fields.version || 1
327
- });
328
- }
329
- }
330
- }
331
-
332
- return memoryRecords;
333
-
334
- } catch (error) {
335
- console.error('Error getting user memory records:', error);
336
- return [];
337
- }
338
- }
339
-
340
- /**
341
- * Get user's memory indices
342
- */
343
- async getUserMemoryIndices(userAddress: string): Promise<MemoryIndex[]> {
344
- try {
345
- const response = await this.client.getOwnedObjects({
346
- owner: userAddress,
347
- filter: {
348
- StructType: `${this.config.packageId}::memory::MemoryIndex`
349
- },
350
- options: {
351
- showContent: true,
352
- showType: true
353
- }
354
- });
355
-
356
- const memoryIndices: MemoryIndex[] = [];
357
-
358
- for (const item of response.data) {
359
- if (item.data && item.data.content) {
360
- const content = item.data.content as any;
361
- if (content.dataType === 'moveObject' && content.fields) {
362
- const fields = content.fields;
363
- memoryIndices.push({
364
- id: item.data.objectId,
365
- owner: fields.owner,
366
- version: parseInt(fields.version),
367
- indexBlobId: fields.index_blob_id,
368
- graphBlobId: fields.graph_blob_id,
369
- lastUpdated: new Date() // TODO: Get from blockchain events
370
- });
371
- }
372
- }
373
- }
374
-
375
- return memoryIndices;
376
-
377
- } catch (error) {
378
- console.error('Error getting user memory indices:', error);
379
- return [];
380
- }
381
- }
382
-
383
- // ==================== BATCH OPERATIONS ====================
384
-
385
- /**
386
- * Process pending batch transactions
387
- */
388
- async processBatchQueue(): Promise<TransactionResult[]> {
389
- if (this.batchQueue.length === 0) {
390
- return [];
391
- }
392
-
393
- const batch = [...this.batchQueue];
394
- this.batchQueue.length = 0; // Clear queue
395
-
396
- if (this.batchTimer) {
397
- clearTimeout(this.batchTimer);
398
- this.batchTimer = undefined;
399
- }
400
-
401
- console.log(`Processing batch of ${batch.length} transactions`);
402
-
403
- // Sort by priority (higher priority first)
404
- batch.sort((a, b) => b.priority - a.priority);
405
-
406
- const results: TransactionResult[] = [];
407
-
408
- // Execute transactions in sequence to avoid nonce conflicts
409
- for (const transaction of batch) {
410
- try {
411
- let result: TransactionResult;
412
-
413
- switch (transaction.operation) {
414
- case 'create_memory':
415
- const { category, vectorId, blobId, metadata } = transaction.parameters;
416
- result = await this.executeCreateMemoryRecord(
417
- transaction.userId, category, vectorId, blobId, metadata
418
- );
419
- break;
420
-
421
- case 'create_index':
422
- const { indexBlobId, graphBlobId } = transaction.parameters;
423
- result = await this.executeCreateMemoryIndex(
424
- transaction.userId, indexBlobId, graphBlobId
425
- );
426
- break;
427
-
428
- case 'update_index':
429
- const { indexId, expectedVersion, newIndexBlobId, newGraphBlobId } = transaction.parameters;
430
- result = await this.executeUpdateMemoryIndex(
431
- indexId, transaction.userId, expectedVersion, newIndexBlobId, newGraphBlobId
432
- );
433
- break;
434
-
435
- default:
436
- result = {
437
- digest: '',
438
- success: false,
439
- error: `Unknown operation: ${transaction.operation}`
440
- };
441
- }
442
-
443
- results.push(result);
444
- this.stats.batchedTransactions++;
445
-
446
- } catch (error) {
447
- results.push({
448
- digest: '',
449
- success: false,
450
- error: error instanceof Error ? error.message : 'Unknown error'
451
- });
452
- }
453
- }
454
-
455
- console.log(`Batch processing complete: ${results.filter(r => r.success).length}/${results.length} successful`);
456
- return results;
457
- }
458
-
459
- /**
460
- * Force process batch queue immediately
461
- */
462
- async flushBatchQueue(): Promise<TransactionResult[]> {
463
- return await this.processBatchQueue();
464
- }
465
-
466
- // ==================== NETWORK OPERATIONS ====================
467
-
468
- /**
469
- * Check network health
470
- */
471
- async checkNetworkHealth(): Promise<'healthy' | 'degraded' | 'offline'> {
472
- try {
473
- const start = Date.now();
474
- const response = await this.client.getLatestCheckpointSequenceNumber();
475
- const latency = Date.now() - start;
476
-
477
- if (response && latency < 5000) {
478
- this.stats.networkHealth = 'healthy';
479
- } else {
480
- this.stats.networkHealth = 'degraded';
481
- }
482
-
483
- } catch (error) {
484
- this.stats.networkHealth = 'offline';
485
- }
486
-
487
- return this.stats.networkHealth;
488
- }
489
-
490
- /**
491
- * Get gas price recommendations
492
- */
493
- async getGasPrice(): Promise<{ referenceGasPrice: number; recommendation: string }> {
494
- try {
495
- const gasPrice = await this.client.getReferenceGasPrice();
496
-
497
- return {
498
- referenceGasPrice: parseInt(gasPrice.toString()),
499
- recommendation: parseInt(gasPrice.toString()) > 1000 ? 'high' : 'normal'
500
- };
501
-
502
- } catch (error) {
503
- console.error('Error getting gas price:', error);
504
- return {
505
- referenceGasPrice: 1000,
506
- recommendation: 'normal'
507
- };
508
- }
509
- }
510
-
511
- /**
512
- * Get transaction by digest
513
- */
514
- async getTransaction(digest: string) {
515
- try {
516
- return await this.client.getTransactionBlock({
517
- digest,
518
- options: {
519
- showEffects: true,
520
- showEvents: true,
521
- showInput: true,
522
- showObjectChanges: true
523
- }
524
- });
525
- } catch (error) {
526
- console.error('Error getting transaction:', error);
527
- return null;
528
- }
529
- }
530
-
531
- // ==================== STATISTICS & MONITORING ====================
532
-
533
- /**
534
- * Get service statistics
535
- */
536
- getStats(): SuiStats {
537
- return { ...this.stats };
538
- }
539
-
540
- /**
541
- * Get batch queue status
542
- */
543
- getBatchQueueStatus(): {
544
- pending: number;
545
- nextProcessing: Date | null;
546
- averageBatchSize: number;
547
- } {
548
- return {
549
- pending: this.batchQueue.length,
550
- nextProcessing: this.batchTimer ? new Date(Date.now() + this.config.batchDelayMs) : null,
551
- averageBatchSize: this.stats.batchedTransactions > 0
552
- ? this.config.batchSize
553
- : 0
554
- };
555
- }
556
-
557
- /**
558
- * Reset statistics
559
- */
560
- resetStats(): void {
561
- this.stats = {
562
- totalTransactions: 0,
563
- successfulTransactions: 0,
564
- failedTransactions: 0,
565
- averageGasUsed: 0,
566
- batchedTransactions: 0,
567
- totalGasCost: 0,
568
- networkHealth: 'healthy'
569
- };
570
- }
571
-
572
- // ==================== PRIVATE METHODS ====================
573
-
574
- private initializeSuiClient(): void {
575
- try {
576
- const networkUrl = this.config.rpcUrl || getFullnodeUrl(this.config.network);
577
- this.client = new SuiClient({ url: networkUrl });
578
- console.log(`Sui client initialized for ${this.config.network} network`);
579
- } catch (error) {
580
- console.error('Failed to initialize Sui client:', error);
581
- throw new Error(`Sui client initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
582
- }
583
- }
584
-
585
- private initializeAdminKeypair(): void {
586
- if (!this.config.adminPrivateKey) {
587
- console.warn('No admin private key provided. Creating random keypair for development.');
588
- this.adminKeypair = new Ed25519Keypair();
589
- return;
590
- }
591
-
592
- try {
593
- // Clean up the private key
594
- let privateKey = this.config.adminPrivateKey.replace(/\s+/g, '');
595
-
596
- if (privateKey.startsWith('suiprivkey1')) {
597
- // Sui private key format
598
- this.adminKeypair = Ed25519Keypair.fromSecretKey(privateKey);
599
- } else {
600
- // Hex format
601
- if (!privateKey.startsWith('0x')) {
602
- privateKey = '0x' + privateKey;
603
- }
604
-
605
- const keyBuffer = Buffer.from(privateKey.replace('0x', ''), 'hex');
606
- if (keyBuffer.length !== 32) {
607
- throw new Error(`Invalid key length: ${keyBuffer.length}, expected 32`);
608
- }
609
-
610
- this.adminKeypair = Ed25519Keypair.fromSecretKey(new Uint8Array(keyBuffer));
611
- }
612
-
613
- const adminAddress = this.adminKeypair.getPublicKey().toSuiAddress();
614
- console.log(`Admin keypair initialized with address: ${adminAddress}`);
615
-
616
- } catch (error) {
617
- console.error('Failed to initialize admin keypair:', error);
618
- console.warn('Using random keypair for development');
619
- this.adminKeypair = new Ed25519Keypair();
620
- }
621
- }
622
-
623
- private async executeCreateMemoryRecord(
624
- userAddress: string,
625
- category: string,
626
- vectorId: number,
627
- blobId: string,
628
- metadata: MemoryMetadata
629
- ): Promise<TransactionResult> {
630
- try {
631
- const tx = new Transaction();
632
-
633
- // Convert metadata to Move-compatible format
634
- const metadataFields = this.serializeMetadata(metadata);
635
-
636
- tx.moveCall({
637
- target: `${this.config.packageId}::memory::create_memory_record`,
638
- arguments: [
639
- tx.pure.string(category),
640
- tx.pure.u64(vectorId),
641
- tx.pure.string(blobId),
642
- tx.pure(metadataFields) // Serialized metadata
643
- ]
644
- });
645
-
646
- return await this.executeTransaction(tx, userAddress);
647
-
648
- } catch (error) {
649
- console.error('Error creating memory record:', error);
650
- return {
651
- digest: '',
652
- success: false,
653
- error: (error instanceof Error ? error.message : 'Unknown error')
654
- };
655
- }
656
- }
657
-
658
- private async executeCreateMemoryIndex(
659
- userAddress: string,
660
- indexBlobId: string,
661
- graphBlobId: string
662
- ): Promise<TransactionResult> {
663
- try {
664
- const tx = new Transaction();
665
-
666
- tx.moveCall({
667
- target: `${this.config.packageId}::memory::create_memory_index`,
668
- arguments: [
669
- tx.pure(new TextEncoder().encode(indexBlobId)),
670
- tx.pure(new TextEncoder().encode(graphBlobId))
671
- ]
672
- });
673
-
674
- return await this.executeTransaction(tx, userAddress);
675
-
676
- } catch (error) {
677
- console.error('Error creating memory index:', error);
678
- return {
679
- digest: '',
680
- success: false,
681
- error: (error instanceof Error ? error.message : 'Unknown error')
682
- };
683
- }
684
- }
685
-
686
- private async executeUpdateMemoryIndex(
687
- indexId: string,
688
- userAddress: string,
689
- expectedVersion: number,
690
- newIndexBlobId: string,
691
- newGraphBlobId: string
692
- ): Promise<TransactionResult> {
693
- try {
694
- const tx = new Transaction();
695
-
696
- tx.moveCall({
697
- target: `${this.config.packageId}::memory::update_memory_index`,
698
- arguments: [
699
- tx.object(indexId),
700
- tx.pure.u64(expectedVersion),
701
- tx.pure.string(newIndexBlobId),
702
- tx.pure.string(newGraphBlobId)
703
- ]
704
- });
705
-
706
- return await this.executeTransaction(tx, userAddress);
707
-
708
- } catch (error) {
709
- console.error('Error updating memory index:', error);
710
- return {
711
- digest: '',
712
- success: false,
713
- error: (error instanceof Error ? error.message : 'Unknown error')
714
- };
715
- }
716
- }
717
-
718
- private async executeTransaction(tx: Transaction, signer?: string): Promise<TransactionResult> {
719
- const startTime = Date.now();
720
- this.stats.totalTransactions++;
721
-
722
- try {
723
- if (!this.adminKeypair) {
724
- throw new Error('Admin keypair not initialized');
725
- }
726
-
727
- // Set gas payment
728
- const coins = await this.client.getCoins({
729
- owner: this.adminKeypair.getPublicKey().toSuiAddress(),
730
- coinType: '0x2::sui::SUI'
731
- });
732
-
733
- if (coins.data.length === 0) {
734
- throw new Error('No gas coins available');
735
- }
736
-
737
- tx.setGasPayment(coins.data.slice(0, 10).map(coin => ({
738
- objectId: coin.coinObjectId,
739
- version: coin.version,
740
- digest: coin.digest
741
- })));
742
-
743
- // Execute transaction
744
- const result = await this.client.signAndExecuteTransaction({
745
- transaction: tx,
746
- signer: this.adminKeypair,
747
- options: {
748
- showEffects: true,
749
- showEvents: true,
750
- showObjectChanges: true
751
- }
752
- });
753
-
754
- // Wait for transaction to be finalized to prevent gas coin version conflicts
755
- if (result.digest) {
756
- await this.client.waitForTransaction({ digest: result.digest });
757
- }
758
-
759
- // Update statistics
760
- const gasUsed = result.effects?.gasUsed?.computationCost || 0;
761
- this.updateGasStats(typeof gasUsed === 'string' ? parseInt(gasUsed) : gasUsed);
762
- this.stats.successfulTransactions++;
763
- this.stats.lastSuccessfulTransaction = new Date();
764
-
765
- // Extract created object ID if any
766
- const objectId = this.extractCreatedObjectId(result);
767
-
768
- return {
769
- digest: result.digest,
770
- objectId,
771
- effects: result.effects,
772
- events: result.events || [],
773
- success: true,
774
- gasUsed: typeof gasUsed === 'string' ? parseInt(gasUsed) : gasUsed
775
- };
776
-
777
- } catch (error) {
778
- this.stats.failedTransactions++;
779
- console.error('Transaction execution failed:', error);
780
-
781
- return {
782
- digest: '',
783
- success: false,
784
- error: (error instanceof Error ? error.message : 'Unknown error')
785
- };
786
- }
787
- }
788
-
789
- private extractCreatedObjectId(result: any): string | undefined {
790
- if (result.objectChanges) {
791
- for (const change of result.objectChanges) {
792
- if (change.type === 'created') {
793
- return change.objectId;
794
- }
795
- }
796
- }
797
- return undefined;
798
- }
799
-
800
- private addToBatch(transaction: BatchTransaction): Promise<TransactionResult> {
801
- this.batchQueue.push(transaction);
802
- this.scheduleBatchProcessing();
803
-
804
- // Return a promise that resolves when batch is processed
805
- return new Promise((resolve) => {
806
- // Simple implementation - in production, track individual transaction results
807
- setTimeout(() => {
808
- resolve({
809
- digest: 'batched',
810
- success: true
811
- });
812
- }, this.config.batchDelayMs + 1000);
813
- });
814
- }
815
-
816
- private scheduleBatchProcessing(): void {
817
- if (this.batchTimer) {
818
- return; // Already scheduled
819
- }
820
-
821
- // Process when batch is full or after delay
822
- if (this.batchQueue.length >= this.config.batchSize) {
823
- setImmediate(() => this.processBatchQueue());
824
- } else {
825
- this.batchTimer = setTimeout(() => {
826
- this.processBatchQueue();
827
- }, this.config.batchDelayMs);
828
- }
829
- }
830
-
831
- private serializeMetadata(metadata: MemoryMetadata): any {
832
- // Convert to Move-compatible format
833
- return {
834
- content_type: metadata.contentType,
835
- content_size: metadata.contentSize,
836
- content_hash: metadata.contentHash,
837
- category: metadata.category,
838
- topic: metadata.topic,
839
- importance: Math.max(1, Math.min(10, metadata.importance)),
840
- embedding_blob_id: metadata.embeddingBlobId,
841
- embedding_dimension: metadata.embeddingDimension,
842
- created_timestamp: metadata.createdTimestamp,
843
- updated_timestamp: metadata.updatedTimestamp || metadata.createdTimestamp,
844
- custom_metadata: Object.entries(metadata.customMetadata).map(([key, value]) => ({ key, value }))
845
- };
846
- }
847
-
848
- private parseMetadata(fields: any): MemoryMetadata {
849
- return {
850
- contentType: fields.content_type,
851
- contentSize: parseInt(fields.content_size),
852
- contentHash: fields.content_hash,
853
- category: fields.category,
854
- topic: fields.topic,
855
- importance: parseInt(fields.importance),
856
- embeddingBlobId: fields.embedding_blob_id,
857
- embeddingDimension: parseInt(fields.embedding_dimension),
858
- createdTimestamp: parseInt(fields.created_timestamp),
859
- updatedTimestamp: parseInt(fields.updated_timestamp),
860
- customMetadata: this.parseCustomMetadata(fields.custom_metadata)
861
- };
862
- }
863
-
864
- private parseCustomMetadata(customMetadataVec: any[]): Record<string, string> {
865
- const result: Record<string, string> = {};
866
-
867
- if (Array.isArray(customMetadataVec)) {
868
- for (const item of customMetadataVec) {
869
- if (item.key && item.value) {
870
- result[item.key] = item.value;
871
- }
872
- }
873
- }
874
-
875
- return result;
876
- }
877
-
878
- private updateGasStats(gasUsed: number): void {
879
- this.stats.totalGasCost += gasUsed;
880
- this.stats.averageGasUsed = this.stats.totalGasCost / this.stats.successfulTransactions;
881
- }
882
-
883
- private generateTransactionId(): string {
884
- return `tx_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
885
- }
886
- }
887
-
888
- export default SuiService;
1
+ /**
2
+ * SuiService - Blockchain Integration for Memory Records
3
+ *
4
+ * Comprehensive Sui blockchain integration for memory ownership records,
5
+ * transaction batching, and decentralized metadata management.
6
+ */
7
+
8
+ import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
9
+ import { Transaction } from '@mysten/sui/transactions';
10
+ import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
11
+ import { fromB64, toB64 } from '@mysten/bcs';
12
+
13
+ export interface SuiConfig {
14
+ network?: 'testnet' | 'mainnet' | 'devnet' | 'localnet';
15
+ packageId?: string;
16
+ adminPrivateKey?: string;
17
+ rpcUrl?: string;
18
+ enableBatching?: boolean;
19
+ batchSize?: number;
20
+ batchDelayMs?: number;
21
+ gasObjectId?: string;
22
+ }
23
+
24
+ export interface MemoryRecord {
25
+ id: string;
26
+ owner: string;
27
+ category: string;
28
+ vectorId: number;
29
+ blobId: string;
30
+ metadata: MemoryMetadata;
31
+ createdAt: Date;
32
+ version: number;
33
+ }
34
+
35
+ export interface MemoryIndex {
36
+ id: string;
37
+ owner: string;
38
+ version: number;
39
+ indexBlobId: string;
40
+ graphBlobId: string;
41
+ lastUpdated: Date;
42
+ }
43
+
44
+ export interface MemoryMetadata {
45
+ contentType: string;
46
+ contentSize: number;
47
+ contentHash: string;
48
+ category: string;
49
+ topic: string;
50
+ importance: number; // 1-10 scale
51
+ embeddingBlobId: string;
52
+ embeddingDimension: number;
53
+ createdTimestamp: number;
54
+ updatedTimestamp: number;
55
+ customMetadata: Record<string, string>;
56
+ }
57
+
58
+ export interface TransactionResult {
59
+ digest: string;
60
+ objectId?: string;
61
+ effects?: any;
62
+ events?: any[];
63
+ success: boolean;
64
+ error?: string;
65
+ gasUsed?: number;
66
+ }
67
+
68
+ export interface BatchTransaction {
69
+ id: string;
70
+ userId: string;
71
+ operation: 'create_memory' | 'create_index' | 'update_index';
72
+ parameters: any;
73
+ priority: number;
74
+ timestamp: Date;
75
+ }
76
+
77
+ export interface SuiStats {
78
+ totalTransactions: number;
79
+ successfulTransactions: number;
80
+ failedTransactions: number;
81
+ averageGasUsed: number;
82
+ batchedTransactions: number;
83
+ totalGasCost: number;
84
+ networkHealth: 'healthy' | 'degraded' | 'offline';
85
+ lastSuccessfulTransaction?: Date;
86
+ }
87
+
88
+ /**
89
+ * Sui blockchain service for memory ownership and metadata management
90
+ */
91
+ export class SuiService {
92
+ private client!: SuiClient;
93
+ private adminKeypair?: Ed25519Keypair;
94
+ private readonly config: Required<SuiConfig>;
95
+ private batchQueue: BatchTransaction[] = [];
96
+ private batchTimer?: NodeJS.Timeout;
97
+ private pendingTransactions = new Map<string, Promise<TransactionResult>>();
98
+
99
+ private stats: SuiStats = {
100
+ totalTransactions: 0,
101
+ successfulTransactions: 0,
102
+ failedTransactions: 0,
103
+ averageGasUsed: 0,
104
+ batchedTransactions: 0,
105
+ totalGasCost: 0,
106
+ networkHealth: 'healthy'
107
+ };
108
+
109
+ constructor(config: Partial<SuiConfig> = {}) {
110
+ this.config = {
111
+ network: config.network || 'testnet',
112
+ packageId: config.packageId || '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
113
+ adminPrivateKey: config.adminPrivateKey || '',
114
+ rpcUrl: config.rpcUrl || '',
115
+ enableBatching: config.enableBatching !== false,
116
+ batchSize: config.batchSize || 10,
117
+ batchDelayMs: config.batchDelayMs || 5000,
118
+ gasObjectId: config.gasObjectId || ''
119
+ };
120
+
121
+ this.initializeSuiClient();
122
+ this.initializeAdminKeypair();
123
+ }
124
+
125
+ // ==================== MEMORY RECORD OPERATIONS ====================
126
+
127
+ /**
128
+ * Create memory record on Sui blockchain
129
+ */
130
+ async createMemoryRecord(
131
+ userAddress: string,
132
+ category: string,
133
+ vectorId: number,
134
+ blobId: string,
135
+ metadata: MemoryMetadata,
136
+ options: {
137
+ enableBatching?: boolean;
138
+ priority?: number;
139
+ } = {}
140
+ ): Promise<TransactionResult> {
141
+ if (options.enableBatching && this.config.enableBatching) {
142
+ return this.addToBatch({
143
+ id: this.generateTransactionId(),
144
+ userId: userAddress,
145
+ operation: 'create_memory',
146
+ parameters: { category, vectorId, blobId, metadata },
147
+ priority: options.priority || 1,
148
+ timestamp: new Date()
149
+ });
150
+ }
151
+
152
+ return await this.executeCreateMemoryRecord(userAddress, category, vectorId, blobId, metadata);
153
+ }
154
+
155
+ /**
156
+ * Create memory index on Sui blockchain
157
+ */
158
+ async createMemoryIndex(
159
+ userAddress: string,
160
+ indexBlobId: string,
161
+ graphBlobId: string,
162
+ options: {
163
+ enableBatching?: boolean;
164
+ priority?: number;
165
+ } = {}
166
+ ): Promise<TransactionResult> {
167
+ if (options.enableBatching && this.config.enableBatching) {
168
+ return this.addToBatch({
169
+ id: this.generateTransactionId(),
170
+ userId: userAddress,
171
+ operation: 'create_index',
172
+ parameters: { indexBlobId, graphBlobId },
173
+ priority: options.priority || 1,
174
+ timestamp: new Date()
175
+ });
176
+ }
177
+
178
+ return await this.executeCreateMemoryIndex(userAddress, indexBlobId, graphBlobId);
179
+ }
180
+
181
+ /**
182
+ * Update memory index on Sui blockchain
183
+ */
184
+ async updateMemoryIndex(
185
+ indexId: string,
186
+ userAddress: string,
187
+ expectedVersion: number,
188
+ newIndexBlobId: string,
189
+ newGraphBlobId: string,
190
+ options: {
191
+ enableBatching?: boolean;
192
+ priority?: number;
193
+ } = {}
194
+ ): Promise<TransactionResult> {
195
+ if (options.enableBatching && this.config.enableBatching) {
196
+ return this.addToBatch({
197
+ id: this.generateTransactionId(),
198
+ userId: userAddress,
199
+ operation: 'update_index',
200
+ parameters: { indexId, expectedVersion, newIndexBlobId, newGraphBlobId },
201
+ priority: options.priority || 1,
202
+ timestamp: new Date()
203
+ });
204
+ }
205
+
206
+ return await this.executeUpdateMemoryIndex(
207
+ indexId,
208
+ userAddress,
209
+ expectedVersion,
210
+ newIndexBlobId,
211
+ newGraphBlobId
212
+ );
213
+ }
214
+
215
+ /**
216
+ * Get memory record by ID
217
+ */
218
+ async getMemoryRecord(objectId: string): Promise<MemoryRecord | null> {
219
+ try {
220
+ const response = await this.client.getObject({
221
+ id: objectId,
222
+ options: {
223
+ showContent: true,
224
+ showOwner: true,
225
+ showType: true
226
+ }
227
+ });
228
+
229
+ if (!response.data) {
230
+ return null;
231
+ }
232
+
233
+ const content = response.data.content as any;
234
+ if (!content || content.dataType !== 'moveObject') {
235
+ return null;
236
+ }
237
+
238
+ const fields = content.fields;
239
+ return {
240
+ id: objectId,
241
+ owner: fields.owner,
242
+ category: fields.category,
243
+ vectorId: parseInt(fields.vector_id),
244
+ blobId: fields.blob_id,
245
+ metadata: this.parseMetadata(fields.metadata),
246
+ createdAt: new Date(parseInt(fields.metadata.created_timestamp)),
247
+ version: fields.version || 1
248
+ };
249
+
250
+ } catch (error) {
251
+ console.error('Error getting memory record:', error);
252
+ return null;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Get memory index by ID
258
+ */
259
+ async getMemoryIndex(indexId: string): Promise<MemoryIndex | null> {
260
+ try {
261
+ const response = await this.client.getObject({
262
+ id: indexId,
263
+ options: {
264
+ showContent: true,
265
+ showOwner: true
266
+ }
267
+ });
268
+
269
+ if (!response.data) {
270
+ return null;
271
+ }
272
+
273
+ const content = response.data.content as any;
274
+ if (!content || content.dataType !== 'moveObject') {
275
+ return null;
276
+ }
277
+
278
+ const fields = content.fields;
279
+ return {
280
+ id: indexId,
281
+ owner: fields.owner,
282
+ version: parseInt(fields.version),
283
+ indexBlobId: fields.index_blob_id,
284
+ graphBlobId: fields.graph_blob_id,
285
+ lastUpdated: new Date() // TODO: Get from blockchain timestamp
286
+ };
287
+
288
+ } catch (error) {
289
+ console.error('Error getting memory index:', error);
290
+ return null;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Get user's memory records
296
+ */
297
+ async getUserMemoryRecords(userAddress: string, limit: number = 100): Promise<MemoryRecord[]> {
298
+ try {
299
+ const response = await this.client.getOwnedObjects({
300
+ owner: userAddress,
301
+ filter: {
302
+ StructType: `${this.config.packageId}::memory::Memory`
303
+ },
304
+ options: {
305
+ showContent: true,
306
+ showType: true
307
+ },
308
+ limit
309
+ });
310
+
311
+ const memoryRecords: MemoryRecord[] = [];
312
+
313
+ for (const item of response.data) {
314
+ if (item.data && item.data.content) {
315
+ const content = item.data.content as any;
316
+ if (content.dataType === 'moveObject' && content.fields) {
317
+ const fields = content.fields;
318
+ memoryRecords.push({
319
+ id: item.data.objectId,
320
+ owner: fields.owner,
321
+ category: fields.category,
322
+ vectorId: parseInt(fields.vector_id),
323
+ blobId: fields.blob_id,
324
+ metadata: this.parseMetadata(fields.metadata),
325
+ createdAt: new Date(parseInt(fields.metadata.created_timestamp)),
326
+ version: fields.version || 1
327
+ });
328
+ }
329
+ }
330
+ }
331
+
332
+ return memoryRecords;
333
+
334
+ } catch (error) {
335
+ console.error('Error getting user memory records:', error);
336
+ return [];
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Get user's memory indices
342
+ */
343
+ async getUserMemoryIndices(userAddress: string): Promise<MemoryIndex[]> {
344
+ try {
345
+ const response = await this.client.getOwnedObjects({
346
+ owner: userAddress,
347
+ filter: {
348
+ StructType: `${this.config.packageId}::memory::MemoryIndex`
349
+ },
350
+ options: {
351
+ showContent: true,
352
+ showType: true
353
+ }
354
+ });
355
+
356
+ const memoryIndices: MemoryIndex[] = [];
357
+
358
+ for (const item of response.data) {
359
+ if (item.data && item.data.content) {
360
+ const content = item.data.content as any;
361
+ if (content.dataType === 'moveObject' && content.fields) {
362
+ const fields = content.fields;
363
+ memoryIndices.push({
364
+ id: item.data.objectId,
365
+ owner: fields.owner,
366
+ version: parseInt(fields.version),
367
+ indexBlobId: fields.index_blob_id,
368
+ graphBlobId: fields.graph_blob_id,
369
+ lastUpdated: new Date() // TODO: Get from blockchain events
370
+ });
371
+ }
372
+ }
373
+ }
374
+
375
+ return memoryIndices;
376
+
377
+ } catch (error) {
378
+ console.error('Error getting user memory indices:', error);
379
+ return [];
380
+ }
381
+ }
382
+
383
+ // ==================== BATCH OPERATIONS ====================
384
+
385
+ /**
386
+ * Process pending batch transactions
387
+ */
388
+ async processBatchQueue(): Promise<TransactionResult[]> {
389
+ if (this.batchQueue.length === 0) {
390
+ return [];
391
+ }
392
+
393
+ const batch = [...this.batchQueue];
394
+ this.batchQueue.length = 0; // Clear queue
395
+
396
+ if (this.batchTimer) {
397
+ clearTimeout(this.batchTimer);
398
+ this.batchTimer = undefined;
399
+ }
400
+
401
+ console.log(`Processing batch of ${batch.length} transactions`);
402
+
403
+ // Sort by priority (higher priority first)
404
+ batch.sort((a, b) => b.priority - a.priority);
405
+
406
+ const results: TransactionResult[] = [];
407
+
408
+ // Execute transactions in sequence to avoid nonce conflicts
409
+ for (const transaction of batch) {
410
+ try {
411
+ let result: TransactionResult;
412
+
413
+ switch (transaction.operation) {
414
+ case 'create_memory':
415
+ const { category, vectorId, blobId, metadata } = transaction.parameters;
416
+ result = await this.executeCreateMemoryRecord(
417
+ transaction.userId, category, vectorId, blobId, metadata
418
+ );
419
+ break;
420
+
421
+ case 'create_index':
422
+ const { indexBlobId, graphBlobId } = transaction.parameters;
423
+ result = await this.executeCreateMemoryIndex(
424
+ transaction.userId, indexBlobId, graphBlobId
425
+ );
426
+ break;
427
+
428
+ case 'update_index':
429
+ const { indexId, expectedVersion, newIndexBlobId, newGraphBlobId } = transaction.parameters;
430
+ result = await this.executeUpdateMemoryIndex(
431
+ indexId, transaction.userId, expectedVersion, newIndexBlobId, newGraphBlobId
432
+ );
433
+ break;
434
+
435
+ default:
436
+ result = {
437
+ digest: '',
438
+ success: false,
439
+ error: `Unknown operation: ${transaction.operation}`
440
+ };
441
+ }
442
+
443
+ results.push(result);
444
+ this.stats.batchedTransactions++;
445
+
446
+ } catch (error) {
447
+ results.push({
448
+ digest: '',
449
+ success: false,
450
+ error: error instanceof Error ? error.message : 'Unknown error'
451
+ });
452
+ }
453
+ }
454
+
455
+ console.log(`Batch processing complete: ${results.filter(r => r.success).length}/${results.length} successful`);
456
+ return results;
457
+ }
458
+
459
+ /**
460
+ * Force process batch queue immediately
461
+ */
462
+ async flushBatchQueue(): Promise<TransactionResult[]> {
463
+ return await this.processBatchQueue();
464
+ }
465
+
466
+ // ==================== NETWORK OPERATIONS ====================
467
+
468
+ /**
469
+ * Check network health
470
+ */
471
+ async checkNetworkHealth(): Promise<'healthy' | 'degraded' | 'offline'> {
472
+ try {
473
+ const start = Date.now();
474
+ const response = await this.client.getLatestCheckpointSequenceNumber();
475
+ const latency = Date.now() - start;
476
+
477
+ if (response && latency < 5000) {
478
+ this.stats.networkHealth = 'healthy';
479
+ } else {
480
+ this.stats.networkHealth = 'degraded';
481
+ }
482
+
483
+ } catch (error) {
484
+ this.stats.networkHealth = 'offline';
485
+ }
486
+
487
+ return this.stats.networkHealth;
488
+ }
489
+
490
+ /**
491
+ * Get gas price recommendations
492
+ */
493
+ async getGasPrice(): Promise<{ referenceGasPrice: number; recommendation: string }> {
494
+ try {
495
+ const gasPrice = await this.client.getReferenceGasPrice();
496
+
497
+ return {
498
+ referenceGasPrice: parseInt(gasPrice.toString()),
499
+ recommendation: parseInt(gasPrice.toString()) > 1000 ? 'high' : 'normal'
500
+ };
501
+
502
+ } catch (error) {
503
+ console.error('Error getting gas price:', error);
504
+ return {
505
+ referenceGasPrice: 1000,
506
+ recommendation: 'normal'
507
+ };
508
+ }
509
+ }
510
+
511
+ /**
512
+ * Get transaction by digest
513
+ */
514
+ async getTransaction(digest: string) {
515
+ try {
516
+ return await this.client.getTransactionBlock({
517
+ digest,
518
+ options: {
519
+ showEffects: true,
520
+ showEvents: true,
521
+ showInput: true,
522
+ showObjectChanges: true
523
+ }
524
+ });
525
+ } catch (error) {
526
+ console.error('Error getting transaction:', error);
527
+ return null;
528
+ }
529
+ }
530
+
531
+ // ==================== STATISTICS & MONITORING ====================
532
+
533
+ /**
534
+ * Get service statistics
535
+ */
536
+ getStats(): SuiStats {
537
+ return { ...this.stats };
538
+ }
539
+
540
+ /**
541
+ * Get batch queue status
542
+ */
543
+ getBatchQueueStatus(): {
544
+ pending: number;
545
+ nextProcessing: Date | null;
546
+ averageBatchSize: number;
547
+ } {
548
+ return {
549
+ pending: this.batchQueue.length,
550
+ nextProcessing: this.batchTimer ? new Date(Date.now() + this.config.batchDelayMs) : null,
551
+ averageBatchSize: this.stats.batchedTransactions > 0
552
+ ? this.config.batchSize
553
+ : 0
554
+ };
555
+ }
556
+
557
+ /**
558
+ * Reset statistics
559
+ */
560
+ resetStats(): void {
561
+ this.stats = {
562
+ totalTransactions: 0,
563
+ successfulTransactions: 0,
564
+ failedTransactions: 0,
565
+ averageGasUsed: 0,
566
+ batchedTransactions: 0,
567
+ totalGasCost: 0,
568
+ networkHealth: 'healthy'
569
+ };
570
+ }
571
+
572
+ // ==================== PRIVATE METHODS ====================
573
+
574
+ private initializeSuiClient(): void {
575
+ try {
576
+ const networkUrl = this.config.rpcUrl || getFullnodeUrl(this.config.network);
577
+ this.client = new SuiClient({ url: networkUrl });
578
+ console.log(`Sui client initialized for ${this.config.network} network`);
579
+ } catch (error) {
580
+ console.error('Failed to initialize Sui client:', error);
581
+ throw new Error(`Sui client initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
582
+ }
583
+ }
584
+
585
+ private initializeAdminKeypair(): void {
586
+ if (!this.config.adminPrivateKey) {
587
+ console.warn('No admin private key provided. Creating random keypair for development.');
588
+ this.adminKeypair = new Ed25519Keypair();
589
+ return;
590
+ }
591
+
592
+ try {
593
+ // Clean up the private key
594
+ let privateKey = this.config.adminPrivateKey.replace(/\s+/g, '');
595
+
596
+ if (privateKey.startsWith('suiprivkey1')) {
597
+ // Sui private key format
598
+ this.adminKeypair = Ed25519Keypair.fromSecretKey(privateKey);
599
+ } else {
600
+ // Hex format
601
+ if (!privateKey.startsWith('0x')) {
602
+ privateKey = '0x' + privateKey;
603
+ }
604
+
605
+ const keyBuffer = Buffer.from(privateKey.replace('0x', ''), 'hex');
606
+ if (keyBuffer.length !== 32) {
607
+ throw new Error(`Invalid key length: ${keyBuffer.length}, expected 32`);
608
+ }
609
+
610
+ this.adminKeypair = Ed25519Keypair.fromSecretKey(new Uint8Array(keyBuffer));
611
+ }
612
+
613
+ const adminAddress = this.adminKeypair.getPublicKey().toSuiAddress();
614
+ console.log(`Admin keypair initialized with address: ${adminAddress}`);
615
+
616
+ } catch (error) {
617
+ console.error('Failed to initialize admin keypair:', error);
618
+ console.warn('Using random keypair for development');
619
+ this.adminKeypair = new Ed25519Keypair();
620
+ }
621
+ }
622
+
623
+ private async executeCreateMemoryRecord(
624
+ userAddress: string,
625
+ category: string,
626
+ vectorId: number,
627
+ blobId: string,
628
+ metadata: MemoryMetadata
629
+ ): Promise<TransactionResult> {
630
+ try {
631
+ const tx = new Transaction();
632
+
633
+ // Convert metadata to Move-compatible format
634
+ const metadataFields = this.serializeMetadata(metadata);
635
+
636
+ tx.moveCall({
637
+ target: `${this.config.packageId}::memory::create_memory_record`,
638
+ arguments: [
639
+ tx.pure.string(category),
640
+ tx.pure.u64(vectorId),
641
+ tx.pure.string(blobId),
642
+ tx.pure(metadataFields) // Serialized metadata
643
+ ]
644
+ });
645
+
646
+ return await this.executeTransaction(tx, userAddress);
647
+
648
+ } catch (error) {
649
+ console.error('Error creating memory record:', error);
650
+ return {
651
+ digest: '',
652
+ success: false,
653
+ error: (error instanceof Error ? error.message : 'Unknown error')
654
+ };
655
+ }
656
+ }
657
+
658
+ private async executeCreateMemoryIndex(
659
+ userAddress: string,
660
+ indexBlobId: string,
661
+ graphBlobId: string
662
+ ): Promise<TransactionResult> {
663
+ try {
664
+ const tx = new Transaction();
665
+
666
+ tx.moveCall({
667
+ target: `${this.config.packageId}::memory::create_memory_index`,
668
+ arguments: [
669
+ tx.pure(new TextEncoder().encode(indexBlobId)),
670
+ tx.pure(new TextEncoder().encode(graphBlobId))
671
+ ]
672
+ });
673
+
674
+ return await this.executeTransaction(tx, userAddress);
675
+
676
+ } catch (error) {
677
+ console.error('Error creating memory index:', error);
678
+ return {
679
+ digest: '',
680
+ success: false,
681
+ error: (error instanceof Error ? error.message : 'Unknown error')
682
+ };
683
+ }
684
+ }
685
+
686
+ private async executeUpdateMemoryIndex(
687
+ indexId: string,
688
+ userAddress: string,
689
+ expectedVersion: number,
690
+ newIndexBlobId: string,
691
+ newGraphBlobId: string
692
+ ): Promise<TransactionResult> {
693
+ try {
694
+ const tx = new Transaction();
695
+
696
+ tx.moveCall({
697
+ target: `${this.config.packageId}::memory::update_memory_index`,
698
+ arguments: [
699
+ tx.object(indexId),
700
+ tx.pure.u64(expectedVersion),
701
+ tx.pure.string(newIndexBlobId),
702
+ tx.pure.string(newGraphBlobId)
703
+ ]
704
+ });
705
+
706
+ return await this.executeTransaction(tx, userAddress);
707
+
708
+ } catch (error) {
709
+ console.error('Error updating memory index:', error);
710
+ return {
711
+ digest: '',
712
+ success: false,
713
+ error: (error instanceof Error ? error.message : 'Unknown error')
714
+ };
715
+ }
716
+ }
717
+
718
+ private async executeTransaction(tx: Transaction, signer?: string): Promise<TransactionResult> {
719
+ const startTime = Date.now();
720
+ this.stats.totalTransactions++;
721
+
722
+ try {
723
+ if (!this.adminKeypair) {
724
+ throw new Error('Admin keypair not initialized');
725
+ }
726
+
727
+ // Set gas payment
728
+ const coins = await this.client.getCoins({
729
+ owner: this.adminKeypair.getPublicKey().toSuiAddress(),
730
+ coinType: '0x2::sui::SUI'
731
+ });
732
+
733
+ if (coins.data.length === 0) {
734
+ throw new Error('No gas coins available');
735
+ }
736
+
737
+ tx.setGasPayment(coins.data.slice(0, 10).map(coin => ({
738
+ objectId: coin.coinObjectId,
739
+ version: coin.version,
740
+ digest: coin.digest
741
+ })));
742
+
743
+ // Execute transaction
744
+ const result = await this.client.signAndExecuteTransaction({
745
+ transaction: tx,
746
+ signer: this.adminKeypair,
747
+ options: {
748
+ showEffects: true,
749
+ showEvents: true,
750
+ showObjectChanges: true
751
+ }
752
+ });
753
+
754
+ // Wait for transaction to be finalized to prevent gas coin version conflicts
755
+ if (result.digest) {
756
+ await this.client.waitForTransaction({ digest: result.digest });
757
+ }
758
+
759
+ // Update statistics
760
+ const gasUsed = result.effects?.gasUsed?.computationCost || 0;
761
+ this.updateGasStats(typeof gasUsed === 'string' ? parseInt(gasUsed) : gasUsed);
762
+ this.stats.successfulTransactions++;
763
+ this.stats.lastSuccessfulTransaction = new Date();
764
+
765
+ // Extract created object ID if any
766
+ const objectId = this.extractCreatedObjectId(result);
767
+
768
+ return {
769
+ digest: result.digest,
770
+ objectId,
771
+ effects: result.effects,
772
+ events: result.events || [],
773
+ success: true,
774
+ gasUsed: typeof gasUsed === 'string' ? parseInt(gasUsed) : gasUsed
775
+ };
776
+
777
+ } catch (error) {
778
+ this.stats.failedTransactions++;
779
+ console.error('Transaction execution failed:', error);
780
+
781
+ return {
782
+ digest: '',
783
+ success: false,
784
+ error: (error instanceof Error ? error.message : 'Unknown error')
785
+ };
786
+ }
787
+ }
788
+
789
+ private extractCreatedObjectId(result: any): string | undefined {
790
+ if (result.objectChanges) {
791
+ for (const change of result.objectChanges) {
792
+ if (change.type === 'created') {
793
+ return change.objectId;
794
+ }
795
+ }
796
+ }
797
+ return undefined;
798
+ }
799
+
800
+ private addToBatch(transaction: BatchTransaction): Promise<TransactionResult> {
801
+ this.batchQueue.push(transaction);
802
+ this.scheduleBatchProcessing();
803
+
804
+ // Return a promise that resolves when batch is processed
805
+ return new Promise((resolve) => {
806
+ // Simple implementation - in production, track individual transaction results
807
+ setTimeout(() => {
808
+ resolve({
809
+ digest: 'batched',
810
+ success: true
811
+ });
812
+ }, this.config.batchDelayMs + 1000);
813
+ });
814
+ }
815
+
816
+ private scheduleBatchProcessing(): void {
817
+ if (this.batchTimer) {
818
+ return; // Already scheduled
819
+ }
820
+
821
+ // Process when batch is full or after delay
822
+ if (this.batchQueue.length >= this.config.batchSize) {
823
+ setImmediate(() => this.processBatchQueue());
824
+ } else {
825
+ this.batchTimer = setTimeout(() => {
826
+ this.processBatchQueue();
827
+ }, this.config.batchDelayMs);
828
+ }
829
+ }
830
+
831
+ private serializeMetadata(metadata: MemoryMetadata): any {
832
+ // Convert to Move-compatible format
833
+ return {
834
+ content_type: metadata.contentType,
835
+ content_size: metadata.contentSize,
836
+ content_hash: metadata.contentHash,
837
+ category: metadata.category,
838
+ topic: metadata.topic,
839
+ importance: Math.max(1, Math.min(10, metadata.importance)),
840
+ embedding_blob_id: metadata.embeddingBlobId,
841
+ embedding_dimension: metadata.embeddingDimension,
842
+ created_timestamp: metadata.createdTimestamp,
843
+ updated_timestamp: metadata.updatedTimestamp || metadata.createdTimestamp,
844
+ custom_metadata: Object.entries(metadata.customMetadata).map(([key, value]) => ({ key, value }))
845
+ };
846
+ }
847
+
848
+ private parseMetadata(fields: any): MemoryMetadata {
849
+ return {
850
+ contentType: fields.content_type,
851
+ contentSize: parseInt(fields.content_size),
852
+ contentHash: fields.content_hash,
853
+ category: fields.category,
854
+ topic: fields.topic,
855
+ importance: parseInt(fields.importance),
856
+ embeddingBlobId: fields.embedding_blob_id,
857
+ embeddingDimension: parseInt(fields.embedding_dimension),
858
+ createdTimestamp: parseInt(fields.created_timestamp),
859
+ updatedTimestamp: parseInt(fields.updated_timestamp),
860
+ customMetadata: this.parseCustomMetadata(fields.custom_metadata)
861
+ };
862
+ }
863
+
864
+ private parseCustomMetadata(customMetadataVec: any[]): Record<string, string> {
865
+ const result: Record<string, string> = {};
866
+
867
+ if (Array.isArray(customMetadataVec)) {
868
+ for (const item of customMetadataVec) {
869
+ if (item.key && item.value) {
870
+ result[item.key] = item.value;
871
+ }
872
+ }
873
+ }
874
+
875
+ return result;
876
+ }
877
+
878
+ private updateGasStats(gasUsed: number): void {
879
+ this.stats.totalGasCost += gasUsed;
880
+ this.stats.averageGasUsed = this.stats.totalGasCost / this.stats.successfulTransactions;
881
+ }
882
+
883
+ private generateTransactionId(): string {
884
+ return `tx_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
885
+ }
886
+ }
887
+
888
+ export default SuiService;