@cmdoss/memwal-sdk 0.6.2 → 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 (247) hide show
  1. package/ARCHITECTURE.md +547 -547
  2. package/BENCHMARKS.md +238 -238
  3. package/README.md +310 -181
  4. package/dist/ai-sdk/tools.d.ts +2 -2
  5. package/dist/ai-sdk/tools.js +2 -2
  6. package/dist/client/ClientMemoryManager.js +2 -2
  7. package/dist/client/ClientMemoryManager.js.map +1 -1
  8. package/dist/client/PersonalDataWallet.d.ts.map +1 -1
  9. package/dist/client/SimplePDWClient.d.ts +29 -1
  10. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  11. package/dist/client/SimplePDWClient.js +45 -13
  12. package/dist/client/SimplePDWClient.js.map +1 -1
  13. package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
  14. package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
  15. package/dist/client/namespaces/MemoryNamespace.d.ts +31 -0
  16. package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
  17. package/dist/client/namespaces/MemoryNamespace.js +272 -39
  18. package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
  19. package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
  20. package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
  21. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts +12 -2
  22. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
  23. package/dist/client/namespaces/consolidated/BlockchainNamespace.js +62 -4
  24. package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
  25. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
  26. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
  27. package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
  28. package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
  29. package/dist/config/ConfigurationHelper.js +61 -61
  30. package/dist/config/defaults.js +2 -2
  31. package/dist/config/defaults.js.map +1 -1
  32. package/dist/graph/GraphService.js +21 -21
  33. package/dist/graph/GraphService.js.map +1 -1
  34. package/dist/index.d.ts +3 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +3 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/infrastructure/seal/EncryptionService.d.ts +9 -5
  39. package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
  40. package/dist/infrastructure/seal/EncryptionService.js +37 -15
  41. package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
  42. package/dist/infrastructure/seal/SealService.d.ts +13 -5
  43. package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
  44. package/dist/infrastructure/seal/SealService.js +36 -34
  45. package/dist/infrastructure/seal/SealService.js.map +1 -1
  46. package/dist/langchain/createPDWRAG.js +30 -30
  47. package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
  48. package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
  49. package/dist/retrieval/MemoryDecryptionPipeline.js.map +1 -1
  50. package/dist/retrieval/MemoryRetrievalService.d.ts +31 -0
  51. package/dist/retrieval/MemoryRetrievalService.d.ts.map +1 -1
  52. package/dist/retrieval/MemoryRetrievalService.js +44 -4
  53. package/dist/retrieval/MemoryRetrievalService.js.map +1 -1
  54. package/dist/services/CapabilityService.d.ts.map +1 -1
  55. package/dist/services/CapabilityService.js +30 -14
  56. package/dist/services/CapabilityService.js.map +1 -1
  57. package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
  58. package/dist/services/CrossContextPermissionService.js +9 -7
  59. package/dist/services/CrossContextPermissionService.js.map +1 -1
  60. package/dist/services/EmbeddingService.d.ts +28 -1
  61. package/dist/services/EmbeddingService.d.ts.map +1 -1
  62. package/dist/services/EmbeddingService.js +54 -0
  63. package/dist/services/EmbeddingService.js.map +1 -1
  64. package/dist/services/EncryptionService.d.ts.map +1 -1
  65. package/dist/services/EncryptionService.js +6 -5
  66. package/dist/services/EncryptionService.js.map +1 -1
  67. package/dist/services/GeminiAIService.js +309 -309
  68. package/dist/services/IndexManager.d.ts +5 -1
  69. package/dist/services/IndexManager.d.ts.map +1 -1
  70. package/dist/services/IndexManager.js +17 -40
  71. package/dist/services/IndexManager.js.map +1 -1
  72. package/dist/services/QueryService.js +1 -1
  73. package/dist/services/QueryService.js.map +1 -1
  74. package/dist/services/StorageService.d.ts +11 -0
  75. package/dist/services/StorageService.d.ts.map +1 -1
  76. package/dist/services/StorageService.js +73 -10
  77. package/dist/services/StorageService.js.map +1 -1
  78. package/dist/services/TransactionService.d.ts +20 -0
  79. package/dist/services/TransactionService.d.ts.map +1 -1
  80. package/dist/services/TransactionService.js +43 -0
  81. package/dist/services/TransactionService.js.map +1 -1
  82. package/dist/services/ViewService.js +2 -2
  83. package/dist/services/ViewService.js.map +1 -1
  84. package/dist/services/storage/QuiltBatchManager.d.ts +101 -1
  85. package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
  86. package/dist/services/storage/QuiltBatchManager.js +410 -20
  87. package/dist/services/storage/QuiltBatchManager.js.map +1 -1
  88. package/dist/services/storage/index.d.ts +1 -1
  89. package/dist/services/storage/index.d.ts.map +1 -1
  90. package/dist/services/storage/index.js.map +1 -1
  91. package/dist/utils/LRUCache.d.ts +106 -0
  92. package/dist/utils/LRUCache.d.ts.map +1 -0
  93. package/dist/utils/LRUCache.js +281 -0
  94. package/dist/utils/LRUCache.js.map +1 -0
  95. package/dist/utils/index.d.ts +1 -0
  96. package/dist/utils/index.d.ts.map +1 -1
  97. package/dist/utils/index.js +2 -0
  98. package/dist/utils/index.js.map +1 -1
  99. package/dist/utils/memoryIndexOnChain.d.ts +212 -0
  100. package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
  101. package/dist/utils/memoryIndexOnChain.js +312 -0
  102. package/dist/utils/memoryIndexOnChain.js.map +1 -0
  103. package/dist/utils/rebuildIndexNode.d.ts +29 -0
  104. package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
  105. package/dist/utils/rebuildIndexNode.js +366 -98
  106. package/dist/utils/rebuildIndexNode.js.map +1 -1
  107. package/dist/vector/HnswWasmService.d.ts +20 -5
  108. package/dist/vector/HnswWasmService.d.ts.map +1 -1
  109. package/dist/vector/HnswWasmService.js +73 -40
  110. package/dist/vector/HnswWasmService.js.map +1 -1
  111. package/dist/vector/IHnswService.d.ts +10 -1
  112. package/dist/vector/IHnswService.d.ts.map +1 -1
  113. package/dist/vector/IHnswService.js.map +1 -1
  114. package/dist/vector/NodeHnswService.d.ts +16 -0
  115. package/dist/vector/NodeHnswService.d.ts.map +1 -1
  116. package/dist/vector/NodeHnswService.js +84 -5
  117. package/dist/vector/NodeHnswService.js.map +1 -1
  118. package/dist/vector/createHnswService.d.ts +1 -1
  119. package/dist/vector/createHnswService.js +1 -1
  120. package/dist/vector/index.d.ts +1 -1
  121. package/dist/vector/index.js +1 -1
  122. package/package.json +157 -157
  123. package/src/access/PermissionService.ts +635 -635
  124. package/src/aggregation/AggregationService.ts +389 -389
  125. package/src/ai-sdk/PDWVectorStore.ts +715 -715
  126. package/src/ai-sdk/index.ts +65 -65
  127. package/src/ai-sdk/tools.ts +460 -460
  128. package/src/ai-sdk/types.ts +404 -404
  129. package/src/batch/BatchManager.ts +597 -597
  130. package/src/batch/BatchingService.ts +429 -429
  131. package/src/batch/MemoryProcessingCache.ts +492 -492
  132. package/src/batch/index.ts +30 -30
  133. package/src/browser.ts +200 -200
  134. package/src/client/ClientMemoryManager.ts +987 -987
  135. package/src/client/PersonalDataWallet.ts +345 -345
  136. package/src/client/SimplePDWClient.ts +1289 -1222
  137. package/src/client/factory.ts +154 -154
  138. package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
  139. package/src/client/namespaces/BatchNamespace.ts +356 -356
  140. package/src/client/namespaces/CacheNamespace.ts +123 -123
  141. package/src/client/namespaces/CapabilityNamespace.ts +217 -217
  142. package/src/client/namespaces/ClassifyNamespace.ts +169 -169
  143. package/src/client/namespaces/ContextNamespace.ts +297 -297
  144. package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
  145. package/src/client/namespaces/EncryptionNamespace.ts +221 -221
  146. package/src/client/namespaces/GraphNamespace.ts +468 -468
  147. package/src/client/namespaces/IndexNamespace.ts +361 -361
  148. package/src/client/namespaces/MemoryNamespace.ts +1422 -1135
  149. package/src/client/namespaces/PermissionsNamespace.ts +254 -254
  150. package/src/client/namespaces/PipelineNamespace.ts +220 -220
  151. package/src/client/namespaces/SearchNamespace.ts +1049 -1049
  152. package/src/client/namespaces/StorageNamespace.ts +458 -458
  153. package/src/client/namespaces/TxNamespace.ts +260 -260
  154. package/src/client/namespaces/WalletNamespace.ts +243 -243
  155. package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
  156. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -546
  157. package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
  158. package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
  159. package/src/client/namespaces/consolidated/index.ts +39 -39
  160. package/src/client/signers/KeypairSigner.ts +108 -108
  161. package/src/client/signers/UnifiedSigner.ts +110 -110
  162. package/src/client/signers/WalletAdapterSigner.ts +159 -159
  163. package/src/client/signers/index.ts +26 -26
  164. package/src/config/ConfigurationHelper.ts +412 -412
  165. package/src/config/defaults.ts +51 -51
  166. package/src/config/index.ts +8 -8
  167. package/src/config/validation.ts +70 -70
  168. package/src/core/index.ts +14 -14
  169. package/src/core/interfaces/IService.ts +307 -307
  170. package/src/core/interfaces/index.ts +8 -8
  171. package/src/core/types/capability.ts +297 -297
  172. package/src/core/types/index.ts +870 -870
  173. package/src/core/types/wallet.ts +270 -270
  174. package/src/core/types.ts +9 -9
  175. package/src/core/wallet.ts +222 -222
  176. package/src/embedding/index.ts +19 -19
  177. package/src/embedding/types.ts +357 -357
  178. package/src/errors/index.ts +602 -602
  179. package/src/errors/recovery.ts +461 -461
  180. package/src/errors/validation.ts +567 -567
  181. package/src/generated/pdw/capability.ts +319 -319
  182. package/src/graph/GraphService.ts +887 -887
  183. package/src/graph/KnowledgeGraphManager.ts +728 -728
  184. package/src/graph/index.ts +25 -25
  185. package/src/index.ts +498 -474
  186. package/src/infrastructure/index.ts +22 -22
  187. package/src/infrastructure/seal/EncryptionService.ts +628 -603
  188. package/src/infrastructure/seal/SealService.ts +613 -615
  189. package/src/infrastructure/seal/index.ts +9 -9
  190. package/src/infrastructure/sui/BlockchainManager.ts +627 -627
  191. package/src/infrastructure/sui/SuiService.ts +888 -888
  192. package/src/infrastructure/sui/index.ts +9 -9
  193. package/src/infrastructure/walrus/StorageManager.ts +604 -604
  194. package/src/infrastructure/walrus/WalrusStorageService.ts +612 -612
  195. package/src/infrastructure/walrus/index.ts +9 -9
  196. package/src/langchain/PDWEmbeddings.ts +145 -145
  197. package/src/langchain/PDWVectorStore.ts +456 -456
  198. package/src/langchain/createPDWRAG.ts +303 -303
  199. package/src/langchain/index.ts +47 -47
  200. package/src/permissions/ConsentRepository.browser.ts +249 -249
  201. package/src/permissions/ConsentRepository.ts +364 -364
  202. package/src/pipeline/MemoryPipeline.ts +862 -862
  203. package/src/pipeline/PipelineManager.ts +683 -683
  204. package/src/pipeline/index.ts +26 -26
  205. package/src/retrieval/AdvancedSearchService.ts +629 -629
  206. package/src/retrieval/MemoryAnalyticsService.ts +711 -711
  207. package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
  208. package/src/retrieval/MemoryRetrievalService.ts +904 -830
  209. package/src/retrieval/index.ts +42 -42
  210. package/src/services/BatchService.ts +352 -352
  211. package/src/services/CapabilityService.ts +464 -448
  212. package/src/services/ClassifierService.ts +465 -465
  213. package/src/services/CrossContextPermissionService.ts +486 -484
  214. package/src/services/EmbeddingService.ts +771 -706
  215. package/src/services/EncryptionService.ts +712 -711
  216. package/src/services/GeminiAIService.ts +753 -753
  217. package/src/services/IndexManager.ts +977 -1004
  218. package/src/services/MemoryIndexService.ts +1003 -1003
  219. package/src/services/MemoryService.ts +369 -369
  220. package/src/services/QueryService.ts +890 -890
  221. package/src/services/StorageService.ts +1182 -1111
  222. package/src/services/TransactionService.ts +838 -790
  223. package/src/services/VectorService.ts +462 -462
  224. package/src/services/ViewService.ts +484 -484
  225. package/src/services/index.ts +25 -25
  226. package/src/services/storage/BlobAttributesManager.ts +333 -333
  227. package/src/services/storage/KnowledgeGraphManager.ts +425 -425
  228. package/src/services/storage/MemorySearchManager.ts +387 -387
  229. package/src/services/storage/QuiltBatchManager.ts +1130 -660
  230. package/src/services/storage/WalrusMetadataManager.ts +268 -268
  231. package/src/services/storage/WalrusStorageManager.ts +287 -287
  232. package/src/services/storage/index.ts +57 -52
  233. package/src/types/index.ts +13 -13
  234. package/src/utils/LRUCache.ts +378 -0
  235. package/src/utils/index.ts +76 -68
  236. package/src/utils/memoryIndexOnChain.ts +507 -0
  237. package/src/utils/rebuildIndex.ts +290 -290
  238. package/src/utils/rebuildIndexNode.ts +771 -424
  239. package/src/vector/BrowserHnswIndexService.ts +758 -758
  240. package/src/vector/HnswWasmService.ts +731 -679
  241. package/src/vector/IHnswService.ts +233 -224
  242. package/src/vector/NodeHnswService.ts +833 -735
  243. package/src/vector/VectorManager.ts +478 -478
  244. package/src/vector/createHnswService.ts +135 -135
  245. package/src/vector/index.ts +56 -56
  246. package/src/wallet/ContextWalletService.ts +656 -656
  247. package/src/wallet/MainWalletService.ts +317 -317
