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