@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.
- package/ARCHITECTURE.md +547 -547
- package/BENCHMARKS.md +238 -238
- package/README.md +310 -181
- package/dist/ai-sdk/tools.d.ts +2 -2
- package/dist/ai-sdk/tools.js +2 -2
- package/dist/client/ClientMemoryManager.js +2 -2
- package/dist/client/ClientMemoryManager.js.map +1 -1
- package/dist/client/PersonalDataWallet.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.d.ts +29 -1
- package/dist/client/SimplePDWClient.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.js +45 -13
- package/dist/client/SimplePDWClient.js.map +1 -1
- package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
- package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
- package/dist/client/namespaces/MemoryNamespace.d.ts +31 -0
- package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/MemoryNamespace.js +272 -39
- package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
- package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
- package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts +12 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js +62 -4
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
- package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
- package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
- package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
- package/dist/config/ConfigurationHelper.js +61 -61
- package/dist/config/defaults.js +2 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/graph/GraphService.js +21 -21
- package/dist/graph/GraphService.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/seal/EncryptionService.d.ts +9 -5
- package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
- package/dist/infrastructure/seal/EncryptionService.js +37 -15
- package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
- package/dist/infrastructure/seal/SealService.d.ts +13 -5
- package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
- package/dist/infrastructure/seal/SealService.js +36 -34
- package/dist/infrastructure/seal/SealService.js.map +1 -1
- package/dist/langchain/createPDWRAG.js +30 -30
- package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
- package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
- package/dist/retrieval/MemoryDecryptionPipeline.js.map +1 -1
- package/dist/retrieval/MemoryRetrievalService.d.ts +31 -0
- package/dist/retrieval/MemoryRetrievalService.d.ts.map +1 -1
- package/dist/retrieval/MemoryRetrievalService.js +44 -4
- package/dist/retrieval/MemoryRetrievalService.js.map +1 -1
- package/dist/services/CapabilityService.d.ts.map +1 -1
- package/dist/services/CapabilityService.js +30 -14
- package/dist/services/CapabilityService.js.map +1 -1
- package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
- package/dist/services/CrossContextPermissionService.js +9 -7
- package/dist/services/CrossContextPermissionService.js.map +1 -1
- package/dist/services/EmbeddingService.d.ts +28 -1
- package/dist/services/EmbeddingService.d.ts.map +1 -1
- package/dist/services/EmbeddingService.js +54 -0
- package/dist/services/EmbeddingService.js.map +1 -1
- package/dist/services/EncryptionService.d.ts.map +1 -1
- package/dist/services/EncryptionService.js +6 -5
- package/dist/services/EncryptionService.js.map +1 -1
- package/dist/services/GeminiAIService.js +309 -309
- package/dist/services/IndexManager.d.ts +5 -1
- package/dist/services/IndexManager.d.ts.map +1 -1
- package/dist/services/IndexManager.js +17 -40
- package/dist/services/IndexManager.js.map +1 -1
- package/dist/services/QueryService.js +1 -1
- package/dist/services/QueryService.js.map +1 -1
- package/dist/services/StorageService.d.ts +11 -0
- package/dist/services/StorageService.d.ts.map +1 -1
- package/dist/services/StorageService.js +73 -10
- package/dist/services/StorageService.js.map +1 -1
- package/dist/services/TransactionService.d.ts +20 -0
- package/dist/services/TransactionService.d.ts.map +1 -1
- package/dist/services/TransactionService.js +43 -0
- package/dist/services/TransactionService.js.map +1 -1
- package/dist/services/ViewService.js +2 -2
- package/dist/services/ViewService.js.map +1 -1
- package/dist/services/storage/QuiltBatchManager.d.ts +101 -1
- package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
- package/dist/services/storage/QuiltBatchManager.js +410 -20
- package/dist/services/storage/QuiltBatchManager.js.map +1 -1
- package/dist/services/storage/index.d.ts +1 -1
- package/dist/services/storage/index.d.ts.map +1 -1
- package/dist/services/storage/index.js.map +1 -1
- package/dist/utils/LRUCache.d.ts +106 -0
- package/dist/utils/LRUCache.d.ts.map +1 -0
- package/dist/utils/LRUCache.js +281 -0
- package/dist/utils/LRUCache.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/memoryIndexOnChain.d.ts +212 -0
- package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
- package/dist/utils/memoryIndexOnChain.js +312 -0
- package/dist/utils/memoryIndexOnChain.js.map +1 -0
- package/dist/utils/rebuildIndexNode.d.ts +29 -0
- package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
- package/dist/utils/rebuildIndexNode.js +366 -98
- package/dist/utils/rebuildIndexNode.js.map +1 -1
- package/dist/vector/HnswWasmService.d.ts +20 -5
- package/dist/vector/HnswWasmService.d.ts.map +1 -1
- package/dist/vector/HnswWasmService.js +73 -40
- package/dist/vector/HnswWasmService.js.map +1 -1
- package/dist/vector/IHnswService.d.ts +10 -1
- package/dist/vector/IHnswService.d.ts.map +1 -1
- package/dist/vector/IHnswService.js.map +1 -1
- package/dist/vector/NodeHnswService.d.ts +16 -0
- package/dist/vector/NodeHnswService.d.ts.map +1 -1
- package/dist/vector/NodeHnswService.js +84 -5
- package/dist/vector/NodeHnswService.js.map +1 -1
- package/dist/vector/createHnswService.d.ts +1 -1
- package/dist/vector/createHnswService.js +1 -1
- package/dist/vector/index.d.ts +1 -1
- package/dist/vector/index.js +1 -1
- package/package.json +157 -157
- package/src/access/PermissionService.ts +635 -635
- package/src/aggregation/AggregationService.ts +389 -389
- package/src/ai-sdk/PDWVectorStore.ts +715 -715
- package/src/ai-sdk/index.ts +65 -65
- package/src/ai-sdk/tools.ts +460 -460
- package/src/ai-sdk/types.ts +404 -404
- package/src/batch/BatchManager.ts +597 -597
- package/src/batch/BatchingService.ts +429 -429
- package/src/batch/MemoryProcessingCache.ts +492 -492
- package/src/batch/index.ts +30 -30
- package/src/browser.ts +200 -200
- package/src/client/ClientMemoryManager.ts +987 -987
- package/src/client/PersonalDataWallet.ts +345 -345
- package/src/client/SimplePDWClient.ts +1289 -1222
- package/src/client/factory.ts +154 -154
- package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
- package/src/client/namespaces/BatchNamespace.ts +356 -356
- package/src/client/namespaces/CacheNamespace.ts +123 -123
- package/src/client/namespaces/CapabilityNamespace.ts +217 -217
- package/src/client/namespaces/ClassifyNamespace.ts +169 -169
- package/src/client/namespaces/ContextNamespace.ts +297 -297
- package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
- package/src/client/namespaces/EncryptionNamespace.ts +221 -221
- package/src/client/namespaces/GraphNamespace.ts +468 -468
- package/src/client/namespaces/IndexNamespace.ts +361 -361
- package/src/client/namespaces/MemoryNamespace.ts +1422 -1135
- package/src/client/namespaces/PermissionsNamespace.ts +254 -254
- package/src/client/namespaces/PipelineNamespace.ts +220 -220
- package/src/client/namespaces/SearchNamespace.ts +1049 -1049
- package/src/client/namespaces/StorageNamespace.ts +458 -458
- package/src/client/namespaces/TxNamespace.ts +260 -260
- package/src/client/namespaces/WalletNamespace.ts +243 -243
- package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
- package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -546
- package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
- package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
- package/src/client/namespaces/consolidated/index.ts +39 -39
- package/src/client/signers/KeypairSigner.ts +108 -108
- package/src/client/signers/UnifiedSigner.ts +110 -110
- package/src/client/signers/WalletAdapterSigner.ts +159 -159
- package/src/client/signers/index.ts +26 -26
- package/src/config/ConfigurationHelper.ts +412 -412
- package/src/config/defaults.ts +51 -51
- package/src/config/index.ts +8 -8
- package/src/config/validation.ts +70 -70
- package/src/core/index.ts +14 -14
- package/src/core/interfaces/IService.ts +307 -307
- package/src/core/interfaces/index.ts +8 -8
- package/src/core/types/capability.ts +297 -297
- package/src/core/types/index.ts +870 -870
- package/src/core/types/wallet.ts +270 -270
- package/src/core/types.ts +9 -9
- package/src/core/wallet.ts +222 -222
- package/src/embedding/index.ts +19 -19
- package/src/embedding/types.ts +357 -357
- package/src/errors/index.ts +602 -602
- package/src/errors/recovery.ts +461 -461
- package/src/errors/validation.ts +567 -567
- package/src/generated/pdw/capability.ts +319 -319
- package/src/graph/GraphService.ts +887 -887
- package/src/graph/KnowledgeGraphManager.ts +728 -728
- package/src/graph/index.ts +25 -25
- package/src/index.ts +498 -474
- package/src/infrastructure/index.ts +22 -22
- package/src/infrastructure/seal/EncryptionService.ts +628 -603
- package/src/infrastructure/seal/SealService.ts +613 -615
- package/src/infrastructure/seal/index.ts +9 -9
- package/src/infrastructure/sui/BlockchainManager.ts +627 -627
- package/src/infrastructure/sui/SuiService.ts +888 -888
- package/src/infrastructure/sui/index.ts +9 -9
- package/src/infrastructure/walrus/StorageManager.ts +604 -604
- package/src/infrastructure/walrus/WalrusStorageService.ts +612 -612
- package/src/infrastructure/walrus/index.ts +9 -9
- package/src/langchain/PDWEmbeddings.ts +145 -145
- package/src/langchain/PDWVectorStore.ts +456 -456
- package/src/langchain/createPDWRAG.ts +303 -303
- package/src/langchain/index.ts +47 -47
- package/src/permissions/ConsentRepository.browser.ts +249 -249
- package/src/permissions/ConsentRepository.ts +364 -364
- package/src/pipeline/MemoryPipeline.ts +862 -862
- package/src/pipeline/PipelineManager.ts +683 -683
- package/src/pipeline/index.ts +26 -26
- package/src/retrieval/AdvancedSearchService.ts +629 -629
- package/src/retrieval/MemoryAnalyticsService.ts +711 -711
- package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
- package/src/retrieval/MemoryRetrievalService.ts +904 -830
- package/src/retrieval/index.ts +42 -42
- package/src/services/BatchService.ts +352 -352
- package/src/services/CapabilityService.ts +464 -448
- package/src/services/ClassifierService.ts +465 -465
- package/src/services/CrossContextPermissionService.ts +486 -484
- package/src/services/EmbeddingService.ts +771 -706
- package/src/services/EncryptionService.ts +712 -711
- package/src/services/GeminiAIService.ts +753 -753
- package/src/services/IndexManager.ts +977 -1004
- package/src/services/MemoryIndexService.ts +1003 -1003
- package/src/services/MemoryService.ts +369 -369
- package/src/services/QueryService.ts +890 -890
- package/src/services/StorageService.ts +1182 -1111
- package/src/services/TransactionService.ts +838 -790
- package/src/services/VectorService.ts +462 -462
- package/src/services/ViewService.ts +484 -484
- package/src/services/index.ts +25 -25
- package/src/services/storage/BlobAttributesManager.ts +333 -333
- package/src/services/storage/KnowledgeGraphManager.ts +425 -425
- package/src/services/storage/MemorySearchManager.ts +387 -387
- package/src/services/storage/QuiltBatchManager.ts +1130 -660
- package/src/services/storage/WalrusMetadataManager.ts +268 -268
- package/src/services/storage/WalrusStorageManager.ts +287 -287
- package/src/services/storage/index.ts +57 -52
- package/src/types/index.ts +13 -13
- package/src/utils/LRUCache.ts +378 -0
- package/src/utils/index.ts +76 -68
- package/src/utils/memoryIndexOnChain.ts +507 -0
- package/src/utils/rebuildIndex.ts +290 -290
- package/src/utils/rebuildIndexNode.ts +771 -424
- package/src/vector/BrowserHnswIndexService.ts +758 -758
- package/src/vector/HnswWasmService.ts +731 -679
- package/src/vector/IHnswService.ts +233 -224
- package/src/vector/NodeHnswService.ts +833 -735
- package/src/vector/VectorManager.ts +478 -478
- package/src/vector/createHnswService.ts +135 -135
- package/src/vector/index.ts +56 -56
- package/src/wallet/ContextWalletService.ts +656 -656
- package/src/wallet/MainWalletService.ts +317 -317
|
@@ -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;
|