bedrock-ts-sdk 0.0.1

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/dist/index.mjs ADDED
@@ -0,0 +1,2030 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/client/bedrock-core.ts
9
+ import { AuthenticatedAlephHttpClient as AuthenticatedAlephHttpClient2 } from "@aleph-sdk/client";
10
+ import { getAccountFromProvider, importAccountFromPrivateKey } from "@aleph-sdk/ethereum";
11
+ import { PrivateKey } from "eciesjs";
12
+ import web3 from "web3";
13
+
14
+ // src/types/errors.ts
15
+ var BedrockError = class _BedrockError extends Error {
16
+ constructor(message, code) {
17
+ super(message);
18
+ this.code = code;
19
+ this.name = "BedrockError";
20
+ Object.setPrototypeOf(this, _BedrockError.prototype);
21
+ }
22
+ };
23
+ var AuthenticationError = class _AuthenticationError extends BedrockError {
24
+ constructor(message) {
25
+ super(message, "AUTH_ERROR");
26
+ this.name = "AuthenticationError";
27
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
28
+ }
29
+ };
30
+ var EncryptionError = class _EncryptionError extends BedrockError {
31
+ constructor(message) {
32
+ super(message, "ENCRYPTION_ERROR");
33
+ this.name = "EncryptionError";
34
+ Object.setPrototypeOf(this, _EncryptionError.prototype);
35
+ }
36
+ };
37
+ var FileError = class _FileError extends BedrockError {
38
+ constructor(message) {
39
+ super(message, "FILE_ERROR");
40
+ this.name = "FileError";
41
+ Object.setPrototypeOf(this, _FileError.prototype);
42
+ }
43
+ };
44
+ var FileNotFoundError = class _FileNotFoundError extends FileError {
45
+ constructor(path) {
46
+ super(`File not found: ${path}`);
47
+ this.name = "FileNotFoundError";
48
+ this.code = "FILE_NOT_FOUND";
49
+ Object.setPrototypeOf(this, _FileNotFoundError.prototype);
50
+ }
51
+ };
52
+ var ContactError = class _ContactError extends BedrockError {
53
+ constructor(message) {
54
+ super(message, "CONTACT_ERROR");
55
+ this.name = "ContactError";
56
+ Object.setPrototypeOf(this, _ContactError.prototype);
57
+ }
58
+ };
59
+ var KnowledgeBaseError = class _KnowledgeBaseError extends BedrockError {
60
+ constructor(message) {
61
+ super(message, "KB_ERROR");
62
+ this.name = "KnowledgeBaseError";
63
+ Object.setPrototypeOf(this, _KnowledgeBaseError.prototype);
64
+ }
65
+ };
66
+ var CreditError = class _CreditError extends BedrockError {
67
+ constructor(message) {
68
+ super(message, "CREDIT_ERROR");
69
+ this.name = "CreditError";
70
+ Object.setPrototypeOf(this, _CreditError.prototype);
71
+ }
72
+ };
73
+ var NetworkError = class _NetworkError extends BedrockError {
74
+ constructor(message) {
75
+ super(message, "NETWORK_ERROR");
76
+ this.name = "NetworkError";
77
+ Object.setPrototypeOf(this, _NetworkError.prototype);
78
+ }
79
+ };
80
+ var ValidationError = class _ValidationError extends BedrockError {
81
+ constructor(message) {
82
+ super(message, "VALIDATION_ERROR");
83
+ this.name = "ValidationError";
84
+ Object.setPrototypeOf(this, _ValidationError.prototype);
85
+ }
86
+ };
87
+
88
+ // src/types/schemas.ts
89
+ import { z } from "zod";
90
+ var BEDROCK_MESSAGE = "Bedrock.im";
91
+ var SECURITY_AGGREGATE_KEY = "security";
92
+ var ALEPH_GENERAL_CHANNEL = "BEDROCK_STORAGE";
93
+ var AGGREGATE_KEYS = {
94
+ FILE_ENTRIES: "bedrock_file_entries",
95
+ CONTACTS: "bedrock_contacts",
96
+ KNOWLEDGE_BASES: "bedrock_knowledge_bases",
97
+ CREDITS: "credits"
98
+ };
99
+ var POST_TYPES = {
100
+ FILE: "bedrock_file",
101
+ PUBLIC_FILE: "bedrock_public_file"
102
+ };
103
+ var HexString64Schema = z.string().length(64).regex(/^[0-9a-f]{64}$/);
104
+ var HexString32Schema = z.string().length(32).regex(/^[0-9a-f]{32}$/);
105
+ var DatetimeSchema = z.string().datetime();
106
+ var AddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);
107
+ var FileEntrySchema = z.object({
108
+ path: z.string(),
109
+ // Encrypted path
110
+ post_hash: HexString64Schema,
111
+ shared_with: z.array(z.string()).default([])
112
+ // Public keys of contacts
113
+ });
114
+ var FileMetaEncryptedSchema = z.object({
115
+ name: z.string(),
116
+ // Encrypted filename
117
+ path: z.string(),
118
+ // Encrypted path
119
+ key: HexString64Schema,
120
+ // Encrypted AES key
121
+ iv: HexString32Schema,
122
+ // Encrypted IV
123
+ store_hash: HexString64Schema,
124
+ // Aleph STORE hash
125
+ size: z.string(),
126
+ // Encrypted size
127
+ created_at: z.string(),
128
+ // Encrypted datetime
129
+ deleted_at: z.string().nullable(),
130
+ // Encrypted datetime or null
131
+ shared_keys: z.record(z.string(), z.object({
132
+ key: z.string(),
133
+ // Encrypted key for recipient
134
+ iv: z.string()
135
+ // Encrypted IV for recipient
136
+ })).default({})
137
+ });
138
+ var FileMetaSchema = z.object({
139
+ name: z.string(),
140
+ path: z.string(),
141
+ key: HexString64Schema,
142
+ iv: HexString32Schema,
143
+ store_hash: HexString64Schema,
144
+ size: z.number(),
145
+ created_at: DatetimeSchema,
146
+ deleted_at: DatetimeSchema.nullable(),
147
+ shared_keys: z.record(z.string(), z.object({
148
+ key: HexString64Schema,
149
+ iv: HexString32Schema
150
+ })).default({})
151
+ });
152
+ var PublicFileMetaSchema = z.object({
153
+ name: z.string(),
154
+ size: z.number(),
155
+ created_at: DatetimeSchema,
156
+ store_hash: HexString64Schema,
157
+ username: z.string()
158
+ });
159
+ var ContactSchema = z.object({
160
+ name: z.string(),
161
+ address: AddressSchema,
162
+ public_key: z.string()
163
+ // Hex-encoded public key
164
+ });
165
+ var ContactsAggregateSchema = z.object({
166
+ contacts: z.array(ContactSchema).default([])
167
+ });
168
+ var KnowledgeBaseSchema = z.object({
169
+ name: z.string(),
170
+ file_paths: z.array(z.string()).default([]),
171
+ // Encrypted paths
172
+ created_at: DatetimeSchema,
173
+ updated_at: DatetimeSchema
174
+ });
175
+ var KnowledgeBasesAggregateSchema = z.object({
176
+ knowledge_bases: z.array(KnowledgeBaseSchema).default([])
177
+ });
178
+ var CreditTransactionSchema = z.object({
179
+ id: z.string(),
180
+ amount: z.number(),
181
+ type: z.enum(["top_up", "deduct"]),
182
+ timestamp: z.number(),
183
+ description: z.string(),
184
+ txHash: z.string().optional()
185
+ });
186
+ var UserCreditSchema = z.object({
187
+ balance: z.number().default(0),
188
+ transactions: z.array(CreditTransactionSchema).default([])
189
+ });
190
+ var CreditAggregateSchema = z.record(z.string(), UserCreditSchema);
191
+ var FileEntriesAggregateSchema = z.object({
192
+ files: z.array(FileEntrySchema).default([])
193
+ });
194
+ var SecurityAggregateSchema = z.object({
195
+ authorizations: z.array(z.object({
196
+ address: AddressSchema,
197
+ chain: z.string(),
198
+ channels: z.array(z.string()).optional(),
199
+ post_types: z.array(z.string()).optional(),
200
+ aggregate_keys: z.array(z.string()).optional()
201
+ }))
202
+ });
203
+
204
+ // src/client/aleph-service.ts
205
+ import { AuthenticatedAlephHttpClient } from "@aleph-sdk/client";
206
+ import { ItemType } from "@aleph-sdk/message";
207
+ import { z as z2 } from "zod";
208
+ var AlephService = class {
209
+ constructor(account, channel = ALEPH_GENERAL_CHANNEL, apiServer = "https://api2.aleph.im") {
210
+ this.account = account;
211
+ this.channel = channel;
212
+ this.subAccountClient = new AuthenticatedAlephHttpClient(account, apiServer);
213
+ }
214
+ /**
215
+ * Get the account address
216
+ */
217
+ getAddress() {
218
+ return this.account.address;
219
+ }
220
+ /**
221
+ * Get the account's public key
222
+ */
223
+ getPublicKey() {
224
+ return this.account.publicKey || "";
225
+ }
226
+ /**
227
+ * Get the underlying Aleph client
228
+ */
229
+ getClient() {
230
+ return this.subAccountClient;
231
+ }
232
+ /**
233
+ * Get the account
234
+ */
235
+ getAccount() {
236
+ return this.account;
237
+ }
238
+ // ============================================================================
239
+ // STORE operations (file storage)
240
+ // ============================================================================
241
+ /**
242
+ * Upload a file to Aleph storage
243
+ * @param fileObject - File content as Buffer or File
244
+ * @returns Store message
245
+ */
246
+ async uploadFile(fileObject) {
247
+ try {
248
+ return await this.subAccountClient.createStore({
249
+ fileObject,
250
+ storageEngine: ItemType.ipfs,
251
+ channel: this.channel
252
+ });
253
+ } catch (error) {
254
+ throw new NetworkError(`Failed to upload file: ${error.message}`);
255
+ }
256
+ }
257
+ /**
258
+ * Download a file from Aleph storage
259
+ * @param storeHash - The STORE message item_hash
260
+ * @returns File content as ArrayBuffer
261
+ */
262
+ async downloadFile(storeHash) {
263
+ try {
264
+ const ipfsHash = await this.subAccountClient.getMessage(storeHash);
265
+ const ContentSchema = z2.object({
266
+ address: z2.string(),
267
+ item_type: z2.string(),
268
+ item_hash: z2.string(),
269
+ time: z2.number()
270
+ });
271
+ const { success, data } = ContentSchema.safeParse(ipfsHash.content);
272
+ if (!success) throw new Error(`Invalid data from Aleph: ${data}`);
273
+ return this.subAccountClient.downloadFile(data.item_hash);
274
+ } catch (error) {
275
+ throw new NetworkError(`Failed to download file ${storeHash}: ${error.message}`);
276
+ }
277
+ }
278
+ /**
279
+ * Delete files from Aleph storage
280
+ * @param itemHashes - Array of item hashes to forget
281
+ * @returns Forget message
282
+ */
283
+ async deleteFiles(itemHashes) {
284
+ try {
285
+ return this.subAccountClient.forget({ hashes: itemHashes });
286
+ } catch (error) {
287
+ throw new NetworkError(`Failed to delete files: ${error.message}`);
288
+ }
289
+ }
290
+ // ============================================================================
291
+ // AGGREGATE operations (key-value storage)
292
+ // ============================================================================
293
+ async createAggregate(key, content) {
294
+ try {
295
+ return this.subAccountClient.createAggregate({
296
+ key,
297
+ content,
298
+ channel: this.channel,
299
+ address: this.account.address
300
+ });
301
+ } catch (error) {
302
+ throw new NetworkError(`Failed to create aggregate: ${error.message}`);
303
+ }
304
+ }
305
+ async fetchAggregate(key, schema, owner = this.account.address) {
306
+ try {
307
+ const unparsedData = await this.subAccountClient.fetchAggregate(owner, key);
308
+ const { success, data, error } = schema.safeParse(unparsedData);
309
+ if (!success)
310
+ throw new Error(`Invalid data from Aleph: ${error.message}, data was ${JSON.stringify(unparsedData)}`);
311
+ return data;
312
+ } catch (error) {
313
+ throw new NetworkError(`Failed to fetch aggregate: ${error.message}`);
314
+ }
315
+ }
316
+ async updateAggregate(key, schema, update_content) {
317
+ try {
318
+ const currentContent = await this.fetchAggregate(key, schema);
319
+ const newContent = await update_content(currentContent);
320
+ return await this.createAggregate(key, newContent);
321
+ } catch (error) {
322
+ throw new NetworkError(`Failed to update aggregate: ${error.message}`);
323
+ }
324
+ }
325
+ // ============================================================================
326
+ // POST operations (JSON messages)
327
+ // ============================================================================
328
+ async createPost(type, content) {
329
+ try {
330
+ return this.subAccountClient.createPost({
331
+ postType: type,
332
+ content,
333
+ channel: this.channel,
334
+ address: this.account.address
335
+ });
336
+ } catch (error) {
337
+ throw new NetworkError(`Failed to create post: ${error.message}`);
338
+ }
339
+ }
340
+ async fetchPosts(type, schema, addresses = [this.account.address], hashes = []) {
341
+ try {
342
+ return z2.array(schema).parse(
343
+ (await this.subAccountClient.getPosts({
344
+ channels: [this.channel],
345
+ types: [type],
346
+ addresses,
347
+ hashes
348
+ })).posts.map((post) => post.content)
349
+ );
350
+ } catch (error) {
351
+ throw new NetworkError(`Failed to fetch posts: ${error.message}`);
352
+ }
353
+ }
354
+ async fetchPost(type, schema, addresses = [this.account.address], hash) {
355
+ try {
356
+ return schema.parse(
357
+ (await this.subAccountClient.getPost({
358
+ channels: [this.channel],
359
+ types: [type],
360
+ addresses,
361
+ hashes: [hash]
362
+ })).content
363
+ );
364
+ } catch (error) {
365
+ throw new NetworkError(`Failed to fetch post: ${error.message}`);
366
+ }
367
+ }
368
+ async updatePost(type, hash, addresses, schema, update_content) {
369
+ try {
370
+ const currentContent = await this.fetchPost(type, schema, addresses, hash);
371
+ const newContent = await update_content(currentContent);
372
+ return await this.subAccountClient.createPost({
373
+ postType: type,
374
+ content: newContent,
375
+ ref: hash,
376
+ channel: this.channel,
377
+ address: this.account.address
378
+ });
379
+ } catch (error) {
380
+ throw new NetworkError(`Failed to update post: ${error.message}`);
381
+ }
382
+ }
383
+ };
384
+
385
+ // src/client/bedrock-core.ts
386
+ var BedrockCore = class _BedrockCore {
387
+ constructor(mainAccount, subAccount, alephService, encryptionPrivateKey, _config) {
388
+ this.mainAccount = mainAccount;
389
+ this.subAccount = subAccount;
390
+ this.alephService = alephService;
391
+ this.encryptionPrivateKey = encryptionPrivateKey;
392
+ }
393
+ /**
394
+ * Initialize from signature hash (matches Bedrock app pattern)
395
+ * @param signatureHash - Signature hash from wallet
396
+ * @param provider - EIP-1193 provider (for MetaMask/Rabby)
397
+ * @param config - Optional configuration
398
+ */
399
+ static async fromSignature(signatureHash, provider, config) {
400
+ try {
401
+ const cfg = {
402
+ channel: config?.channel || ALEPH_GENERAL_CHANNEL,
403
+ apiServer: config?.apiServer || "https://api2.aleph.im"
404
+ };
405
+ const privateKey = web3.utils.sha3(signatureHash);
406
+ if (!privateKey) {
407
+ throw new AuthenticationError("Failed to derive private key from signature");
408
+ }
409
+ const encryptionPrivateKey = PrivateKey.fromHex(privateKey);
410
+ let mainAccount;
411
+ if (provider?.id && ["io.rabby", "io.metamask"].includes(provider.id)) {
412
+ if (typeof window !== "undefined" && window.ethereum) {
413
+ mainAccount = await getAccountFromProvider(window.ethereum);
414
+ } else {
415
+ throw new AuthenticationError("window.ethereum not available");
416
+ }
417
+ } else {
418
+ mainAccount = await getAccountFromProvider(provider);
419
+ }
420
+ const subAccount = importAccountFromPrivateKey(privateKey);
421
+ await _BedrockCore.setupSecurityPermissions(mainAccount, subAccount, cfg);
422
+ const alephService = new AlephService(subAccount, cfg.channel, cfg.apiServer);
423
+ return new _BedrockCore(mainAccount, subAccount, alephService, encryptionPrivateKey, cfg);
424
+ } catch (error) {
425
+ throw new AuthenticationError(`Failed to initialize from signature: ${error.message}`);
426
+ }
427
+ }
428
+ /**
429
+ * Create BedrockCore from a private key (for testing/CLI)
430
+ * @param privateKey - Ethereum private key (hex string with or without 0x prefix)
431
+ * @param config - Optional configuration
432
+ */
433
+ static async fromPrivateKey(privateKey, config) {
434
+ try {
435
+ const cfg = {
436
+ channel: config?.channel || ALEPH_GENERAL_CHANNEL,
437
+ apiServer: config?.apiServer || "https://api2.aleph.im"
438
+ };
439
+ const key = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
440
+ const mainAccount = importAccountFromPrivateKey(key);
441
+ const signatureHash = web3.utils.sha3(key + BEDROCK_MESSAGE);
442
+ if (!signatureHash) {
443
+ throw new AuthenticationError("Failed to derive signature");
444
+ }
445
+ const subPrivateKey = web3.utils.sha3(signatureHash);
446
+ if (!subPrivateKey) {
447
+ throw new AuthenticationError("Failed to derive sub-account key");
448
+ }
449
+ const encryptionPrivateKey = PrivateKey.fromHex(subPrivateKey);
450
+ const subAccount = importAccountFromPrivateKey(subPrivateKey);
451
+ await _BedrockCore.setupSecurityPermissions(mainAccount, subAccount, cfg);
452
+ const alephService = new AlephService(subAccount, cfg.channel, cfg.apiServer);
453
+ return new _BedrockCore(mainAccount, subAccount, alephService, encryptionPrivateKey, cfg);
454
+ } catch (error) {
455
+ throw new AuthenticationError(`Failed to import account: ${error.message}`);
456
+ }
457
+ }
458
+ /**
459
+ * Create BedrockCore from a wallet provider (e.g., MetaMask)
460
+ * This will automatically request signature from the user
461
+ * @param provider - EIP-1193 provider
462
+ * @param config - Optional configuration
463
+ */
464
+ static async fromProvider(provider, config) {
465
+ try {
466
+ const accounts = await provider.request({ method: "eth_requestAccounts" });
467
+ if (!accounts || accounts.length === 0) {
468
+ throw new AuthenticationError("No accounts found");
469
+ }
470
+ const signature = await provider.request({
471
+ method: "personal_sign",
472
+ params: [BEDROCK_MESSAGE, accounts[0]]
473
+ });
474
+ return await _BedrockCore.fromSignature(signature, provider, config);
475
+ } catch (error) {
476
+ throw new AuthenticationError(`Failed to connect to provider: ${error.message}`);
477
+ }
478
+ }
479
+ /**
480
+ * Get the main account
481
+ */
482
+ getMainAccount() {
483
+ return this.mainAccount;
484
+ }
485
+ /**
486
+ * Get the sub-account
487
+ */
488
+ getSubAccount() {
489
+ return this.subAccount;
490
+ }
491
+ /**
492
+ * Get the AlephService instance
493
+ */
494
+ getAlephService() {
495
+ return this.alephService;
496
+ }
497
+ /**
498
+ * Get the encryption key
499
+ */
500
+ getEncryptionKey() {
501
+ return Buffer.from(this.encryptionPrivateKey.secret);
502
+ }
503
+ /**
504
+ * Get the encryption private key
505
+ */
506
+ getEncryptionPrivateKey() {
507
+ return this.encryptionPrivateKey;
508
+ }
509
+ /**
510
+ * Get the main account's address
511
+ */
512
+ getMainAddress() {
513
+ return this.mainAccount.address;
514
+ }
515
+ /**
516
+ * Get the sub-account's address
517
+ */
518
+ getSubAddress() {
519
+ return this.subAccount.address;
520
+ }
521
+ /**
522
+ * Get the main account's public key
523
+ */
524
+ getPublicKey() {
525
+ return this.mainAccount.publicKey || "";
526
+ }
527
+ /**
528
+ * Get the sub-account's private key (as hex string)
529
+ */
530
+ getSubAccountPrivateKey() {
531
+ return this.encryptionPrivateKey.toHex();
532
+ }
533
+ // ============================================================================
534
+ // Static helper methods
535
+ // ============================================================================
536
+ /**
537
+ * Setup security permissions (matches Bedrock app pattern)
538
+ */
539
+ static async setupSecurityPermissions(mainAccount, subAccount, config) {
540
+ try {
541
+ const accountClient = new AuthenticatedAlephHttpClient2(mainAccount, config.apiServer);
542
+ try {
543
+ const securitySettings = await accountClient.fetchAggregate(
544
+ mainAccount.address,
545
+ SECURITY_AGGREGATE_KEY
546
+ );
547
+ const authorizations = securitySettings?.authorizations || [];
548
+ const isAuthorized = authorizations.find(
549
+ (auth) => auth.address === subAccount.address && auth.types === void 0 && auth.channels?.includes(config.channel)
550
+ );
551
+ if (!isAuthorized) {
552
+ const oldAuthorizations = authorizations.filter((a) => a.address !== subAccount.address);
553
+ await accountClient.createAggregate({
554
+ key: SECURITY_AGGREGATE_KEY,
555
+ content: {
556
+ authorizations: [
557
+ ...oldAuthorizations,
558
+ {
559
+ address: subAccount.address,
560
+ channels: [config.channel]
561
+ }
562
+ ]
563
+ }
564
+ });
565
+ }
566
+ } catch (_error) {
567
+ await accountClient.createAggregate({
568
+ key: SECURITY_AGGREGATE_KEY,
569
+ content: {
570
+ authorizations: [
571
+ {
572
+ address: subAccount.address,
573
+ channels: [config.channel]
574
+ }
575
+ ]
576
+ }
577
+ });
578
+ }
579
+ } catch (error) {
580
+ throw new AuthenticationError(`Failed to setup security permissions: ${error.message}`);
581
+ }
582
+ }
583
+ };
584
+
585
+ // src/crypto/encryption.ts
586
+ import { encrypt as eciesEncrypt, decrypt as eciesDecrypt } from "eciesjs";
587
+ var CryptoUtils = class {
588
+ /**
589
+ * Get crypto implementation (Node.js or browser)
590
+ */
591
+ static getCrypto() {
592
+ if (this.isBrowser) {
593
+ return window.crypto;
594
+ }
595
+ const nodeCrypto = __require("crypto");
596
+ return nodeCrypto.webcrypto || nodeCrypto;
597
+ }
598
+ /**
599
+ * Generate random bytes
600
+ */
601
+ static getRandomBytes(length) {
602
+ const crypto = this.getCrypto();
603
+ const bytes = new Uint8Array(length);
604
+ if (this.isBrowser) {
605
+ crypto.getRandomValues(bytes);
606
+ } else {
607
+ const nodeCrypto = __require("crypto");
608
+ const randomBytes = nodeCrypto.randomBytes(length);
609
+ bytes.set(randomBytes);
610
+ }
611
+ return bytes;
612
+ }
613
+ /**
614
+ * Convert buffer to hex string
615
+ */
616
+ static bufferToHex(buffer) {
617
+ return Buffer.from(buffer).toString("hex");
618
+ }
619
+ /**
620
+ * Convert hex string to buffer
621
+ */
622
+ static hexToBuffer(hex) {
623
+ return Buffer.from(hex, "hex");
624
+ }
625
+ /**
626
+ * Hash using SHA-256
627
+ */
628
+ static async sha256(data) {
629
+ const bytes = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
630
+ if (this.isBrowser) {
631
+ const crypto = this.getCrypto();
632
+ const hashBuffer = await crypto.subtle.digest("SHA-256", bytes);
633
+ return Buffer.from(hashBuffer);
634
+ } else {
635
+ const nodeCrypto = __require("crypto");
636
+ return nodeCrypto.createHash("sha256").update(bytes).digest();
637
+ }
638
+ }
639
+ };
640
+ CryptoUtils.isBrowser = typeof window !== "undefined" && typeof window.crypto !== "undefined";
641
+ var EncryptionService = class {
642
+ /**
643
+ * Generate a random encryption key (32 bytes for AES-256)
644
+ */
645
+ static generateKey() {
646
+ return Buffer.from(CryptoUtils.getRandomBytes(32));
647
+ }
648
+ /**
649
+ * Generate a random initialization vector (16 bytes for AES)
650
+ */
651
+ static generateIv() {
652
+ return Buffer.from(CryptoUtils.getRandomBytes(16));
653
+ }
654
+ /**
655
+ * Encrypt data using AES-256-CBC
656
+ * @param data - Data to encrypt
657
+ * @param key - 32-byte encryption key
658
+ * @param iv - 16-byte initialization vector
659
+ * @returns Hex-encoded encrypted data
660
+ */
661
+ static async encrypt(data, key, iv) {
662
+ try {
663
+ if (key.length !== 32) {
664
+ throw new EncryptionError("Key must be 32 bytes for AES-256");
665
+ }
666
+ if (iv.length !== 16) {
667
+ throw new EncryptionError("IV must be 16 bytes");
668
+ }
669
+ if (CryptoUtils.isBrowser) {
670
+ return await this.encryptBrowser(data, key, iv);
671
+ } else {
672
+ return this.encryptNode(data, key, iv);
673
+ }
674
+ } catch (error) {
675
+ throw new EncryptionError(`Encryption failed: ${error.message}`);
676
+ }
677
+ }
678
+ /**
679
+ * Decrypt data using AES-256-CBC
680
+ * @param encryptedData - Hex-encoded encrypted data
681
+ * @param key - 32-byte encryption key
682
+ * @param iv - 16-byte initialization vector
683
+ * @returns Decrypted string
684
+ */
685
+ static async decrypt(encryptedData, key, iv) {
686
+ try {
687
+ if (key.length !== 32) {
688
+ throw new EncryptionError("Key must be 32 bytes for AES-256");
689
+ }
690
+ if (iv.length !== 16) {
691
+ throw new EncryptionError("IV must be 16 bytes");
692
+ }
693
+ if (CryptoUtils.isBrowser) {
694
+ return await this.decryptBrowser(encryptedData, key, iv);
695
+ } else {
696
+ return this.decryptNode(encryptedData, key, iv);
697
+ }
698
+ } catch (error) {
699
+ throw new EncryptionError(`Decryption failed: ${error.message}`);
700
+ }
701
+ }
702
+ /**
703
+ * Encrypt file buffer using AES-256-CBC
704
+ * @param fileBuffer - File data as Buffer or ArrayBuffer
705
+ * @param key - 32-byte encryption key
706
+ * @param iv - 16-byte initialization vector
707
+ * @returns Encrypted file buffer
708
+ */
709
+ static async encryptFile(fileBuffer, key, iv) {
710
+ try {
711
+ const buffer = Buffer.isBuffer(fileBuffer) ? fileBuffer : Buffer.from(fileBuffer);
712
+ if (CryptoUtils.isBrowser) {
713
+ return await this.encryptFileBrowser(buffer, key, iv);
714
+ } else {
715
+ return this.encryptFileNode(buffer, key, iv);
716
+ }
717
+ } catch (error) {
718
+ throw new EncryptionError(`File encryption failed: ${error.message}`);
719
+ }
720
+ }
721
+ /**
722
+ * Decrypt file buffer using AES-256-CBC
723
+ * @param encryptedBuffer - Encrypted file data
724
+ * @param key - 32-byte encryption key
725
+ * @param iv - 16-byte initialization vector
726
+ * @returns Decrypted file buffer
727
+ */
728
+ static async decryptFile(encryptedBuffer, key, iv) {
729
+ try {
730
+ const buffer = Buffer.isBuffer(encryptedBuffer) ? encryptedBuffer : Buffer.from(encryptedBuffer);
731
+ if (CryptoUtils.isBrowser) {
732
+ return await this.decryptFileBrowser(buffer, key, iv);
733
+ } else {
734
+ return this.decryptFileNode(buffer, key, iv);
735
+ }
736
+ } catch (error) {
737
+ throw new EncryptionError(`File decryption failed: ${error.message}`);
738
+ }
739
+ }
740
+ /**
741
+ * Encrypt data using ECIES (Elliptic Curve Integrated Encryption Scheme)
742
+ * @param data - Data to encrypt
743
+ * @param publicKey - Recipient's public key (hex or Buffer)
744
+ * @returns Hex-encoded encrypted data
745
+ */
746
+ static encryptEcies(data, publicKey) {
747
+ try {
748
+ const pubKeyBuffer = typeof publicKey === "string" ? CryptoUtils.hexToBuffer(publicKey) : publicKey;
749
+ const dataBuffer = Buffer.from(data, "utf-8");
750
+ const encrypted = eciesEncrypt(pubKeyBuffer, dataBuffer);
751
+ return encrypted.toString("hex");
752
+ } catch (error) {
753
+ throw new EncryptionError(`ECIES encryption failed: ${error.message}`);
754
+ }
755
+ }
756
+ /**
757
+ * Decrypt data using ECIES
758
+ * @param encryptedData - Hex-encoded encrypted data
759
+ * @param privateKey - Recipient's private key (hex or Buffer)
760
+ * @returns Decrypted string
761
+ */
762
+ static decryptEcies(encryptedData, privateKey) {
763
+ try {
764
+ const privKeyBuffer = typeof privateKey === "string" ? CryptoUtils.hexToBuffer(privateKey) : privateKey;
765
+ const encryptedBuffer = CryptoUtils.hexToBuffer(encryptedData);
766
+ const decrypted = eciesDecrypt(privKeyBuffer, encryptedBuffer);
767
+ return decrypted.toString("utf-8");
768
+ } catch (error) {
769
+ throw new EncryptionError(`ECIES decryption failed: ${error.message}`);
770
+ }
771
+ }
772
+ /**
773
+ * Hash data using SHA-256
774
+ */
775
+ static async hash(data) {
776
+ const hashBuffer = await CryptoUtils.sha256(data);
777
+ return CryptoUtils.bufferToHex(hashBuffer);
778
+ }
779
+ // ============================================================================
780
+ // Node.js implementations
781
+ // ============================================================================
782
+ static encryptNode(data, key, iv) {
783
+ const crypto = __require("crypto");
784
+ const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
785
+ let encrypted = cipher.update(data, "utf-8", "hex");
786
+ encrypted += cipher.final("hex");
787
+ return encrypted;
788
+ }
789
+ static decryptNode(encryptedData, key, iv) {
790
+ const crypto = __require("crypto");
791
+ const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
792
+ let decrypted = decipher.update(encryptedData, "hex", "utf-8");
793
+ decrypted += decipher.final("utf-8");
794
+ return decrypted;
795
+ }
796
+ static encryptFileNode(buffer, key, iv) {
797
+ const crypto = __require("crypto");
798
+ const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
799
+ return Buffer.concat([cipher.update(buffer), cipher.final()]);
800
+ }
801
+ static decryptFileNode(buffer, key, iv) {
802
+ const crypto = __require("crypto");
803
+ const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
804
+ return Buffer.concat([decipher.update(buffer), decipher.final()]);
805
+ }
806
+ // ============================================================================
807
+ // Browser implementations
808
+ // ============================================================================
809
+ static async encryptBrowser(data, key, iv) {
810
+ const crypto = CryptoUtils.getCrypto();
811
+ const cryptoKey = await crypto.subtle.importKey(
812
+ "raw",
813
+ key,
814
+ { name: "AES-CBC" },
815
+ false,
816
+ ["encrypt"]
817
+ );
818
+ const dataBuffer = Buffer.from(data, "utf-8");
819
+ const encrypted = await crypto.subtle.encrypt(
820
+ { name: "AES-CBC", iv },
821
+ cryptoKey,
822
+ dataBuffer
823
+ );
824
+ return CryptoUtils.bufferToHex(Buffer.from(encrypted));
825
+ }
826
+ static async decryptBrowser(encryptedData, key, iv) {
827
+ const crypto = CryptoUtils.getCrypto();
828
+ const cryptoKey = await crypto.subtle.importKey(
829
+ "raw",
830
+ key,
831
+ { name: "AES-CBC" },
832
+ false,
833
+ ["decrypt"]
834
+ );
835
+ const encryptedBuffer = CryptoUtils.hexToBuffer(encryptedData);
836
+ const decrypted = await crypto.subtle.decrypt(
837
+ { name: "AES-CBC", iv },
838
+ cryptoKey,
839
+ encryptedBuffer
840
+ );
841
+ return Buffer.from(decrypted).toString("utf-8");
842
+ }
843
+ static async encryptFileBrowser(buffer, key, iv) {
844
+ const crypto = CryptoUtils.getCrypto();
845
+ const cryptoKey = await crypto.subtle.importKey(
846
+ "raw",
847
+ key,
848
+ { name: "AES-CBC" },
849
+ false,
850
+ ["encrypt"]
851
+ );
852
+ const encrypted = await crypto.subtle.encrypt(
853
+ { name: "AES-CBC", iv },
854
+ cryptoKey,
855
+ buffer
856
+ );
857
+ return Buffer.from(encrypted);
858
+ }
859
+ static async decryptFileBrowser(buffer, key, iv) {
860
+ const crypto = CryptoUtils.getCrypto();
861
+ const cryptoKey = await crypto.subtle.importKey(
862
+ "raw",
863
+ key,
864
+ { name: "AES-CBC" },
865
+ false,
866
+ ["decrypt"]
867
+ );
868
+ const decrypted = await crypto.subtle.decrypt(
869
+ { name: "AES-CBC", iv },
870
+ cryptoKey,
871
+ buffer
872
+ );
873
+ return Buffer.from(decrypted);
874
+ }
875
+ };
876
+
877
+ // src/services/file-service.ts
878
+ import { AlephHttpClient } from "@aleph-sdk/client";
879
+ var FileService = class {
880
+ constructor(core) {
881
+ this.core = core;
882
+ }
883
+ /**
884
+ * Initialize file entries aggregate if it doesn't exist
885
+ */
886
+ async setup() {
887
+ const aleph = this.core.getAlephService();
888
+ try {
889
+ await aleph.fetchAggregate(
890
+ AGGREGATE_KEYS.FILE_ENTRIES,
891
+ FileEntriesAggregateSchema
892
+ );
893
+ } catch {
894
+ await aleph.createAggregate(AGGREGATE_KEYS.FILE_ENTRIES, { files: [] });
895
+ }
896
+ }
897
+ /**
898
+ * Upload files with encryption
899
+ * @param files - Array of files to upload
900
+ * @param directoryPath - Optional directory path prefix
901
+ * @returns Array of uploaded file info
902
+ */
903
+ async uploadFiles(files, directoryPath = "") {
904
+ const aleph = this.core.getAlephService();
905
+ const publicKey = this.core.getPublicKey();
906
+ const uploadedFiles = [];
907
+ try {
908
+ for (const file of files) {
909
+ const key = EncryptionService.generateKey();
910
+ const iv = EncryptionService.generateIv();
911
+ let fileBuffer;
912
+ if (file.content instanceof Buffer) {
913
+ fileBuffer = file.content;
914
+ } else {
915
+ const arrayBuffer = await file.content.arrayBuffer();
916
+ fileBuffer = Buffer.from(arrayBuffer);
917
+ }
918
+ const encryptedContent = await EncryptionService.encryptFile(fileBuffer, key, iv);
919
+ const storeResult = await aleph.uploadFile(encryptedContent);
920
+ const fullPath = directoryPath ? `${directoryPath}/${file.path}` : file.path;
921
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
922
+ const fileMeta = {
923
+ name: file.name,
924
+ path: fullPath,
925
+ key: key.toString("hex"),
926
+ iv: iv.toString("hex"),
927
+ store_hash: storeResult.item_hash,
928
+ size: fileBuffer.length,
929
+ created_at: createdAt,
930
+ deleted_at: null,
931
+ shared_keys: {}
932
+ };
933
+ const encryptedMeta = await this.encryptFileMeta(fileMeta);
934
+ const postResult = await aleph.createPost(POST_TYPES.FILE, encryptedMeta);
935
+ const encryptedPath = EncryptionService.encryptEcies(fullPath, publicKey);
936
+ const fileEntry = {
937
+ path: encryptedPath,
938
+ post_hash: postResult.item_hash,
939
+ shared_with: []
940
+ };
941
+ uploadedFiles.push({ ...fileEntry, ...fileMeta });
942
+ }
943
+ await this.saveFileEntries(uploadedFiles);
944
+ return uploadedFiles;
945
+ } catch (error) {
946
+ throw new FileError(`Failed to upload files: ${error.message}`);
947
+ }
948
+ }
949
+ /**
950
+ * Download and decrypt a file
951
+ * @param fileInfo - File information
952
+ * @returns Decrypted file buffer
953
+ */
954
+ async downloadFile(fileInfo) {
955
+ const aleph = this.core.getAlephService();
956
+ try {
957
+ const key = Buffer.from(fileInfo.key, "hex");
958
+ const iv = Buffer.from(fileInfo.iv, "hex");
959
+ const encryptedContent = await aleph.downloadFile(fileInfo.store_hash);
960
+ const decryptedContent = await EncryptionService.decryptFile(encryptedContent, key, iv);
961
+ return decryptedContent;
962
+ } catch (error) {
963
+ throw new FileError(`Failed to download file: ${error.message}`);
964
+ }
965
+ }
966
+ /**
967
+ * Fetch all file entries
968
+ */
969
+ async fetchFileEntries() {
970
+ const aleph = this.core.getAlephService();
971
+ try {
972
+ const aggregate = await aleph.fetchAggregate(
973
+ AGGREGATE_KEYS.FILE_ENTRIES,
974
+ FileEntriesAggregateSchema
975
+ );
976
+ return aggregate.files;
977
+ } catch (error) {
978
+ throw new FileError(`Failed to fetch file entries: ${error.message}`);
979
+ }
980
+ }
981
+ /**
982
+ * Fetch file metadata from entries
983
+ * @param entries - File entries
984
+ * @param owner - Optional owner address
985
+ * @returns Array of full file info
986
+ */
987
+ async fetchFilesMetaFromEntries(entries, owner) {
988
+ const aleph = this.core.getAlephService();
989
+ const privateKey = owner ? void 0 : this.core.getSubAccountPrivateKey();
990
+ const files = [];
991
+ try {
992
+ for (const entry of entries) {
993
+ try {
994
+ const encryptedMeta = await aleph.fetchPost(
995
+ POST_TYPES.FILE,
996
+ FileMetaEncryptedSchema,
997
+ owner ? [owner] : void 0,
998
+ entry.post_hash
999
+ );
1000
+ const decryptedMeta = await this.decryptFileMeta(encryptedMeta, privateKey);
1001
+ files.push({
1002
+ ...entry,
1003
+ ...decryptedMeta
1004
+ });
1005
+ } catch (error) {
1006
+ console.warn(`Failed to fetch metadata for ${entry.post_hash}:`, error);
1007
+ }
1008
+ }
1009
+ return files;
1010
+ } catch (error) {
1011
+ throw new FileError(`Failed to fetch files metadata: ${error.message}`);
1012
+ }
1013
+ }
1014
+ /**
1015
+ * List all files
1016
+ * @param includeDeleted - Include soft-deleted files
1017
+ */
1018
+ async listFiles(includeDeleted = false) {
1019
+ const entries = await this.fetchFileEntries();
1020
+ const files = await this.fetchFilesMetaFromEntries(entries);
1021
+ if (!includeDeleted) {
1022
+ return files.filter((f) => !f.deleted_at);
1023
+ }
1024
+ return files;
1025
+ }
1026
+ /**
1027
+ * Get a file by path
1028
+ * @param path - File path
1029
+ */
1030
+ async getFile(path) {
1031
+ const files = await this.listFiles(true);
1032
+ const file = files.find((f) => f.path === path);
1033
+ if (!file) {
1034
+ throw new FileNotFoundError(path);
1035
+ }
1036
+ return file;
1037
+ }
1038
+ /**
1039
+ * Soft delete files
1040
+ * @param filePaths - Paths of files to delete
1041
+ * @param deletionDate - Optional deletion date
1042
+ */
1043
+ async softDeleteFiles(filePaths, deletionDate) {
1044
+ const aleph = this.core.getAlephService();
1045
+ const deletedAt = (deletionDate || /* @__PURE__ */ new Date()).toISOString();
1046
+ try {
1047
+ for (const path of filePaths) {
1048
+ const file = await this.getFile(path);
1049
+ await aleph.updatePost(
1050
+ POST_TYPES.FILE,
1051
+ file.post_hash,
1052
+ [this.core.getMainAddress()],
1053
+ FileMetaEncryptedSchema,
1054
+ async (encryptedMeta) => {
1055
+ const decryptedMeta = await this.decryptFileMeta(encryptedMeta);
1056
+ decryptedMeta.deleted_at = deletedAt;
1057
+ return await this.encryptFileMeta(decryptedMeta);
1058
+ }
1059
+ );
1060
+ }
1061
+ } catch (error) {
1062
+ throw new FileError(`Failed to soft delete files: ${error.message}`);
1063
+ }
1064
+ }
1065
+ /**
1066
+ * Restore soft-deleted files
1067
+ * @param filePaths - Paths of files to restore
1068
+ */
1069
+ async restoreFiles(filePaths) {
1070
+ const aleph = this.core.getAlephService();
1071
+ try {
1072
+ for (const path of filePaths) {
1073
+ const file = await this.getFile(path);
1074
+ await aleph.updatePost(
1075
+ POST_TYPES.FILE,
1076
+ file.post_hash,
1077
+ [this.core.getMainAddress()],
1078
+ FileMetaEncryptedSchema,
1079
+ async (encryptedMeta) => {
1080
+ const decryptedMeta = await this.decryptFileMeta(encryptedMeta);
1081
+ decryptedMeta.deleted_at = null;
1082
+ return await this.encryptFileMeta(decryptedMeta);
1083
+ }
1084
+ );
1085
+ }
1086
+ } catch (error) {
1087
+ throw new FileError(`Failed to restore files: ${error.message}`);
1088
+ }
1089
+ }
1090
+ /**
1091
+ * Hard delete files (permanently remove from Aleph)
1092
+ * @param filePaths - Paths of files to delete
1093
+ */
1094
+ async hardDeleteFiles(filePaths) {
1095
+ const aleph = this.core.getAlephService();
1096
+ try {
1097
+ const files = await Promise.all(filePaths.map((path) => this.getFile(path)));
1098
+ await aleph.updateAggregate(
1099
+ AGGREGATE_KEYS.FILE_ENTRIES,
1100
+ FileEntriesAggregateSchema,
1101
+ async (aggregate) => ({
1102
+ files: aggregate.files.filter(
1103
+ (entry) => !files.some((f) => f.post_hash === entry.post_hash)
1104
+ )
1105
+ })
1106
+ );
1107
+ const hashesToForget = files.flatMap((f) => [f.store_hash, f.post_hash]);
1108
+ await aleph.deleteFiles(hashesToForget);
1109
+ } catch (error) {
1110
+ throw new FileError(`Failed to hard delete files: ${error.message}`);
1111
+ }
1112
+ }
1113
+ /**
1114
+ * Move/rename files
1115
+ * @param moves - Array of {oldPath, newPath} objects
1116
+ */
1117
+ async moveFiles(moves) {
1118
+ const aleph = this.core.getAlephService();
1119
+ const publicKey = this.core.getPublicKey();
1120
+ try {
1121
+ for (const { oldPath, newPath } of moves) {
1122
+ const file = await this.getFile(oldPath);
1123
+ await aleph.updatePost(
1124
+ POST_TYPES.FILE,
1125
+ file.post_hash,
1126
+ [this.core.getMainAddress()],
1127
+ FileMetaEncryptedSchema,
1128
+ async (encryptedMeta) => {
1129
+ const decryptedMeta = await this.decryptFileMeta(encryptedMeta);
1130
+ decryptedMeta.path = newPath;
1131
+ decryptedMeta.name = newPath.split("/").pop() || newPath;
1132
+ return await this.encryptFileMeta(decryptedMeta);
1133
+ }
1134
+ );
1135
+ const newEncryptedPath = EncryptionService.encryptEcies(newPath, publicKey);
1136
+ await aleph.updateAggregate(
1137
+ AGGREGATE_KEYS.FILE_ENTRIES,
1138
+ FileEntriesAggregateSchema,
1139
+ async (aggregate) => ({
1140
+ files: aggregate.files.map(
1141
+ (entry) => entry.post_hash === file.post_hash ? { ...entry, path: newEncryptedPath } : entry
1142
+ )
1143
+ })
1144
+ );
1145
+ }
1146
+ } catch (error) {
1147
+ throw new FileError(`Failed to move files: ${error.message}`);
1148
+ }
1149
+ }
1150
+ /**
1151
+ * Duplicate a file
1152
+ * @param sourcePath - Source file path
1153
+ * @param newPath - New file path
1154
+ */
1155
+ async duplicateFile(sourcePath, newPath) {
1156
+ try {
1157
+ const sourceFile = await this.getFile(sourcePath);
1158
+ const content = await this.downloadFile(sourceFile);
1159
+ const [newFile] = await this.uploadFiles([{
1160
+ name: newPath.split("/").pop() || newPath,
1161
+ path: newPath,
1162
+ content
1163
+ }]);
1164
+ return newFile;
1165
+ } catch (error) {
1166
+ throw new FileError(`Failed to duplicate file: ${error.message}`);
1167
+ }
1168
+ }
1169
+ /**
1170
+ * Share a file with a contact
1171
+ * @param filePath - File path
1172
+ * @param contactPublicKey - Contact's public key
1173
+ */
1174
+ async shareFile(filePath, contactPublicKey) {
1175
+ const aleph = this.core.getAlephService();
1176
+ try {
1177
+ const file = await this.getFile(filePath);
1178
+ const encryptedKey = EncryptionService.encryptEcies(file.key, contactPublicKey);
1179
+ const encryptedIv = EncryptionService.encryptEcies(file.iv, contactPublicKey);
1180
+ await aleph.updatePost(
1181
+ POST_TYPES.FILE,
1182
+ file.post_hash,
1183
+ [this.core.getMainAddress()],
1184
+ FileMetaEncryptedSchema,
1185
+ async (encryptedMeta) => {
1186
+ const decryptedMeta = await this.decryptFileMeta(encryptedMeta);
1187
+ decryptedMeta.shared_keys[contactPublicKey] = {
1188
+ key: encryptedKey,
1189
+ iv: encryptedIv
1190
+ };
1191
+ return await this.encryptFileMeta(decryptedMeta);
1192
+ }
1193
+ );
1194
+ await aleph.updateAggregate(
1195
+ AGGREGATE_KEYS.FILE_ENTRIES,
1196
+ FileEntriesAggregateSchema,
1197
+ async (aggregate) => ({
1198
+ files: aggregate.files.map(
1199
+ (entry) => entry.post_hash === file.post_hash ? { ...entry, shared_with: [.../* @__PURE__ */ new Set([...entry.shared_with, contactPublicKey])] } : entry
1200
+ )
1201
+ })
1202
+ );
1203
+ } catch (error) {
1204
+ throw new FileError(`Failed to share file: ${error.message}`);
1205
+ }
1206
+ }
1207
+ /**
1208
+ * Unshare a file with a contact
1209
+ * @param filePath - File path
1210
+ * @param contactPublicKey - Contact's public key
1211
+ */
1212
+ async unshareFile(filePath, contactPublicKey) {
1213
+ const aleph = this.core.getAlephService();
1214
+ try {
1215
+ const file = await this.getFile(filePath);
1216
+ await aleph.updatePost(
1217
+ POST_TYPES.FILE,
1218
+ file.post_hash,
1219
+ [this.core.getMainAddress()],
1220
+ FileMetaEncryptedSchema,
1221
+ async (encryptedMeta) => {
1222
+ const decryptedMeta = await this.decryptFileMeta(encryptedMeta);
1223
+ delete decryptedMeta.shared_keys[contactPublicKey];
1224
+ return await this.encryptFileMeta(decryptedMeta);
1225
+ }
1226
+ );
1227
+ await aleph.updateAggregate(
1228
+ AGGREGATE_KEYS.FILE_ENTRIES,
1229
+ FileEntriesAggregateSchema,
1230
+ async (aggregate) => ({
1231
+ files: aggregate.files.map(
1232
+ (entry) => entry.post_hash === file.post_hash ? { ...entry, shared_with: entry.shared_with.filter((pk) => pk !== contactPublicKey) } : entry
1233
+ )
1234
+ })
1235
+ );
1236
+ } catch (error) {
1237
+ throw new FileError(`Failed to unshare file: ${error.message}`);
1238
+ }
1239
+ }
1240
+ /**
1241
+ * Share a file publicly (unencrypted, anyone can access)
1242
+ * @param fileInfo - File to share publicly
1243
+ * @param username - Username for attribution
1244
+ * @returns Public post hash for sharing
1245
+ */
1246
+ async shareFilePublicly(fileInfo, username) {
1247
+ const aleph = this.core.getAlephService();
1248
+ try {
1249
+ const decryptedContent = await this.downloadFile(fileInfo);
1250
+ const storeResult = await aleph.uploadFile(decryptedContent);
1251
+ const publicMeta = {
1252
+ name: fileInfo.name,
1253
+ size: fileInfo.size,
1254
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1255
+ store_hash: storeResult.item_hash,
1256
+ username
1257
+ };
1258
+ const postResult = await aleph.createPost(POST_TYPES.PUBLIC_FILE, publicMeta);
1259
+ return postResult.item_hash;
1260
+ } catch (error) {
1261
+ throw new FileError(`Failed to share file publicly: ${error.message}`);
1262
+ }
1263
+ }
1264
+ /**
1265
+ * Fetch public file metadata (static - no auth required)
1266
+ * @param postHash - Public post hash
1267
+ * @returns Public file metadata or null if not found
1268
+ */
1269
+ static async fetchPublicFileMeta(postHash) {
1270
+ try {
1271
+ const client = new AlephHttpClient("https://api2.aleph.im");
1272
+ const post = await client.getPost({
1273
+ channels: [ALEPH_GENERAL_CHANNEL],
1274
+ types: [POST_TYPES.PUBLIC_FILE],
1275
+ hashes: [postHash]
1276
+ });
1277
+ return PublicFileMetaSchema.parse(post.content);
1278
+ } catch {
1279
+ return null;
1280
+ }
1281
+ }
1282
+ /**
1283
+ * Download public file (static - no auth required)
1284
+ * @param storeHash - Store hash from public metadata
1285
+ * @returns File content as ArrayBuffer
1286
+ */
1287
+ static async downloadPublicFile(storeHash) {
1288
+ try {
1289
+ const client = new AlephHttpClient("https://api2.aleph.im");
1290
+ return await client.downloadFile(storeHash);
1291
+ } catch (error) {
1292
+ throw new FileError(`Failed to download public file: ${error.message}`);
1293
+ }
1294
+ }
1295
+ // ============================================================================
1296
+ // Private helper methods
1297
+ // ============================================================================
1298
+ async saveFileEntries(files) {
1299
+ const aleph = this.core.getAlephService();
1300
+ await aleph.updateAggregate(
1301
+ AGGREGATE_KEYS.FILE_ENTRIES,
1302
+ FileEntriesAggregateSchema,
1303
+ async (aggregate) => {
1304
+ const newEntries = files.map((f) => ({
1305
+ path: f.path,
1306
+ post_hash: f.post_hash,
1307
+ shared_with: f.shared_with || []
1308
+ }));
1309
+ return { files: [...aggregate.files, ...newEntries] };
1310
+ }
1311
+ );
1312
+ }
1313
+ async encryptFileMeta(meta) {
1314
+ const key = this.core.getEncryptionKey();
1315
+ const iv = EncryptionService.generateIv();
1316
+ const publicKey = this.core.getPublicKey();
1317
+ return {
1318
+ name: await EncryptionService.encrypt(meta.name, key, iv),
1319
+ path: EncryptionService.encryptEcies(meta.path, publicKey),
1320
+ key: EncryptionService.encryptEcies(meta.key, publicKey),
1321
+ iv: EncryptionService.encryptEcies(meta.iv, publicKey),
1322
+ store_hash: meta.store_hash,
1323
+ size: await EncryptionService.encrypt(meta.size.toString(), key, iv),
1324
+ created_at: await EncryptionService.encrypt(meta.created_at, key, iv),
1325
+ deleted_at: meta.deleted_at ? await EncryptionService.encrypt(meta.deleted_at, key, iv) : null,
1326
+ shared_keys: meta.shared_keys
1327
+ };
1328
+ }
1329
+ async decryptFileMeta(encryptedMeta, privateKey) {
1330
+ const key = this.core.getEncryptionKey();
1331
+ const iv = EncryptionService.generateIv();
1332
+ const privKey = privateKey || this.core.getSubAccountPrivateKey();
1333
+ if (!privKey) {
1334
+ throw new EncryptionError("Private key not available");
1335
+ }
1336
+ return {
1337
+ name: await EncryptionService.decrypt(encryptedMeta.name, key, iv),
1338
+ path: EncryptionService.decryptEcies(encryptedMeta.path, privKey),
1339
+ key: EncryptionService.decryptEcies(encryptedMeta.key, privKey),
1340
+ iv: EncryptionService.decryptEcies(encryptedMeta.iv, privKey),
1341
+ store_hash: encryptedMeta.store_hash,
1342
+ size: parseInt(await EncryptionService.decrypt(encryptedMeta.size, key, iv)),
1343
+ created_at: await EncryptionService.decrypt(encryptedMeta.created_at, key, iv),
1344
+ deleted_at: encryptedMeta.deleted_at ? await EncryptionService.decrypt(encryptedMeta.deleted_at, key, iv) : null,
1345
+ shared_keys: encryptedMeta.shared_keys || {}
1346
+ };
1347
+ }
1348
+ };
1349
+
1350
+ // src/services/contact-service.ts
1351
+ var ContactService = class {
1352
+ constructor(core, fileService) {
1353
+ this.core = core;
1354
+ this.fileService = fileService;
1355
+ }
1356
+ /**
1357
+ * Initialize contacts aggregate if it doesn't exist
1358
+ */
1359
+ async setup() {
1360
+ const aleph = this.core.getAlephService();
1361
+ try {
1362
+ await aleph.fetchAggregate(
1363
+ AGGREGATE_KEYS.CONTACTS,
1364
+ ContactsAggregateSchema
1365
+ );
1366
+ } catch {
1367
+ await aleph.createAggregate(AGGREGATE_KEYS.CONTACTS, { contacts: [] });
1368
+ }
1369
+ }
1370
+ /**
1371
+ * Fetch all contacts
1372
+ */
1373
+ async listContacts() {
1374
+ const aleph = this.core.getAlephService();
1375
+ try {
1376
+ const aggregate = await aleph.fetchAggregate(
1377
+ AGGREGATE_KEYS.CONTACTS,
1378
+ ContactsAggregateSchema
1379
+ );
1380
+ return aggregate.contacts;
1381
+ } catch (error) {
1382
+ throw new ContactError(`Failed to fetch contacts: ${error.message}`);
1383
+ }
1384
+ }
1385
+ /**
1386
+ * Get a contact by public key
1387
+ * @param publicKey - Contact's public key
1388
+ */
1389
+ async getContact(publicKey) {
1390
+ const contacts = await this.listContacts();
1391
+ const contact = contacts.find((c) => c.public_key === publicKey);
1392
+ if (!contact) {
1393
+ throw new ContactError(`Contact not found: ${publicKey}`);
1394
+ }
1395
+ return contact;
1396
+ }
1397
+ /**
1398
+ * Get a contact by address
1399
+ * @param address - Contact's Ethereum address
1400
+ */
1401
+ async getContactByAddress(address) {
1402
+ const contacts = await this.listContacts();
1403
+ const contact = contacts.find((c) => c.address.toLowerCase() === address.toLowerCase());
1404
+ if (!contact) {
1405
+ throw new ContactError(`Contact not found: ${address}`);
1406
+ }
1407
+ return contact;
1408
+ }
1409
+ /**
1410
+ * Add a new contact
1411
+ * @param name - Contact name
1412
+ * @param address - Contact's Ethereum address
1413
+ * @param publicKey - Contact's public key (hex string)
1414
+ */
1415
+ async addContact(name, address, publicKey) {
1416
+ const aleph = this.core.getAlephService();
1417
+ try {
1418
+ const contacts = await this.listContacts();
1419
+ const existingContact = contacts.find(
1420
+ (c) => c.public_key === publicKey || c.address.toLowerCase() === address.toLowerCase()
1421
+ );
1422
+ if (existingContact) {
1423
+ throw new ContactError("Contact already exists");
1424
+ }
1425
+ const newContact = {
1426
+ name,
1427
+ address,
1428
+ public_key: publicKey
1429
+ };
1430
+ await aleph.updateAggregate(
1431
+ AGGREGATE_KEYS.CONTACTS,
1432
+ ContactsAggregateSchema,
1433
+ async (aggregate) => ({
1434
+ contacts: [...aggregate.contacts, newContact]
1435
+ })
1436
+ );
1437
+ return newContact;
1438
+ } catch (error) {
1439
+ if (error instanceof ContactError) {
1440
+ throw error;
1441
+ }
1442
+ throw new ContactError(`Failed to add contact: ${error.message}`);
1443
+ }
1444
+ }
1445
+ /**
1446
+ * Remove a contact
1447
+ * @param publicKey - Contact's public key
1448
+ */
1449
+ async removeContact(publicKey) {
1450
+ const aleph = this.core.getAlephService();
1451
+ try {
1452
+ await this.getContact(publicKey);
1453
+ await aleph.updateAggregate(
1454
+ AGGREGATE_KEYS.CONTACTS,
1455
+ ContactsAggregateSchema,
1456
+ async (aggregate) => ({
1457
+ contacts: aggregate.contacts.filter((c) => c.public_key !== publicKey)
1458
+ })
1459
+ );
1460
+ } catch (error) {
1461
+ if (error instanceof ContactError) {
1462
+ throw error;
1463
+ }
1464
+ throw new ContactError(`Failed to remove contact: ${error.message}`);
1465
+ }
1466
+ }
1467
+ /**
1468
+ * Update a contact's name
1469
+ * @param publicKey - Contact's public key
1470
+ * @param newName - New name for the contact
1471
+ */
1472
+ async updateContactName(publicKey, newName) {
1473
+ const aleph = this.core.getAlephService();
1474
+ try {
1475
+ const existingContact = await this.getContact(publicKey);
1476
+ const updatedContact = {
1477
+ ...existingContact,
1478
+ name: newName
1479
+ };
1480
+ await aleph.updateAggregate(
1481
+ AGGREGATE_KEYS.CONTACTS,
1482
+ ContactsAggregateSchema,
1483
+ async (aggregate) => ({
1484
+ contacts: aggregate.contacts.map(
1485
+ (c) => c.public_key === publicKey ? updatedContact : c
1486
+ )
1487
+ })
1488
+ );
1489
+ return updatedContact;
1490
+ } catch (error) {
1491
+ if (error instanceof ContactError) {
1492
+ throw error;
1493
+ }
1494
+ throw new ContactError(`Failed to update contact: ${error.message}`);
1495
+ }
1496
+ }
1497
+ /**
1498
+ * Fetch files shared by a contact
1499
+ * @param publicKey - Contact's public key
1500
+ */
1501
+ async getSharedFiles(publicKey) {
1502
+ try {
1503
+ await this.getContact(publicKey);
1504
+ const entries = await this.fileService.fetchFileEntries();
1505
+ const sharedEntries = entries.filter(
1506
+ (entry) => entry.shared_with.includes(publicKey)
1507
+ );
1508
+ const files = await this.fileService.fetchFilesMetaFromEntries(sharedEntries);
1509
+ return files;
1510
+ } catch (error) {
1511
+ throw new ContactError(`Failed to fetch shared files: ${error.message}`);
1512
+ }
1513
+ }
1514
+ /**
1515
+ * Fetch files that a contact has shared with the current user
1516
+ * @param publicKey - Contact's public key
1517
+ * @returns Files shared by the contact with current user
1518
+ */
1519
+ async fetchFilesSharedByContact(publicKey) {
1520
+ try {
1521
+ const contact = await this.getContact(publicKey);
1522
+ const currentUserPublicKey = this.core.getPublicKey();
1523
+ const aleph = this.core.getAlephService();
1524
+ const contactEntries = await aleph.fetchAggregate(
1525
+ AGGREGATE_KEYS.FILE_ENTRIES,
1526
+ FileEntriesAggregateSchema,
1527
+ contact.address
1528
+ // Important: contact's address, not current user's
1529
+ );
1530
+ const sharedEntries = contactEntries.files.filter(
1531
+ (entry) => entry.shared_with.includes(currentUserPublicKey)
1532
+ );
1533
+ const files = await this.fileService.fetchFilesMetaFromEntries(
1534
+ sharedEntries,
1535
+ contact.address
1536
+ // Owner is the contact
1537
+ );
1538
+ return files;
1539
+ } catch (error) {
1540
+ throw new ContactError(`Failed to fetch files shared by contact: ${error.message}`);
1541
+ }
1542
+ }
1543
+ /**
1544
+ * Share a file with a contact
1545
+ * @param filePath - Path of the file to share
1546
+ * @param publicKey - Contact's public key
1547
+ */
1548
+ async shareFileWithContact(filePath, publicKey) {
1549
+ try {
1550
+ await this.getContact(publicKey);
1551
+ await this.fileService.shareFile(filePath, publicKey);
1552
+ } catch (error) {
1553
+ throw new ContactError(`Failed to share file with contact: ${error.message}`);
1554
+ }
1555
+ }
1556
+ /**
1557
+ * Unshare a file with a contact
1558
+ * @param filePath - Path of the file to unshare
1559
+ * @param publicKey - Contact's public key
1560
+ */
1561
+ async unshareFileWithContact(filePath, publicKey) {
1562
+ try {
1563
+ await this.getContact(publicKey);
1564
+ await this.fileService.unshareFile(filePath, publicKey);
1565
+ } catch (error) {
1566
+ throw new ContactError(`Failed to unshare file with contact: ${error.message}`);
1567
+ }
1568
+ }
1569
+ };
1570
+
1571
+ // src/services/knowledge-base-service.ts
1572
+ var KnowledgeBaseService = class {
1573
+ constructor(core) {
1574
+ this.core = core;
1575
+ }
1576
+ /**
1577
+ * Initialize knowledge bases aggregate if it doesn't exist
1578
+ */
1579
+ async setup() {
1580
+ const aleph = this.core.getAlephService();
1581
+ try {
1582
+ await aleph.fetchAggregate(
1583
+ AGGREGATE_KEYS.KNOWLEDGE_BASES,
1584
+ KnowledgeBasesAggregateSchema
1585
+ );
1586
+ } catch {
1587
+ await aleph.createAggregate(AGGREGATE_KEYS.KNOWLEDGE_BASES, { knowledge_bases: [] });
1588
+ }
1589
+ }
1590
+ /**
1591
+ * Fetch all knowledge bases
1592
+ */
1593
+ async listKnowledgeBases() {
1594
+ const aleph = this.core.getAlephService();
1595
+ try {
1596
+ const aggregate = await aleph.fetchAggregate(
1597
+ AGGREGATE_KEYS.KNOWLEDGE_BASES,
1598
+ KnowledgeBasesAggregateSchema
1599
+ );
1600
+ return aggregate.knowledge_bases;
1601
+ } catch (error) {
1602
+ throw new KnowledgeBaseError(`Failed to fetch knowledge bases: ${error.message}`);
1603
+ }
1604
+ }
1605
+ /**
1606
+ * Get a knowledge base by name
1607
+ * @param name - Knowledge base name
1608
+ */
1609
+ async getKnowledgeBase(name) {
1610
+ const kbs = await this.listKnowledgeBases();
1611
+ const kb = kbs.find((k) => k.name === name);
1612
+ if (!kb) {
1613
+ throw new KnowledgeBaseError(`Knowledge base not found: ${name}`);
1614
+ }
1615
+ return kb;
1616
+ }
1617
+ /**
1618
+ * Create a new knowledge base
1619
+ * @param name - Knowledge base name
1620
+ * @param filePaths - Optional initial file paths
1621
+ */
1622
+ async createKnowledgeBase(name, filePaths = []) {
1623
+ const aleph = this.core.getAlephService();
1624
+ try {
1625
+ const kbs = await this.listKnowledgeBases();
1626
+ const existingKb = kbs.find((k) => k.name === name);
1627
+ if (existingKb) {
1628
+ throw new KnowledgeBaseError(`Knowledge base already exists: ${name}`);
1629
+ }
1630
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1631
+ const newKb = {
1632
+ name,
1633
+ file_paths: filePaths,
1634
+ created_at: now,
1635
+ updated_at: now
1636
+ };
1637
+ await aleph.updateAggregate(
1638
+ AGGREGATE_KEYS.KNOWLEDGE_BASES,
1639
+ KnowledgeBasesAggregateSchema,
1640
+ async (aggregate) => ({
1641
+ knowledge_bases: [...aggregate.knowledge_bases, newKb]
1642
+ })
1643
+ );
1644
+ return newKb;
1645
+ } catch (error) {
1646
+ if (error instanceof KnowledgeBaseError) {
1647
+ throw error;
1648
+ }
1649
+ throw new KnowledgeBaseError(`Failed to create knowledge base: ${error.message}`);
1650
+ }
1651
+ }
1652
+ /**
1653
+ * Delete a knowledge base
1654
+ * @param name - Knowledge base name
1655
+ */
1656
+ async deleteKnowledgeBase(name) {
1657
+ const aleph = this.core.getAlephService();
1658
+ try {
1659
+ await this.getKnowledgeBase(name);
1660
+ await aleph.updateAggregate(
1661
+ AGGREGATE_KEYS.KNOWLEDGE_BASES,
1662
+ KnowledgeBasesAggregateSchema,
1663
+ async (aggregate) => ({
1664
+ knowledge_bases: aggregate.knowledge_bases.filter((k) => k.name !== name)
1665
+ })
1666
+ );
1667
+ } catch (error) {
1668
+ if (error instanceof KnowledgeBaseError) {
1669
+ throw error;
1670
+ }
1671
+ throw new KnowledgeBaseError(`Failed to delete knowledge base: ${error.message}`);
1672
+ }
1673
+ }
1674
+ /**
1675
+ * Rename a knowledge base
1676
+ * @param oldName - Current name
1677
+ * @param newName - New name
1678
+ */
1679
+ async renameKnowledgeBase(oldName, newName) {
1680
+ const aleph = this.core.getAlephService();
1681
+ try {
1682
+ const existingKb = await this.getKnowledgeBase(oldName);
1683
+ const kbs = await this.listKnowledgeBases();
1684
+ if (kbs.some((k) => k.name === newName)) {
1685
+ throw new KnowledgeBaseError(`Knowledge base already exists: ${newName}`);
1686
+ }
1687
+ const updatedKb = {
1688
+ ...existingKb,
1689
+ name: newName,
1690
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1691
+ };
1692
+ await aleph.updateAggregate(
1693
+ AGGREGATE_KEYS.KNOWLEDGE_BASES,
1694
+ KnowledgeBasesAggregateSchema,
1695
+ async (aggregate) => ({
1696
+ knowledge_bases: aggregate.knowledge_bases.map(
1697
+ (k) => k.name === oldName ? updatedKb : k
1698
+ )
1699
+ })
1700
+ );
1701
+ return updatedKb;
1702
+ } catch (error) {
1703
+ if (error instanceof KnowledgeBaseError) {
1704
+ throw error;
1705
+ }
1706
+ throw new KnowledgeBaseError(`Failed to rename knowledge base: ${error.message}`);
1707
+ }
1708
+ }
1709
+ /**
1710
+ * Set the files in a knowledge base (replaces all existing files)
1711
+ * @param name - Knowledge base name
1712
+ * @param filePaths - File paths
1713
+ */
1714
+ async setFiles(name, filePaths) {
1715
+ const aleph = this.core.getAlephService();
1716
+ try {
1717
+ const existingKb = await this.getKnowledgeBase(name);
1718
+ const updatedKb = {
1719
+ ...existingKb,
1720
+ file_paths: filePaths,
1721
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1722
+ };
1723
+ await aleph.updateAggregate(
1724
+ AGGREGATE_KEYS.KNOWLEDGE_BASES,
1725
+ KnowledgeBasesAggregateSchema,
1726
+ async (aggregate) => ({
1727
+ knowledge_bases: aggregate.knowledge_bases.map(
1728
+ (k) => k.name === name ? updatedKb : k
1729
+ )
1730
+ })
1731
+ );
1732
+ return updatedKb;
1733
+ } catch (error) {
1734
+ if (error instanceof KnowledgeBaseError) {
1735
+ throw error;
1736
+ }
1737
+ throw new KnowledgeBaseError(`Failed to set files: ${error.message}`);
1738
+ }
1739
+ }
1740
+ /**
1741
+ * Add files to a knowledge base
1742
+ * @param name - Knowledge base name
1743
+ * @param filePaths - File paths to add
1744
+ */
1745
+ async addFiles(name, filePaths) {
1746
+ const aleph = this.core.getAlephService();
1747
+ try {
1748
+ const existingKb = await this.getKnowledgeBase(name);
1749
+ const updatedFilePaths = [.../* @__PURE__ */ new Set([...existingKb.file_paths, ...filePaths])];
1750
+ const updatedKb = {
1751
+ ...existingKb,
1752
+ file_paths: updatedFilePaths,
1753
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1754
+ };
1755
+ await aleph.updateAggregate(
1756
+ AGGREGATE_KEYS.KNOWLEDGE_BASES,
1757
+ KnowledgeBasesAggregateSchema,
1758
+ async (aggregate) => ({
1759
+ knowledge_bases: aggregate.knowledge_bases.map(
1760
+ (k) => k.name === name ? updatedKb : k
1761
+ )
1762
+ })
1763
+ );
1764
+ return updatedKb;
1765
+ } catch (error) {
1766
+ if (error instanceof KnowledgeBaseError) {
1767
+ throw error;
1768
+ }
1769
+ throw new KnowledgeBaseError(`Failed to add files: ${error.message}`);
1770
+ }
1771
+ }
1772
+ /**
1773
+ * Remove files from a knowledge base
1774
+ * @param name - Knowledge base name
1775
+ * @param filePaths - File paths to remove
1776
+ */
1777
+ async removeFiles(name, filePaths) {
1778
+ const aleph = this.core.getAlephService();
1779
+ try {
1780
+ const existingKb = await this.getKnowledgeBase(name);
1781
+ const updatedFilePaths = existingKb.file_paths.filter(
1782
+ (path) => !filePaths.includes(path)
1783
+ );
1784
+ const updatedKb = {
1785
+ ...existingKb,
1786
+ file_paths: updatedFilePaths,
1787
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1788
+ };
1789
+ await aleph.updateAggregate(
1790
+ AGGREGATE_KEYS.KNOWLEDGE_BASES,
1791
+ KnowledgeBasesAggregateSchema,
1792
+ async (aggregate) => ({
1793
+ knowledge_bases: aggregate.knowledge_bases.map(
1794
+ (k) => k.name === name ? updatedKb : k
1795
+ )
1796
+ })
1797
+ );
1798
+ return updatedKb;
1799
+ } catch (error) {
1800
+ if (error instanceof KnowledgeBaseError) {
1801
+ throw error;
1802
+ }
1803
+ throw new KnowledgeBaseError(`Failed to remove files: ${error.message}`);
1804
+ }
1805
+ }
1806
+ /**
1807
+ * Clear all files from a knowledge base
1808
+ * @param name - Knowledge base name
1809
+ */
1810
+ async clearFiles(name) {
1811
+ return await this.setFiles(name, []);
1812
+ }
1813
+ };
1814
+
1815
+ // src/services/credit-service.ts
1816
+ var _CreditService = class _CreditService {
1817
+ constructor(core) {
1818
+ this.core = core;
1819
+ }
1820
+ /**
1821
+ * Get user's credit balance and transaction history
1822
+ * @returns User credit data with balance and transactions
1823
+ */
1824
+ async getCreditBalance() {
1825
+ const aleph = this.core.getAlephService();
1826
+ const userAddress = this.core.getMainAddress();
1827
+ try {
1828
+ const creditAggregate = await aleph.fetchAggregate(
1829
+ AGGREGATE_KEYS.CREDITS,
1830
+ CreditAggregateSchema,
1831
+ _CreditService.BACKEND_ADDRESS
1832
+ );
1833
+ return creditAggregate[userAddress] || { balance: 0, transactions: [] };
1834
+ } catch {
1835
+ return { balance: 0, transactions: [] };
1836
+ }
1837
+ }
1838
+ };
1839
+ _CreditService.BACKEND_ADDRESS = "0x1234567890123456789012345678901234567890";
1840
+ var CreditService = _CreditService;
1841
+
1842
+ // src/bedrock-client.ts
1843
+ var BedrockClient = class _BedrockClient {
1844
+ constructor(core) {
1845
+ this.core = core;
1846
+ this.files = new FileService(core);
1847
+ this.contacts = new ContactService(core, this.files);
1848
+ this.knowledgeBases = new KnowledgeBaseService(core);
1849
+ this.credits = new CreditService(core);
1850
+ }
1851
+ /**
1852
+ * Create BedrockClient from a private key
1853
+ *
1854
+ * @param privateKey - Ethereum private key (hex string with or without 0x prefix)
1855
+ * @param config - Optional configuration
1856
+ * @returns Initialized BedrockClient
1857
+ *
1858
+ * @example
1859
+ * ```typescript
1860
+ * const client = await BedrockClient.fromPrivateKey('0xabc123...');
1861
+ * ```
1862
+ */
1863
+ static async fromPrivateKey(privateKey, config) {
1864
+ const core = await BedrockCore.fromPrivateKey(privateKey, config);
1865
+ const client = new _BedrockClient(core);
1866
+ await client.setup();
1867
+ return client;
1868
+ }
1869
+ /**
1870
+ * Create BedrockClient from a wallet provider (e.g., MetaMask)
1871
+ *
1872
+ * @param provider - EIP-1193 provider
1873
+ * @param config - Optional configuration
1874
+ * @returns Initialized BedrockClient
1875
+ *
1876
+ * @example
1877
+ * ```typescript
1878
+ * const client = await BedrockClient.fromProvider(window.ethereum);
1879
+ * ```
1880
+ */
1881
+ static async fromProvider(provider, config) {
1882
+ const core = await BedrockCore.fromProvider(provider, config);
1883
+ const client = new _BedrockClient(core);
1884
+ await client.setup();
1885
+ return client;
1886
+ }
1887
+ /**
1888
+ * Create BedrockClient from a signature hash
1889
+ *
1890
+ * @param signatureHash - Signature hash from wallet
1891
+ * @param provider - EIP-1193 provider
1892
+ * @param config - Optional configuration
1893
+ * @returns Initialized BedrockClient
1894
+ *
1895
+ * @example
1896
+ * ```typescript
1897
+ * const signature = await wallet.signMessage({ message: 'Bedrock.im' });
1898
+ * const client = await BedrockClient.fromSignature(signature, window.ethereum);
1899
+ * ```
1900
+ */
1901
+ static async fromSignature(signatureHash, provider, config) {
1902
+ const core = await BedrockCore.fromSignature(signatureHash, provider, config);
1903
+ const client = new _BedrockClient(core);
1904
+ await client.setup();
1905
+ return client;
1906
+ }
1907
+ /**
1908
+ * Get the main account address
1909
+ */
1910
+ getMainAddress() {
1911
+ return this.core.getMainAddress();
1912
+ }
1913
+ /**
1914
+ * Get the sub-account address
1915
+ */
1916
+ getSubAddress() {
1917
+ return this.core.getSubAddress();
1918
+ }
1919
+ /**
1920
+ * Get the account's public key
1921
+ */
1922
+ getPublicKey() {
1923
+ return this.core.getPublicKey();
1924
+ }
1925
+ /**
1926
+ * Get the encryption key (32 bytes derived from signature)
1927
+ */
1928
+ getEncryptionKey() {
1929
+ return this.core.getEncryptionKey();
1930
+ }
1931
+ /**
1932
+ * Reset all data (files, contacts, knowledge bases)
1933
+ * WARNING: This will delete all user data from Aleph
1934
+ */
1935
+ async resetAllData() {
1936
+ await Promise.all([
1937
+ this.resetFiles(),
1938
+ this.resetContacts(),
1939
+ this.resetKnowledgeBases()
1940
+ ]);
1941
+ }
1942
+ /**
1943
+ * Reset all files
1944
+ * WARNING: This will delete all files from Aleph
1945
+ */
1946
+ async resetFiles() {
1947
+ const aleph = this.core.getAlephService();
1948
+ await aleph.createAggregate(AGGREGATE_KEYS.FILE_ENTRIES, []);
1949
+ }
1950
+ /**
1951
+ * Reset all contacts
1952
+ * WARNING: This will delete all contacts
1953
+ */
1954
+ async resetContacts() {
1955
+ const aleph = this.core.getAlephService();
1956
+ await aleph.createAggregate(AGGREGATE_KEYS.CONTACTS, []);
1957
+ }
1958
+ /**
1959
+ * Reset all knowledge bases
1960
+ * WARNING: This will delete all knowledge bases
1961
+ */
1962
+ async resetKnowledgeBases() {
1963
+ const aleph = this.core.getAlephService();
1964
+ await aleph.createAggregate(AGGREGATE_KEYS.KNOWLEDGE_BASES, []);
1965
+ }
1966
+ // ============================================================================
1967
+ // Private methods
1968
+ // ============================================================================
1969
+ /**
1970
+ * Setup all services (create aggregates if they don't exist)
1971
+ */
1972
+ async setup() {
1973
+ await Promise.all([
1974
+ this.files.setup(),
1975
+ this.contacts.setup(),
1976
+ this.knowledgeBases.setup()
1977
+ ]);
1978
+ }
1979
+ };
1980
+
1981
+ // src/index.ts
1982
+ import { ETHAccount as ETHAccount2 } from "@aleph-sdk/ethereum";
1983
+ import { AlephHttpClient as AlephHttpClient2, AuthenticatedAlephHttpClient as AuthenticatedAlephHttpClient3 } from "@aleph-sdk/client";
1984
+ export {
1985
+ AGGREGATE_KEYS,
1986
+ ALEPH_GENERAL_CHANNEL,
1987
+ AddressSchema,
1988
+ AlephHttpClient2 as AlephHttpClient,
1989
+ AlephService,
1990
+ AuthenticatedAlephHttpClient3 as AuthenticatedAlephHttpClient,
1991
+ AuthenticationError,
1992
+ BEDROCK_MESSAGE,
1993
+ BedrockClient,
1994
+ BedrockCore,
1995
+ BedrockError,
1996
+ ContactError,
1997
+ ContactSchema,
1998
+ ContactService,
1999
+ ContactsAggregateSchema,
2000
+ CreditAggregateSchema,
2001
+ CreditError,
2002
+ CreditService,
2003
+ CreditTransactionSchema,
2004
+ CryptoUtils,
2005
+ DatetimeSchema,
2006
+ ETHAccount2 as ETHAccount,
2007
+ EncryptionError,
2008
+ EncryptionService,
2009
+ FileEntriesAggregateSchema,
2010
+ FileEntrySchema,
2011
+ FileError,
2012
+ FileMetaEncryptedSchema,
2013
+ FileMetaSchema,
2014
+ FileNotFoundError,
2015
+ FileService,
2016
+ HexString32Schema,
2017
+ HexString64Schema,
2018
+ KnowledgeBaseError,
2019
+ KnowledgeBaseSchema,
2020
+ KnowledgeBaseService,
2021
+ KnowledgeBasesAggregateSchema,
2022
+ NetworkError,
2023
+ POST_TYPES,
2024
+ PublicFileMetaSchema,
2025
+ SECURITY_AGGREGATE_KEY,
2026
+ SecurityAggregateSchema,
2027
+ UserCreditSchema,
2028
+ ValidationError
2029
+ };
2030
+ //# sourceMappingURL=index.mjs.map