@@ -1,791 +1,839 @@
1
- import { Transaction, SerialTransactionExecutor } from '@mysten/sui/transactions';
2
- import { SuiClient } from '@mysten/sui/client';
3
- import {
4
- PDWConfig,
5
- TransactionOptions,
6
- TransactionResult,
7
- CreateMemoryRecordTxOptions,
8
- CreateMemoryRecordLightweightTxOptions,
9
- UpdateMemoryMetadataTxOptions,
10
- UpdateMemoryRecordTxOptions,
11
- DeleteMemoryRecordTxOptions,
12
- CreateMemoryIndexTxOptions,
13
- UpdateMemoryIndexTxOptions,
14
- GrantAccessTxOptions,
15
- RevokeAccessTxOptions,
16
- RegisterContentTxOptions,
17
- } from '../types';
18
-
19
- // Import generated Move contract functions
20
- import * as MemoryModule from '../generated/pdw/memory';
21
- import * as CapabilityModule from '../generated/pdw/capability';
22
-
23
- /**
24
- * TransactionService provides high-level transaction building and execution
25
- * for Personal Data Wallet Move contracts with proper error handling and gas management.
26
- *
27
- * Uses SerialTransactionExecutor to prevent gas coin version conflicts and equivocation
28
- * when multiple transactions are executed in sequence. The executor caches object versions
29
- * and manages gas coins automatically.
30
- *
31
- * @see https://sdk.mystenlabs.com/typescript/executors
32
- */
33
- export class TransactionService {
34
- private executor: SerialTransactionExecutor | null = null;
35
- private currentSigner: any = null;
36
-
37
- constructor(
38
- private client: SuiClient,
39
- private config: PDWConfig
40
- ) {}
41
-
42
- /**
43
- * Get or create SerialTransactionExecutor for the given signer.
44
- * The executor caches object versions and prevents equivocation.
45
- */
46
- private getExecutor(signer: any): SerialTransactionExecutor {
47
- // Create new executor if signer changed or doesn't exist
48
- if (!this.executor || this.currentSigner !== signer) {
49
- this.executor = new SerialTransactionExecutor({
50
- client: this.client,
51
- signer,
52
- });
53
- this.currentSigner = signer;
54
- }
55
- return this.executor;
56
- }
57
-
58
- /**
59
- * Reset the executor (useful when gas coin is exhausted or for cleanup)
60
- */
61
- resetExecutor(): void {
62
- this.executor = null;
63
- this.currentSigner = null;
64
- }
65
-
66
- // ==================== MEMORY TRANSACTIONS ====================
67
-
68
- /**
69
- * Build transaction to create a new memory record
70
- */
71
- buildCreateMemoryRecord(options: CreateMemoryRecordTxOptions): Transaction {
72
- const tx = new Transaction();
73
-
74
- // Set gas budget if provided
75
- if (options.gasBudget) {
76
- tx.setGasBudget(options.gasBudget);
77
- }
78
-
79
- // Set gas price if provided
80
- if (options.gasPrice) {
81
- tx.setGasPrice(options.gasPrice);
82
- }
83
-
84
- // Call the memory contract function with Clock for real-time timestamp
85
- tx.moveCall({
86
- target: `${this.config.packageId}::memory::create_memory_record`,
87
- arguments: [
88
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.category))),
89
- tx.pure.u64(options.vectorId),
90
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.blobId))),
91
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.contentType))),
92
- tx.pure.u64(options.contentSize),
93
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.contentHash))),
94
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.topic))),
95
- tx.pure.u8(options.importance),
96
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.embeddingBlobId))),
97
- tx.object('0x6'), // Sui Clock object for real-time timestamp
98
- ],
99
- });
100
-
101
- return tx;
102
- }
103
-
104
- /**
105
- * Build transaction to create a lightweight memory record
106
- *
107
- * This creates a minimal on-chain Memory struct with only essential queryable fields.
108
- * Rich metadata should be stored as Walrus blob metadata for gas efficiency.
109
- *
110
- * Use this when:
111
- * - Gas costs are a concern (saves ~50% gas vs full metadata)
112
- * - Rich metadata is stored on Walrus blob
113
- * - Only need basic filtering (category, vector_id, importance)
114
- *
115
- * @param options - Lightweight memory creation options
116
- * @returns Transaction to create lightweight memory record
117
- */
118
- buildCreateMemoryRecordLightweight(options: CreateMemoryRecordLightweightTxOptions): Transaction {
119
- const tx = new Transaction();
120
-
121
- // Set gas budget if provided
122
- if (options.gasBudget) {
123
- tx.setGasBudget(options.gasBudget);
124
- }
125
-
126
- // Set gas price if provided
127
- if (options.gasPrice) {
128
- tx.setGasPrice(options.gasPrice);
129
- }
130
-
131
- // Call the lightweight memory creation function
132
- // Note: Walrus blob_id is a base64 string (URL-safe, no padding)
133
- // Example: "E7_nNXvFU_3qZVu3OH1yycRG7LZlyn1-UxEDCDDqGGU"
134
- // We encode the string to vector<u8> for the Move function parameter
135
- // Clock object (0x6) is required for real-time timestamp
136
- tx.moveCall({
137
- target: `${this.config.packageId}::memory::create_memory_record_lightweight`,
138
- arguments: [
139
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.category))),
140
- tx.pure.u64(options.vectorId),
141
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.blobId))),
142
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.blobObjectId || ''))),
143
- tx.pure.u8(options.importance),
144
- tx.object('0x6'), // Sui Clock object for real-time timestamp
145
- ],
146
- });
147
-
148
- return tx;
149
- }
150
-
151
- /**
152
- * Build transaction to update memory metadata
153
- */
154
- buildUpdateMemoryMetadata(options: UpdateMemoryMetadataTxOptions): Transaction {
155
- const tx = new Transaction();
156
-
157
- if (options.gasBudget) {
158
- tx.setGasBudget(options.gasBudget);
159
- }
160
-
161
- if (options.gasPrice) {
162
- tx.setGasPrice(options.gasPrice);
163
- }
164
-
165
- // Note: Using tx.moveCall with Clock for real-time timestamp
166
- tx.moveCall({
167
- target: `${this.config.packageId}::memory::update_memory_metadata`,
168
- arguments: [
169
- tx.object(options.memoryId),
170
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.metadataBlobId))),
171
- tx.pure.u8(options.embeddingDimension || 5),
172
- tx.object('0x6'), // Sui Clock object for real-time timestamp
173
- ]
174
- });
175
-
176
- return tx;
177
- }
178
-
179
- /**
180
- * Build transaction for comprehensive memory record update
181
- *
182
- * Updates multiple fields of a Memory object in a single transaction.
183
- * Only non-empty/non-zero values will be updated on-chain.
184
- *
185
- * @param options - Update options with memoryId and fields to update
186
- * @returns Transaction to update memory record
187
- */
188
- buildUpdateMemoryRecord(options: UpdateMemoryRecordTxOptions): Transaction {
189
- const tx = new Transaction();
190
-
191
- if (options.gasBudget) {
192
- tx.setGasBudget(options.gasBudget);
193
- }
194
-
195
- if (options.gasPrice) {
196
- tx.setGasPrice(options.gasPrice);
197
- }
198
-
199
- // Use direct moveCall with Clock object for real-time timestamp
200
- tx.moveCall({
201
- target: `${this.config.packageId}::memory::update_memory_record`,
202
- arguments: [
203
- tx.object(options.memoryId),
204
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.newBlobId || ''))),
205
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.newCategory || ''))),
206
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.newTopic || ''))),
207
- tx.pure.u8(options.newImportance || 0),
208
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.newEmbeddingBlobId || ''))),
209
- tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.newContentHash || ''))),
210
- tx.pure.u64(options.newContentSize || 0),
211
- tx.object('0x6'), // Sui Clock object for real-time timestamp
212
- ],
213
- });
214
-
215
- return tx;
216
- }
217
-
218
- /**
219
- * Build transaction to delete a memory record
220
- */
221
- buildDeleteMemoryRecord(options: DeleteMemoryRecordTxOptions): Transaction {
222
- const tx = new Transaction();
223
-
224
- if (options.gasBudget) {
225
- tx.setGasBudget(options.gasBudget);
226
- }
227
-
228
- if (options.gasPrice) {
229
- tx.setGasPrice(options.gasPrice);
230
- }
231
-
232
- // Note: Using tx.moveCall for actual implementation
233
- tx.moveCall({
234
- target: `${this.config.packageId}::memory::delete_memory_record`,
235
- arguments: [tx.pure.string(options.memoryId)]
236
- });
237
-
238
- return tx;
239
- }
240
-
241
- /**
242
- * Build transaction to create a memory index
243
- *
244
- * Smart contract signature:
245
- * ```move
246
- * public entry fun create_memory_index(
247
- * index_blob_id: vector<u8>,
248
- * graph_blob_id: vector<u8>,
249
- * ctx: &mut tx_context::TxContext
250
- * )
251
- * ```
252
- */
253
- buildCreateMemoryIndex(options: CreateMemoryIndexTxOptions): Transaction {
254
- const tx = new Transaction();
255
-
256
- if (options.gasBudget) {
257
- tx.setGasBudget(options.gasBudget);
258
- }
259
-
260
- if (options.gasPrice) {
261
- tx.setGasPrice(options.gasPrice);
262
- }
263
-
264
- // Convert strings to vector<u8> for Move
265
- const indexBlobIdBytes = Array.from(new TextEncoder().encode(options.indexBlobId));
266
- const graphBlobIdBytes = Array.from(new TextEncoder().encode(options.graphBlobId));
267
-
268
- tx.moveCall({
269
- target: `${this.config.packageId}::memory::create_memory_index`,
270
- arguments: [
271
- tx.pure.vector('u8', indexBlobIdBytes),
272
- tx.pure.vector('u8', graphBlobIdBytes)
273
- ]
274
- });
275
-
276
- return tx;
277
- }
278
-
279
- /**
280
- * Build transaction to update a memory index
281
- *
282
- * Smart contract signature:
283
- * ```move
284
- * public entry fun update_memory_index(
285
- * memory_index: &mut MemoryIndex,
286
- * expected_version: u64,
287
- * new_index_blob_id: vector<u8>,
288
- * new_graph_blob_id: vector<u8>,
289
- * ctx: &tx_context::TxContext
290
- * )
291
- * ```
292
- *
293
- * @param options - Update options including indexId (object ID), expectedVersion, and new blob IDs
294
- */
295
- buildUpdateMemoryIndex(options: UpdateMemoryIndexTxOptions): Transaction {
296
- const tx = new Transaction();
297
-
298
- if (options.gasBudget) {
299
- tx.setGasBudget(options.gasBudget);
300
- }
301
-
302
- if (options.gasPrice) {
303
- tx.setGasPrice(options.gasPrice);
304
- }
305
-
306
- // Convert strings to vector<u8> for Move
307
- const newIndexBlobIdBytes = Array.from(new TextEncoder().encode(options.newIndexBlobId));
308
- const newGraphBlobIdBytes = Array.from(new TextEncoder().encode(options.newGraphBlobId));
309
-
310
- tx.moveCall({
311
- target: `${this.config.packageId}::memory::update_memory_index`,
312
- arguments: [
313
- tx.object(options.indexId), // &mut MemoryIndex (object reference)
314
- tx.pure.u64(options.expectedVersion), // expected_version: u64
315
- tx.pure.vector('u8', newIndexBlobIdBytes), // new_index_blob_id: vector<u8>
316
- tx.pure.vector('u8', newGraphBlobIdBytes) // new_graph_blob_id: vector<u8>
317
- ]
318
- });
319
-
320
- return tx;
321
- }
322
-
323
- // ==================== ACCESS CONTROL TRANSACTIONS ====================
324
-
325
- /**
326
- * Build transaction to grant access to content
327
- */
328
- buildGrantAccess(options: GrantAccessTxOptions): Transaction {
329
- const tx = new Transaction();
330
-
331
- if (options.gasBudget) {
332
- tx.setGasBudget(options.gasBudget);
333
- }
334
-
335
- if (options.gasPrice) {
336
- tx.setGasPrice(options.gasPrice);
337
- }
338
-
339
- // Note: Using tx.moveCall for actual implementation
340
- tx.moveCall({
341
- target: `${this.config.packageId}::access::grant_access`,
342
- arguments: [
343
- tx.pure.string(options.contentId),
344
- tx.pure.address(options.recipient),
345
- tx.pure.u8(Array.isArray(options.permissions) ? options.permissions[0] : options.permissions),
346
- tx.pure.u64(options.expirationTime || 0)
347
- ]
348
- });
349
-
350
- return tx;
351
- }
352
-
353
- /**
354
- * Build transaction to revoke access from content
355
- */
356
- buildRevokeAccess(options: RevokeAccessTxOptions): Transaction {
357
- const tx = new Transaction();
358
-
359
- if (options.gasBudget) {
360
- tx.setGasBudget(options.gasBudget);
361
- }
362
-
363
- if (options.gasPrice) {
364
- tx.setGasPrice(options.gasPrice);
365
- }
366
-
367
- // Note: Using tx.moveCall for actual implementation
368
- tx.moveCall({
369
- target: `${this.config.packageId}::access::revoke_access`,
370
- arguments: [
371
- tx.pure.string(options.contentId),
372
- tx.pure.address(options.recipient)
373
- ]
374
- });
375
-
376
- return tx;
377
- }
378
-
379
- /**
380
- * Build transaction to register content for access control
381
- */
382
- buildRegisterContent(options: RegisterContentTxOptions): Transaction {
383
- const tx = new Transaction();
384
-
385
- if (options.gasBudget) {
386
- tx.setGasBudget(options.gasBudget);
387
- }
388
-
389
- if (options.gasPrice) {
390
- tx.setGasPrice(options.gasPrice);
391
- }
392
-
393
- // Note: Using tx.moveCall for actual implementation
394
- tx.moveCall({
395
- target: `${this.config.packageId}::access::register_content`,
396
- arguments: [
397
- tx.pure.string(options.contentHash),
398
- tx.pure.string(options.encryptionKey),
399
- tx.pure.string(Array.isArray(options.accessPolicy) ? JSON.stringify(options.accessPolicy) : options.accessPolicy)
400
- ]
401
- });
402
-
403
- return tx;
404
- }
405
-
406
- // ==================== EXECUTION METHODS ====================
407
-
408
- /**
409
- * Execute a transaction using SerialTransactionExecutor with automatic retry.
410
- *
411
- * The executor automatically:
412
- * - Caches object versions to prevent version conflicts within same session
413
- * - Manages gas coins to prevent equivocation (24h lock)
414
- * - Queues transactions sequentially for safe execution
415
- *
416
- * When version conflict occurs (external modification), the executor cache
417
- * is reset and transaction is retried with fresh object references.
418
- *
419
- * @see https://sdk.mystenlabs.com/typescript/executors
420
- * @see https://docs.sui.io/concepts/sui-architecture/epochs#equivocation
421
- */
422
- async executeTransaction(
423
- tx: Transaction,
424
- signer: any,
425
- options: TransactionOptions = {}
426
- ): Promise<TransactionResult> {
427
- const maxRetries = 3;
428
-
429
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
430
- try {
431
- // Set sender if provided
432
- if (options.sender) {
433
- tx.setSender(options.sender);
434
- }
435
-
436
- // Use SerialTransactionExecutor to prevent equivocation and version conflicts
437
- const executor = this.getExecutor(signer);
438
- // Pass options to get full response data including objectChanges
439
- const executorResult = await executor.executeTransaction(tx, {
440
- showEffects: true,
441
- showObjectChanges: true,
442
- showEvents: true,
443
- });
444
-
445
- // SerialTransactionExecutor returns { digest, effects (string), data (SuiTransactionBlockResponse) }
446
- // The actual response data is in executorResult.data
447
- const result = executorResult.data;
448
-
449
- // Parse the result
450
- const transactionResult: TransactionResult = {
451
- digest: executorResult.digest,
452
- effects: result.effects,
453
- status: result.effects?.status?.status === 'success' ? 'success' : 'failure',
454
- gasUsed: result.effects?.gasUsed?.computationCost
455
- ? Number(result.effects.gasUsed.computationCost)
456
- : undefined,
457
- };
458
-
459
- // Extract created objects
460
- if (result.objectChanges) {
461
- transactionResult.createdObjects = result.objectChanges
462
- .filter((change: any) => change.type === 'created')
463
- .map((change: any) => ({
464
- objectId: change.objectId,
465
- objectType: change.objectType || 'unknown',
466
- }));
467
-
468
- transactionResult.mutatedObjects = result.objectChanges
469
- .filter((change: any) => change.type === 'mutated')
470
- .map((change: any) => ({
471
- objectId: change.objectId,
472
- objectType: change.objectType || 'unknown',
473
- }));
474
-
475
- transactionResult.deletedObjects = result.objectChanges
476
- .filter((change: any) => change.type === 'deleted')
477
- .map((change: any) => change.objectId);
478
- }
479
-
480
- // Add error if transaction failed
481
- if (transactionResult.status === 'failure') {
482
- transactionResult.error = result.effects?.status?.error || 'Unknown transaction error';
483
- }
484
-
485
- return transactionResult;
486
- } catch (error: any) {
487
- const errorMsg = error instanceof Error ? error.message : String(error);
488
-
489
- // Check if it's a version conflict error (external modification)
490
- const isVersionConflict =
491
- errorMsg.includes('is not available for consumption') ||
492
- errorMsg.includes('current version') ||
493
- errorMsg.includes('ObjectVersionTooOld') ||
494
- errorMsg.includes('EquivocationError');
495
-
496
- if (isVersionConflict && attempt < maxRetries) {
497
- // Reset executor cache to get fresh object versions
498
- console.log(`⚠️ Version conflict detected (attempt ${attempt}/${maxRetries}), resetting cache...`);
499
- await this.executor?.resetCache();
500
-
501
- // Exponential backoff: 500ms, 1000ms, 2000ms
502
- const delay = 500 * Math.pow(2, attempt - 1);
503
- console.log(` Retrying in ${delay}ms with fresh object references...`);
504
- await new Promise(resolve => setTimeout(resolve, delay));
505
- continue;
506
- }
507
-
508
- console.error('Transaction execution failed:', error);
509
- // Reset executor on final failure to get fresh state for next call
510
- this.resetExecutor();
511
- return {
512
- digest: '',
513
- status: 'failure',
514
- error: errorMsg,
515
- };
516
- }
517
- }
518
-
519
- // Should never reach here, but TypeScript needs a return
520
- return {
521
- digest: '',
522
- status: 'failure',
523
- error: 'Max retries exceeded',
524
- };
525
- }
526
-
527
- /**
528
- * Execute a transaction directly without the executor (legacy method).
529
- * Use this only when you need direct control over execution or for one-off transactions.
530
- *
531
- * WARNING: This method does not protect against equivocation.
532
- * For sequential transactions, use executeTransaction() instead.
533
- */
534
- async executeTransactionDirect(
535
- tx: Transaction,
536
- signer: any,
537
- options: TransactionOptions = {}
538
- ): Promise<TransactionResult> {
539
- try {
540
- // Set sender if provided
541
- if (options.sender) {
542
- tx.setSender(options.sender);
543
- }
544
-
545
- // Execute the transaction directly
546
- const result = await this.client.signAndExecuteTransaction({
547
- transaction: tx,
548
- signer,
549
- options: {
550
- showEffects: true,
551
- showObjectChanges: true,
552
- showEvents: true,
553
- },
554
- });
555
-
556
- // Wait for transaction to be finalized on-chain
557
- if (result.digest) {
558
- try {
559
- await this.client.waitForTransaction({
560
- digest: result.digest,
561
- options: { showEffects: true },
562
- });
563
- } catch (waitError) {
564
- console.warn('waitForTransaction warning:', waitError);
565
- }
566
- }
567
-
568
- // Parse the result
569
- const transactionResult: TransactionResult = {
570
- digest: result.digest,
571
- effects: result.effects,
572
- status: result.effects?.status?.status === 'success' ? 'success' : 'failure',
573
- gasUsed: result.effects?.gasUsed?.computationCost
574
- ? Number(result.effects.gasUsed.computationCost)
575
- : undefined,
576
- };
577
-
578
- // Extract created objects
579
- if (result.objectChanges) {
580
- transactionResult.createdObjects = result.objectChanges
581
- .filter(change => change.type === 'created')
582
- .map(change => ({
583
- objectId: change.objectId,
584
- objectType: change.objectType || 'unknown',
585
- }));
586
-
587
- transactionResult.mutatedObjects = result.objectChanges
588
- .filter(change => change.type === 'mutated')
589
- .map(change => ({
590
- objectId: change.objectId,
591
- objectType: change.objectType || 'unknown',
592
- }));
593
-
594
- transactionResult.deletedObjects = result.objectChanges
595
- .filter(change => change.type === 'deleted')
596
- .map(change => change.objectId);
597
- }
598
-
599
- // Add error if transaction failed
600
- if (transactionResult.status === 'failure') {
601
- transactionResult.error = result.effects?.status?.error || 'Unknown transaction error';
602
- }
603
-
604
- return transactionResult;
605
- } catch (error) {
606
- console.error('Transaction execution failed:', error);
607
- return {
608
- digest: '',
609
- status: 'failure',
610
- error: error instanceof Error ? error.message : String(error),
611
- };
612
- }
613
- }
614
-
615
- // ==================== CONVENIENCE METHODS ====================
616
-
617
- /**
618
- * Create and execute a memory record transaction
619
- */
620
- async createMemoryRecord(
621
- options: CreateMemoryRecordTxOptions,
622
- signer: any
623
- ): Promise<TransactionResult> {
624
- const tx = this.buildCreateMemoryRecord(options);
625
- return this.executeTransaction(tx, signer, options);
626
- }
627
-
628
- /**
629
- * Create and execute an update memory metadata transaction
630
- */
631
- async updateMemoryMetadata(
632
- options: UpdateMemoryMetadataTxOptions,
633
- signer: any
634
- ): Promise<TransactionResult> {
635
- const tx = this.buildUpdateMemoryMetadata(options);
636
- return this.executeTransaction(tx, signer, options);
637
- }
638
-
639
- /**
640
- * Create and execute a delete memory record transaction
641
- */
642
- async deleteMemoryRecord(
643
- options: DeleteMemoryRecordTxOptions,
644
- signer: any
645
- ): Promise<TransactionResult> {
646
- const tx = this.buildDeleteMemoryRecord(options);
647
- return this.executeTransaction(tx, signer, options);
648
- }
649
-
650
- /**
651
- * Create and execute a grant access transaction
652
- */
653
- async grantAccess(
654
- options: GrantAccessTxOptions,
655
- signer: any
656
- ): Promise<TransactionResult> {
657
- const tx = this.buildGrantAccess(options);
658
- return this.executeTransaction(tx, signer, options);
659
- }
660
-
661
- /**
662
- * Create and execute a revoke access transaction
663
- */
664
- async revokeAccess(
665
- options: RevokeAccessTxOptions,
666
- signer: any
667
- ): Promise<TransactionResult> {
668
- const tx = this.buildRevokeAccess(options);
669
- return this.executeTransaction(tx, signer, options);
670
- }
671
-
672
- // ==================== BATCH OPERATIONS ====================
673
-
674
- /**
675
- * Build a batch transaction combining multiple operations
676
- *
677
- * Access control uses capability-based pattern:
678
- * - createCap: Create a new MemoryCap for an app context
679
- * - transferCap: Transfer capability to recipient (grants access)
680
- * - burnCap: Burn capability (revokes access permanently)
681
- */
682
- buildBatchTransaction(operations: Array<{
683
- type: 'createMemory' | 'updateMemory' | 'deleteMemory' | 'createCap' | 'transferCap' | 'burnCap';
684
- options: any;
685
- }>): Transaction {
686
- const tx = new Transaction();
687
-
688
- for (const operation of operations) {
689
- switch (operation.type) {
690
- case 'createMemory':
691
- MemoryModule.createMemoryRecord({
692
- package: this.config.packageId,
693
- arguments: operation.options,
694
- })(tx);
695
- break;
696
-
697
- case 'updateMemory':
698
- MemoryModule.updateMemoryMetadata({
699
- package: this.config.packageId,
700
- arguments: operation.options,
701
- })(tx);
702
- break;
703
-
704
- case 'deleteMemory':
705
- MemoryModule.deleteMemoryRecord({
706
- package: this.config.packageId,
707
- arguments: operation.options,
708
- })(tx);
709
- break;
710
-
711
- case 'createCap':
712
- // Create a new MemoryCap for an app context
713
- CapabilityModule.createMemoryCap({
714
- package: this.config.packageId,
715
- arguments: operation.options,
716
- })(tx);
717
- break;
718
-
719
- case 'transferCap':
720
- // Transfer capability to recipient (grants access)
721
- CapabilityModule.transferCap({
722
- package: this.config.packageId,
723
- arguments: operation.options,
724
- })(tx);
725
- break;
726
-
727
- case 'burnCap':
728
- // Burn capability (revokes access permanently)
729
- CapabilityModule.burnCap({
730
- package: this.config.packageId,
731
- arguments: operation.options,
732
- })(tx);
733
- break;
734
-
735
- default:
736
- console.warn(`Unknown operation type: ${operation.type}`);
737
- }
738
- }
739
-
740
- return tx;
741
- }
742
-
743
- /**
744
- * Execute a batch transaction
745
- */
746
- async executeBatch(
747
- operations: Array<{
748
- type: 'createMemory' | 'updateMemory' | 'deleteMemory' | 'createCap' | 'transferCap' | 'burnCap';
749
- options: any;
750
- }>,
751
- signer: any,
752
- txOptions: TransactionOptions = {}
753
- ): Promise<TransactionResult> {
754
- const tx = this.buildBatchTransaction(operations);
755
-
756
- if (txOptions.gasBudget) {
757
- tx.setGasBudget(txOptions.gasBudget);
758
- }
759
-
760
- return this.executeTransaction(tx, signer, txOptions);
761
- }
762
-
763
- // ==================== UTILITY METHODS ====================
764
-
765
- /**
766
- * Estimate gas cost for a transaction
767
- */
768
- async estimateGas(tx: Transaction, signer: any): Promise<number> {
769
- try {
770
- const dryRunResult = await this.client.dryRunTransactionBlock({
771
- transactionBlock: await tx.build({ client: this.client }),
772
- });
773
-
774
- return dryRunResult.effects.gasUsed?.computationCost
775
- ? Number(dryRunResult.effects.gasUsed.computationCost)
776
- : 0;
777
- } catch (error) {
778
- console.error('Gas estimation failed:', error);
779
- return 0;
780
- }
781
- }
782
-
783
- /**
784
- * Get recommended gas budget based on transaction complexity
785
- */
786
- getRecommendedGasBudget(operationCount: number = 1): number {
787
- const baseGas = 1000000; // 1M MIST base
788
- const perOperationGas = 500000; // 500K MIST per operation
789
- return baseGas + (operationCount * perOperationGas);
790
- }
1
+ import { Transaction, SerialTransactionExecutor } from '@mysten/sui/transactions';
2
+ import { SuiClient } from '@mysten/sui/client';
3
+ import {
4
+ PDWConfig,
5
+ TransactionOptions,
6
+ TransactionResult,
7
+ CreateMemoryRecordTxOptions,
8
+ CreateMemoryRecordLightweightTxOptions,
9
+ UpdateMemoryMetadataTxOptions,
10
+ UpdateMemoryRecordTxOptions,
11
+ DeleteMemoryRecordTxOptions,
12
+ CreateMemoryIndexTxOptions,
13
+ UpdateMemoryIndexTxOptions,
14
+ GrantAccessTxOptions,
15
+ RevokeAccessTxOptions,
16
+ RegisterContentTxOptions,
17
+ } from '../types';
18
+
19
+ // Import generated Move contract functions
20
+ import * as MemoryModule from '../generated/pdw/memory';
21
+ import * as CapabilityModule from '../generated/pdw/capability';
22
+
23
+ /**
24
+ * TransactionService provides high-level transaction building and execution
25
+ * for Personal Data Wallet Move contracts with proper error handling and gas management.
26
+ *
27
+ * Uses SerialTransactionExecutor to prevent gas coin version conflicts and equivocation
28
+ * when multiple transactions are executed in sequence. The executor caches object versions
29
+ * and manages gas coins automatically.
30
+ *
31
+ * @see https://sdk.mystenlabs.com/typescript/executors
32
+ */
33
+ export class TransactionService {
34
+ private executor: SerialTransactionExecutor | null = null;
35
+ private currentSigner: any = null;
36
+
37
+ constructor(
38
+ private client: SuiClient,
39
+ private config: PDWConfig
40
+ ) {}
41
+
42
+ /**
43
+ * Get or create SerialTransactionExecutor for the given signer.
44
+ * The executor caches object versions and prevents equivocation.
45
+ */
46
+ private getExecutor(signer: any): SerialTransactionExecutor {
47
+ // Create new executor if signer changed or doesn't exist
48
+ if (!this.executor || this.currentSigner !== signer) {
49
+ this.executor = new SerialTransactionExecutor({
50
+ client: this.client,
51
+ signer,
52
+ });
53
+ this.currentSigner = signer;
54
+ }
55
+ return this.executor;
56
+ }
57
+
58
+ /**
59
+ * Reset the executor (useful when gas coin is exhausted or for cleanup)
60
+ */
61
+ resetExecutor(): void {
62
+ this.executor = null;
63
+ this.currentSigner = null;
64
+ }
65
+
66
+ // ==================== MEMORY TRANSACTIONS ====================
67
+
68
+ /**
69
+ * Build transaction to create a new memory record
70
+ */
71
+ buildCreateMemoryRecord(options: CreateMemoryRecordTxOptions): Transaction {
72
+ const tx = new Transaction();
73
+
74
+ // Set gas budget if provided
75
+ if (options.gasBudget) {
76
+ tx.setGasBudget(options.gasBudget);
77
+ }
78
+
79
+ // Set gas price if provided
80
+ if (options.gasPrice) {
81
+ tx.setGasPrice(options.gasPrice);
82
+ }
83
+
84
+ // Call the memory contract function with Clock for real-time timestamp
85
+ tx.moveCall({
86
+ target: `${this.config.packageId}::memory::create_memory_record`,
87
+ arguments: [
88
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.category))),
89
+ tx.pure.u64(options.vectorId),
90
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.blobId))),
91
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.contentType))),
92
+ tx.pure.u64(options.contentSize),
93
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.contentHash))),
94
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.topic))),
95
+ tx.pure.u8(options.importance),
96
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.embeddingBlobId))),
97
+ tx.object('0x6'), // Sui Clock object for real-time timestamp
98
+ ],
99
+ });
100
+
101
+ return tx;
102
+ }
103
+
104
+ /**
105
+ * Build transaction to create a lightweight memory record
106
+ *
107
+ * This creates a minimal on-chain Memory struct with only essential queryable fields.
108
+ * Rich metadata should be stored as Walrus blob metadata for gas efficiency.
109
+ *
110
+ * Use this when:
111
+ * - Gas costs are a concern (saves ~50% gas vs full metadata)
112
+ * - Rich metadata is stored on Walrus blob
113
+ * - Only need basic filtering (category, vector_id, importance)
114
+ *
115
+ * @param options - Lightweight memory creation options
116
+ * @returns Transaction to create lightweight memory record
117
+ */
118
+ buildCreateMemoryRecordLightweight(options: CreateMemoryRecordLightweightTxOptions): Transaction {
119
+ const tx = new Transaction();
120
+
121
+ // Set gas budget if provided
122
+ if (options.gasBudget) {
123
+ tx.setGasBudget(options.gasBudget);
124
+ }
125
+
126
+ // Set gas price if provided
127
+ if (options.gasPrice) {
128
+ tx.setGasPrice(options.gasPrice);
129
+ }
130
+
131
+ // Call the lightweight memory creation function
132
+ // Note: Walrus blob_id is a base64 string (URL-safe, no padding)
133
+ // Example: "E7_nNXvFU_3qZVu3OH1yycRG7LZlyn1-UxEDCDDqGGU"
134
+ // We encode the string to vector<u8> for the Move function parameter
135
+ // Clock object (0x6) is required for real-time timestamp
136
+ tx.moveCall({
137
+ target: `${this.config.packageId}::memory::create_memory_record_lightweight`,
138
+ arguments: [
139
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.category))),
140
+ tx.pure.u64(options.vectorId),
141
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.blobId))),
142
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.blobObjectId || ''))),
143
+ tx.pure.u8(options.importance),
144
+ tx.object('0x6'), // Sui Clock object for real-time timestamp
145
+ ],
146
+ });
147
+
148
+ return tx;
149
+ }
150
+
151
+ /**
152
+ * Build transaction to create a lightweight memory record with capability (V2 - RECOMMENDED)
153
+ *
154
+ * Gas-optimized version for capability-based access control with SEAL encryption.
155
+ * This creates a memory record linked to a MemoryCap, enabling:
156
+ * - Identity-based encryption/decryption via SEAL
157
+ * - Cross-package memory sharing via capability transfer
158
+ * - Fine-grained access control
159
+ *
160
+ * Use this when:
161
+ * - Encryption is enabled (SEAL requires capability)
162
+ * - You need cross-package memory sharing
163
+ * - You want capability-based access control
164
+ *
165
+ * @param options - Lightweight memory creation options with capability
166
+ * @returns Transaction to create lightweight memory record with capability
167
+ */
168
+ buildCreateMemoryRecordLightweightWithCap(options: CreateMemoryRecordLightweightTxOptions & { capId: string }): Transaction {
169
+ const tx = new Transaction();
170
+
171
+ // Set gas budget if provided
172
+ if (options.gasBudget) {
173
+ tx.setGasBudget(options.gasBudget);
174
+ }
175
+
176
+ // Set gas price if provided
177
+ if (options.gasPrice) {
178
+ tx.setGasPrice(options.gasPrice);
179
+ }
180
+
181
+ // Call the capability-based memory creation function
182
+ // Note: Clock object (0x6) is required for real-time timestamp
183
+ tx.moveCall({
184
+ target: `${this.config.packageId}::memory::create_memory_lightweight_with_cap`,
185
+ arguments: [
186
+ tx.object(options.capId), // &MemoryCap reference (FIRST argument)
187
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.category))),
188
+ tx.pure.u64(options.vectorId),
189
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.blobId))),
190
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.blobObjectId || ''))),
191
+ tx.pure.u8(options.importance),
192
+ tx.object('0x6'), // Sui Clock object for real-time timestamp
193
+ ],
194
+ });
195
+
196
+ return tx;
197
+ }
198
+
199
+ /**
200
+ * Build transaction to update memory metadata
201
+ */
202
+ buildUpdateMemoryMetadata(options: UpdateMemoryMetadataTxOptions): Transaction {
203
+ const tx = new Transaction();
204
+
205
+ if (options.gasBudget) {
206
+ tx.setGasBudget(options.gasBudget);
207
+ }
208
+
209
+ if (options.gasPrice) {
210
+ tx.setGasPrice(options.gasPrice);
211
+ }
212
+
213
+ // Note: Using tx.moveCall with Clock for real-time timestamp
214
+ tx.moveCall({
215
+ target: `${this.config.packageId}::memory::update_memory_metadata`,
216
+ arguments: [
217
+ tx.object(options.memoryId),
218
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.metadataBlobId))),
219
+ tx.pure.u8(options.embeddingDimension || 5),
220
+ tx.object('0x6'), // Sui Clock object for real-time timestamp
221
+ ]
222
+ });
223
+
224
+ return tx;
225
+ }
226
+
227
+ /**
228
+ * Build transaction for comprehensive memory record update
229
+ *
230
+ * Updates multiple fields of a Memory object in a single transaction.
231
+ * Only non-empty/non-zero values will be updated on-chain.
232
+ *
233
+ * @param options - Update options with memoryId and fields to update
234
+ * @returns Transaction to update memory record
235
+ */
236
+ buildUpdateMemoryRecord(options: UpdateMemoryRecordTxOptions): Transaction {
237
+ const tx = new Transaction();
238
+
239
+ if (options.gasBudget) {
240
+ tx.setGasBudget(options.gasBudget);
241
+ }
242
+
243
+ if (options.gasPrice) {
244
+ tx.setGasPrice(options.gasPrice);
245
+ }
246
+
247
+ // Use direct moveCall with Clock object for real-time timestamp
248
+ tx.moveCall({
249
+ target: `${this.config.packageId}::memory::update_memory_record`,
250
+ arguments: [
251
+ tx.object(options.memoryId),
252
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.newBlobId || ''))),
253
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.newCategory || ''))),
254
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.newTopic || ''))),
255
+ tx.pure.u8(options.newImportance || 0),
256
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.newEmbeddingBlobId || ''))),
257
+ tx.pure.vector('u8', Array.from(new TextEncoder().encode(options.newContentHash || ''))),
258
+ tx.pure.u64(options.newContentSize || 0),
259
+ tx.object('0x6'), // Sui Clock object for real-time timestamp
260
+ ],
261
+ });
262
+
263
+ return tx;
264
+ }
265
+
266
+ /**
267
+ * Build transaction to delete a memory record
268
+ */
269
+ buildDeleteMemoryRecord(options: DeleteMemoryRecordTxOptions): Transaction {
270
+ const tx = new Transaction();
271
+
272
+ if (options.gasBudget) {
273
+ tx.setGasBudget(options.gasBudget);
274
+ }
275
+
276
+ if (options.gasPrice) {
277
+ tx.setGasPrice(options.gasPrice);
278
+ }
279
+
280
+ // Note: Using tx.moveCall for actual implementation
281
+ tx.moveCall({
282
+ target: `${this.config.packageId}::memory::delete_memory_record`,
283
+ arguments: [tx.pure.string(options.memoryId)]
284
+ });
285
+
286
+ return tx;
287
+ }
288
+
289
+ /**
290
+ * Build transaction to create a memory index
291
+ *
292
+ * Smart contract signature:
293
+ * ```move
294
+ * public entry fun create_memory_index(
295
+ * index_blob_id: vector<u8>,
296
+ * graph_blob_id: vector<u8>,
297
+ * ctx: &mut tx_context::TxContext
298
+ * )
299
+ * ```
300
+ */
301
+ buildCreateMemoryIndex(options: CreateMemoryIndexTxOptions): Transaction {
302
+ const tx = new Transaction();
303
+
304
+ if (options.gasBudget) {
305
+ tx.setGasBudget(options.gasBudget);
306
+ }
307
+
308
+ if (options.gasPrice) {
309
+ tx.setGasPrice(options.gasPrice);
310
+ }
311
+
312
+ // Convert strings to vector<u8> for Move
313
+ const indexBlobIdBytes = Array.from(new TextEncoder().encode(options.indexBlobId));
314
+ const graphBlobIdBytes = Array.from(new TextEncoder().encode(options.graphBlobId));
315
+
316
+ tx.moveCall({
317
+ target: `${this.config.packageId}::memory::create_memory_index`,
318
+ arguments: [
319
+ tx.pure.vector('u8', indexBlobIdBytes),
320
+ tx.pure.vector('u8', graphBlobIdBytes)
321
+ ]
322
+ });
323
+
324
+ return tx;
325
+ }
326
+
327
+ /**
328
+ * Build transaction to update a memory index
329
+ *
330
+ * Smart contract signature:
331
+ * ```move
332
+ * public entry fun update_memory_index(
333
+ * memory_index: &mut MemoryIndex,
334
+ * expected_version: u64,
335
+ * new_index_blob_id: vector<u8>,
336
+ * new_graph_blob_id: vector<u8>,
337
+ * ctx: &tx_context::TxContext
338
+ * )
339
+ * ```
340
+ *
341
+ * @param options - Update options including indexId (object ID), expectedVersion, and new blob IDs
342
+ */
343
+ buildUpdateMemoryIndex(options: UpdateMemoryIndexTxOptions): Transaction {
344
+ const tx = new Transaction();
345
+
346
+ if (options.gasBudget) {
347
+ tx.setGasBudget(options.gasBudget);
348
+ }
349
+
350
+ if (options.gasPrice) {
351
+ tx.setGasPrice(options.gasPrice);
352
+ }
353
+
354
+ // Convert strings to vector<u8> for Move
355
+ const newIndexBlobIdBytes = Array.from(new TextEncoder().encode(options.newIndexBlobId));
356
+ const newGraphBlobIdBytes = Array.from(new TextEncoder().encode(options.newGraphBlobId));
357
+
358
+ tx.moveCall({
359
+ target: `${this.config.packageId}::memory::update_memory_index`,
360
+ arguments: [
361
+ tx.object(options.indexId), // &mut MemoryIndex (object reference)
362
+ tx.pure.u64(options.expectedVersion), // expected_version: u64
363
+ tx.pure.vector('u8', newIndexBlobIdBytes), // new_index_blob_id: vector<u8>
364
+ tx.pure.vector('u8', newGraphBlobIdBytes) // new_graph_blob_id: vector<u8>
365
+ ]
366
+ });
367
+
368
+ return tx;
369
+ }
370
+
371
+ // ==================== ACCESS CONTROL TRANSACTIONS ====================
372
+
373
+ /**
374
+ * Build transaction to grant access to content
375
+ */
376
+ buildGrantAccess(options: GrantAccessTxOptions): Transaction {
377
+ const tx = new Transaction();
378
+
379
+ if (options.gasBudget) {
380
+ tx.setGasBudget(options.gasBudget);
381
+ }
382
+
383
+ if (options.gasPrice) {
384
+ tx.setGasPrice(options.gasPrice);
385
+ }
386
+
387
+ // Note: Using tx.moveCall for actual implementation
388
+ tx.moveCall({
389
+ target: `${this.config.packageId}::access::grant_access`,
390
+ arguments: [
391
+ tx.pure.string(options.contentId),
392
+ tx.pure.address(options.recipient),
393
+ tx.pure.u8(Array.isArray(options.permissions) ? options.permissions[0] : options.permissions),
394
+ tx.pure.u64(options.expirationTime || 0)
395
+ ]
396
+ });
397
+
398
+ return tx;
399
+ }
400
+
401
+ /**
402
+ * Build transaction to revoke access from content
403
+ */
404
+ buildRevokeAccess(options: RevokeAccessTxOptions): Transaction {
405
+ const tx = new Transaction();
406
+
407
+ if (options.gasBudget) {
408
+ tx.setGasBudget(options.gasBudget);
409
+ }
410
+
411
+ if (options.gasPrice) {
412
+ tx.setGasPrice(options.gasPrice);
413
+ }
414
+
415
+ // Note: Using tx.moveCall for actual implementation
416
+ tx.moveCall({
417
+ target: `${this.config.packageId}::access::revoke_access`,
418
+ arguments: [
419
+ tx.pure.string(options.contentId),
420
+ tx.pure.address(options.recipient)
421
+ ]
422
+ });
423
+
424
+ return tx;
425
+ }
426
+
427
+ /**
428
+ * Build transaction to register content for access control
429
+ */
430
+ buildRegisterContent(options: RegisterContentTxOptions): Transaction {
431
+ const tx = new Transaction();
432
+
433
+ if (options.gasBudget) {
434
+ tx.setGasBudget(options.gasBudget);
435
+ }
436
+
437
+ if (options.gasPrice) {
438
+ tx.setGasPrice(options.gasPrice);
439
+ }
440
+
441
+ // Note: Using tx.moveCall for actual implementation
442
+ tx.moveCall({
443
+ target: `${this.config.packageId}::access::register_content`,
444
+ arguments: [
445
+ tx.pure.string(options.contentHash),
446
+ tx.pure.string(options.encryptionKey),
447
+ tx.pure.string(Array.isArray(options.accessPolicy) ? JSON.stringify(options.accessPolicy) : options.accessPolicy)
448
+ ]
449
+ });
450
+
451
+ return tx;
452
+ }
453
+
454
+ // ==================== EXECUTION METHODS ====================
455
+
456
+ /**
457
+ * Execute a transaction using SerialTransactionExecutor with automatic retry.
458
+ *
459
+ * The executor automatically:
460
+ * - Caches object versions to prevent version conflicts within same session
461
+ * - Manages gas coins to prevent equivocation (24h lock)
462
+ * - Queues transactions sequentially for safe execution
463
+ *
464
+ * When version conflict occurs (external modification), the executor cache
465
+ * is reset and transaction is retried with fresh object references.
466
+ *
467
+ * @see https://sdk.mystenlabs.com/typescript/executors
468
+ * @see https://docs.sui.io/concepts/sui-architecture/epochs#equivocation
469
+ */
470
+ async executeTransaction(
471
+ tx: Transaction,
472
+ signer: any,
473
+ options: TransactionOptions = {}
474
+ ): Promise<TransactionResult> {
475
+ const maxRetries = 3;
476
+
477
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
478
+ try {
479
+ // Set sender if provided
480
+ if (options.sender) {
481
+ tx.setSender(options.sender);
482
+ }
483
+
484
+ // Use SerialTransactionExecutor to prevent equivocation and version conflicts
485
+ const executor = this.getExecutor(signer);
486
+ // Pass options to get full response data including objectChanges
487
+ const executorResult = await executor.executeTransaction(tx, {
488
+ showEffects: true,
489
+ showObjectChanges: true,
490
+ showEvents: true,
491
+ });
492
+
493
+ // SerialTransactionExecutor returns { digest, effects (string), data (SuiTransactionBlockResponse) }
494
+ // The actual response data is in executorResult.data
495
+ const result = executorResult.data;
496
+
497
+ // Parse the result
498
+ const transactionResult: TransactionResult = {
499
+ digest: executorResult.digest,
500
+ effects: result.effects,
501
+ status: result.effects?.status?.status === 'success' ? 'success' : 'failure',
502
+ gasUsed: result.effects?.gasUsed?.computationCost
503
+ ? Number(result.effects.gasUsed.computationCost)
504
+ : undefined,
505
+ };
506
+
507
+ // Extract created objects
508
+ if (result.objectChanges) {
509
+ transactionResult.createdObjects = result.objectChanges
510
+ .filter((change: any) => change.type === 'created')
511
+ .map((change: any) => ({
512
+ objectId: change.objectId,
513
+ objectType: change.objectType || 'unknown',
514
+ }));
515
+
516
+ transactionResult.mutatedObjects = result.objectChanges
517
+ .filter((change: any) => change.type === 'mutated')
518
+ .map((change: any) => ({
519
+ objectId: change.objectId,
520
+ objectType: change.objectType || 'unknown',
521
+ }));
522
+
523
+ transactionResult.deletedObjects = result.objectChanges
524
+ .filter((change: any) => change.type === 'deleted')
525
+ .map((change: any) => change.objectId);
526
+ }
527
+
528
+ // Add error if transaction failed
529
+ if (transactionResult.status === 'failure') {
530
+ transactionResult.error = result.effects?.status?.error || 'Unknown transaction error';
531
+ }
532
+
533
+ return transactionResult;
534
+ } catch (error: any) {
535
+ const errorMsg = error instanceof Error ? error.message : String(error);
536
+
537
+ // Check if it's a version conflict error (external modification)
538
+ const isVersionConflict =
539
+ errorMsg.includes('is not available for consumption') ||
540
+ errorMsg.includes('current version') ||
541
+ errorMsg.includes('ObjectVersionTooOld') ||
542
+ errorMsg.includes('EquivocationError');
543
+
544
+ if (isVersionConflict && attempt < maxRetries) {
545
+ // Reset executor cache to get fresh object versions
546
+ console.log(`⚠️ Version conflict detected (attempt ${attempt}/${maxRetries}), resetting cache...`);
547
+ await this.executor?.resetCache();
548
+
549
+ // Exponential backoff: 500ms, 1000ms, 2000ms
550
+ const delay = 500 * Math.pow(2, attempt - 1);
551
+ console.log(` Retrying in ${delay}ms with fresh object references...`);
552
+ await new Promise(resolve => setTimeout(resolve, delay));
553
+ continue;
554
+ }
555
+
556
+ console.error('Transaction execution failed:', error);
557
+ // Reset executor on final failure to get fresh state for next call
558
+ this.resetExecutor();
559
+ return {
560
+ digest: '',
561
+ status: 'failure',
562
+ error: errorMsg,
563
+ };
564
+ }
565
+ }
566
+
567
+ // Should never reach here, but TypeScript needs a return
568
+ return {
569
+ digest: '',
570
+ status: 'failure',
571
+ error: 'Max retries exceeded',
572
+ };
573
+ }
574
+
575
+ /**
576
+ * Execute a transaction directly without the executor (legacy method).
577
+ * Use this only when you need direct control over execution or for one-off transactions.
578
+ *
579
+ * WARNING: This method does not protect against equivocation.
580
+ * For sequential transactions, use executeTransaction() instead.
581
+ */
582
+ async executeTransactionDirect(
583
+ tx: Transaction,
584
+ signer: any,
585
+ options: TransactionOptions = {}
586
+ ): Promise<TransactionResult> {
587
+ try {
588
+ // Set sender if provided
589
+ if (options.sender) {
590
+ tx.setSender(options.sender);
591
+ }
592
+
593
+ // Execute the transaction directly
594
+ const result = await this.client.signAndExecuteTransaction({
595
+ transaction: tx,
596
+ signer,
597
+ options: {
598
+ showEffects: true,
599
+ showObjectChanges: true,
600
+ showEvents: true,
601
+ },
602
+ });
603
+
604
+ // Wait for transaction to be finalized on-chain
605
+ if (result.digest) {
606
+ try {
607
+ await this.client.waitForTransaction({
608
+ digest: result.digest,
609
+ options: { showEffects: true },
610
+ });
611
+ } catch (waitError) {
612
+ console.warn('waitForTransaction warning:', waitError);
613
+ }
614
+ }
615
+
616
+ // Parse the result
617
+ const transactionResult: TransactionResult = {
618
+ digest: result.digest,
619
+ effects: result.effects,
620
+ status: result.effects?.status?.status === 'success' ? 'success' : 'failure',
621
+ gasUsed: result.effects?.gasUsed?.computationCost
622
+ ? Number(result.effects.gasUsed.computationCost)
623
+ : undefined,
624
+ };
625
+
626
+ // Extract created objects
627
+ if (result.objectChanges) {
628
+ transactionResult.createdObjects = result.objectChanges
629
+ .filter(change => change.type === 'created')
630
+ .map(change => ({
631
+ objectId: change.objectId,
632
+ objectType: change.objectType || 'unknown',
633
+ }));
634
+
635
+ transactionResult.mutatedObjects = result.objectChanges
636
+ .filter(change => change.type === 'mutated')
637
+ .map(change => ({
638
+ objectId: change.objectId,
639
+ objectType: change.objectType || 'unknown',
640
+ }));
641
+
642
+ transactionResult.deletedObjects = result.objectChanges
643
+ .filter(change => change.type === 'deleted')
644
+ .map(change => change.objectId);
645
+ }
646
+
647
+ // Add error if transaction failed
648
+ if (transactionResult.status === 'failure') {
649
+ transactionResult.error = result.effects?.status?.error || 'Unknown transaction error';
650
+ }
651
+
652
+ return transactionResult;
653
+ } catch (error) {
654
+ console.error('Transaction execution failed:', error);
655
+ return {
656
+ digest: '',
657
+ status: 'failure',
658
+ error: error instanceof Error ? error.message : String(error),
659
+ };
660
+ }
661
+ }
662
+
663
+ // ==================== CONVENIENCE METHODS ====================
664
+
665
+ /**
666
+ * Create and execute a memory record transaction
667
+ */
668
+ async createMemoryRecord(
669
+ options: CreateMemoryRecordTxOptions,
670
+ signer: any
671
+ ): Promise<TransactionResult> {
672
+ const tx = this.buildCreateMemoryRecord(options);
673
+ return this.executeTransaction(tx, signer, options);
674
+ }
675
+
676
+ /**
677
+ * Create and execute an update memory metadata transaction
678
+ */
679
+ async updateMemoryMetadata(
680
+ options: UpdateMemoryMetadataTxOptions,
681
+ signer: any
682
+ ): Promise<TransactionResult> {
683
+ const tx = this.buildUpdateMemoryMetadata(options);
684
+ return this.executeTransaction(tx, signer, options);
685
+ }
686
+
687
+ /**
688
+ * Create and execute a delete memory record transaction
689
+ */
690
+ async deleteMemoryRecord(
691
+ options: DeleteMemoryRecordTxOptions,
692
+ signer: any
693
+ ): Promise<TransactionResult> {
694
+ const tx = this.buildDeleteMemoryRecord(options);
695
+ return this.executeTransaction(tx, signer, options);
696
+ }
697
+
698
+ /**
699
+ * Create and execute a grant access transaction
700
+ */
701
+ async grantAccess(
702
+ options: GrantAccessTxOptions,
703
+ signer: any
704
+ ): Promise<TransactionResult> {
705
+ const tx = this.buildGrantAccess(options);
706
+ return this.executeTransaction(tx, signer, options);
707
+ }
708
+
709
+ /**
710
+ * Create and execute a revoke access transaction
711
+ */
712
+ async revokeAccess(
713
+ options: RevokeAccessTxOptions,
714
+ signer: any
715
+ ): Promise<TransactionResult> {
716
+ const tx = this.buildRevokeAccess(options);
717
+ return this.executeTransaction(tx, signer, options);
718
+ }
719
+
720
+ // ==================== BATCH OPERATIONS ====================
721
+
722
+ /**
723
+ * Build a batch transaction combining multiple operations
724
+ *
725
+ * Access control uses capability-based pattern:
726
+ * - createCap: Create a new MemoryCap for an app context
727
+ * - transferCap: Transfer capability to recipient (grants access)
728
+ * - burnCap: Burn capability (revokes access permanently)
729
+ */
730
+ buildBatchTransaction(operations: Array<{
731
+ type: 'createMemory' | 'updateMemory' | 'deleteMemory' | 'createCap' | 'transferCap' | 'burnCap';
732
+ options: any;
733
+ }>): Transaction {
734
+ const tx = new Transaction();
735
+
736
+ for (const operation of operations) {
737
+ switch (operation.type) {
738
+ case 'createMemory':
739
+ MemoryModule.createMemoryRecord({
740
+ package: this.config.packageId,
741
+ arguments: operation.options,
742
+ })(tx);
743
+ break;
744
+
745
+ case 'updateMemory':
746
+ MemoryModule.updateMemoryMetadata({
747
+ package: this.config.packageId,
748
+ arguments: operation.options,
749
+ })(tx);
750
+ break;
751
+
752
+ case 'deleteMemory':
753
+ MemoryModule.deleteMemoryRecord({
754
+ package: this.config.packageId,
755
+ arguments: operation.options,
756
+ })(tx);
757
+ break;
758
+
759
+ case 'createCap':
760
+ // Create a new MemoryCap for an app context
761
+ CapabilityModule.createMemoryCap({
762
+ package: this.config.packageId,
763
+ arguments: operation.options,
764
+ })(tx);
765
+ break;
766
+
767
+ case 'transferCap':
768
+ // Transfer capability to recipient (grants access)
769
+ CapabilityModule.transferCap({
770
+ package: this.config.packageId,
771
+ arguments: operation.options,
772
+ })(tx);
773
+ break;
774
+
775
+ case 'burnCap':
776
+ // Burn capability (revokes access permanently)
777
+ CapabilityModule.burnCap({
778
+ package: this.config.packageId,
779
+ arguments: operation.options,
780
+ })(tx);
781
+ break;
782
+
783
+ default:
784
+ console.warn(`Unknown operation type: ${operation.type}`);
785
+ }
786
+ }
787
+
788
+ return tx;
789
+ }
790
+
791
+ /**
792
+ * Execute a batch transaction
793
+ */
794
+ async executeBatch(
795
+ operations: Array<{
796
+ type: 'createMemory' | 'updateMemory' | 'deleteMemory' | 'createCap' | 'transferCap' | 'burnCap';
797
+ options: any;
798
+ }>,
799
+ signer: any,
800
+ txOptions: TransactionOptions = {}
801
+ ): Promise<TransactionResult> {
802
+ const tx = this.buildBatchTransaction(operations);
803
+
804
+ if (txOptions.gasBudget) {
805
+ tx.setGasBudget(txOptions.gasBudget);
806
+ }
807
+
808
+ return this.executeTransaction(tx, signer, txOptions);
809
+ }
810
+
811
+ // ==================== UTILITY METHODS ====================
812
+
813
+ /**
814
+ * Estimate gas cost for a transaction
815
+ */
816
+ async estimateGas(tx: Transaction, signer: any): Promise<number> {
817
+ try {
818
+ const dryRunResult = await this.client.dryRunTransactionBlock({
819
+ transactionBlock: await tx.build({ client: this.client }),
820
+ });
821
+
822
+ return dryRunResult.effects.gasUsed?.computationCost
823
+ ? Number(dryRunResult.effects.gasUsed.computationCost)
824
+ : 0;
825
+ } catch (error) {
826
+ console.error('Gas estimation failed:', error);
827
+ return 0;
828
+ }
829
+ }
830
+
831
+ /**
832
+ * Get recommended gas budget based on transaction complexity
833
+ */
834
+ getRecommendedGasBudget(operationCount: number = 1): number {
835
+ const baseGas = 1000000; // 1M MIST base
836
+ const perOperationGas = 500000; // 500K MIST per operation
837
+ return baseGas + (operationCount * perOperationGas);
838
+ }
791
839
  }