gun-eth 2.0.1 → 3.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.
Files changed (64) hide show
  1. package/README.md +320 -141
  2. package/dist/gun-eth.bundle.js +6809 -0
  3. package/dist/gun-eth.cjs +2468 -0
  4. package/dist/types/browser.d.ts +6 -0
  5. package/dist/types/config/local.d.ts +7 -0
  6. package/dist/types/constants/abis.d.ts +37 -0
  7. package/dist/types/constants/index.d.ts +1 -0
  8. package/dist/types/core/gun-eth.d.ts +419 -0
  9. package/dist/types/features/bubbles/client/bubble-client.d.ts +184 -0
  10. package/dist/types/features/bubbles/providers/base-bubble-provider.d.ts +303 -0
  11. package/dist/types/features/bubbles/providers/gun-bubble-provider.d.ts +173 -0
  12. package/dist/types/features/bubbles/providers/hybrid-bubble-provider.d.ts +124 -0
  13. package/dist/types/features/proof/ProofChain.d.ts +225 -0
  14. package/dist/types/features/stealth/StealthChain.d.ts +200 -0
  15. package/dist/types/index.d.ts +61 -0
  16. package/dist/types/utils/common.d.ts +11 -0
  17. package/dist/types/utils/encryption.d.ts +32 -0
  18. package/package.json +110 -26
  19. package/dist/gun-eth-protocol.cjs.js +0 -11528
  20. package/dist/gun-eth-protocol.esm.js +0 -11503
  21. package/dist/gun-eth-protocol.js +0 -18
  22. package/dist/gun-eth-protocol.react.js +0 -11503
  23. package/dist/gun-eth-protocol.umd.js +0 -18
  24. package/jsdoc.json +0 -7
  25. package/rollup.config.js +0 -80
  26. package/src/index.js +0 -181
  27. package/src/lib/authentication/index.js +0 -13
  28. package/src/lib/authentication/isAuthenticated.js +0 -20
  29. package/src/lib/authentication/login.js +0 -25
  30. package/src/lib/authentication/register.js +0 -58
  31. package/src/lib/blockchain/abis/SHINE.json +0 -262
  32. package/src/lib/blockchain/contracts/SHINE.sol +0 -52
  33. package/src/lib/blockchain/ethereum.js +0 -74
  34. package/src/lib/blockchain/shine.js +0 -204
  35. package/src/lib/certificates/friendsCertificates.js +0 -92
  36. package/src/lib/certificates/index.js +0 -44
  37. package/src/lib/certificates/messagingCertificates.js +0 -94
  38. package/src/lib/friends/acceptFriendRequest.js +0 -69
  39. package/src/lib/friends/addFriendRequest.js +0 -49
  40. package/src/lib/friends/friendRequests.js +0 -51
  41. package/src/lib/friends/friendsList.js +0 -57
  42. package/src/lib/friends/index.js +0 -36
  43. package/src/lib/friends/rejectFriendRequest.js +0 -31
  44. package/src/lib/messaging/chatsList.js +0 -42
  45. package/src/lib/messaging/createChat.js +0 -132
  46. package/src/lib/messaging/index.js +0 -36
  47. package/src/lib/messaging/messageList.js +0 -106
  48. package/src/lib/messaging/sendMessage.js +0 -132
  49. package/src/lib/messaging/sendVoiceMessage.js +0 -119
  50. package/src/lib/notes/createNote.js +0 -41
  51. package/src/lib/notes/deleteNote.js +0 -12
  52. package/src/lib/notes/getNote.js +0 -25
  53. package/src/lib/notes/getUserNote.js +0 -59
  54. package/src/lib/notes/index.js +0 -8
  55. package/src/lib/notes/updateNotes.js +0 -35
  56. package/src/lib/post/createPost.js +0 -17
  57. package/src/lib/post/decryptPost.js +0 -14
  58. package/src/lib/post/deletePost.js +0 -13
  59. package/src/lib/post/encryptPost,js +0 -16
  60. package/src/lib/post/getPost.js +0 -36
  61. package/src/lib/post/index.js +0 -9
  62. package/src/lib/post/updatePost.js +0 -16
  63. package/src/state/gun.js +0 -33
  64. package/types/types.d.ts +0 -244
@@ -0,0 +1,2468 @@
1
+ 'use strict';
2
+
3
+ var ethers = require('ethers');
4
+ var Gun = require('gun');
5
+ var SEA$1 = require('gun/sea.js');
6
+ var module$1 = require('module');
7
+ var url = require('url');
8
+ var path = require('path');
9
+ var fs = require('fs');
10
+
11
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
12
+ /**
13
+ * @typedef {Object} ChainConfig
14
+ * @property {number} CHAIN_ID - ID della chain
15
+ * @property {string} RPC_URL - URL del nodo RPC
16
+ * @property {string} PROOF_OF_INTEGRITY_ADDRESS - Indirizzo del contratto ProofOfIntegrity
17
+ * @property {string} STEALTH_ANNOUNCER_ADDRESS - Indirizzo del contratto StealthAnnouncer
18
+ * @property {string} BUBBLE_REGISTRY_ADDRESS - Indirizzo del contratto BubbleRegistry
19
+ */
20
+
21
+ const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('gun-eth.cjs', document.baseURI).href)));
22
+ const __dirname$1 = path.dirname(__filename$1);
23
+ const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('gun-eth.cjs', document.baseURI).href)));
24
+
25
+ // Percorsi relativi agli artifacts
26
+ const artifactsPath = path.join(__dirname$1, '../../artifacts/contracts');
27
+
28
+ // Importa gli ABI dagli artifacts
29
+ const StealthAnnouncer = require$1(path.join(artifactsPath, 'StealthAnnouncer.sol/StealthAnnouncer.json'));
30
+ const BubbleRegistry = require$1(path.join(artifactsPath, 'BubbleRegistry.sol/BubbleRegistry.json'));
31
+ const ProofOfIntegrity = require$1(path.join(artifactsPath, 'ProofOfIntegrity.sol/ProofOfIntegrity.json'));
32
+
33
+ // Esporta gli ABI dai file JSON
34
+ const STEALTH_ANNOUNCER_ABI = StealthAnnouncer.abi;
35
+ const BUBBLE_REGISTRY_ABI = BubbleRegistry.abi;
36
+ const PROOF_OF_INTEGRITY_ABI = ProofOfIntegrity.abi;
37
+
38
+ // Configurazioni per diverse chain
39
+ const chainConfigs = {
40
+ localhost: {
41
+ CHAIN_ID: 31337,
42
+ RPC_URL: "http://127.0.0.1:8545",
43
+ PROOF_OF_INTEGRITY_ADDRESS: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
44
+ STEALTH_ANNOUNCER_ADDRESS: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
45
+ BUBBLE_REGISTRY_ADDRESS: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"
46
+ },
47
+ polygon: {
48
+ CHAIN_ID: 137,
49
+ RPC_URL: "https://polygon-mainnet.g.alchemy.com/v2/yjhjIoJ3o_at8ALT7nCJtFtjdqFpiBdx",
50
+ PROOF_OF_INTEGRITY_ADDRESS: "0x8515fa00a00A5483a3485526c7aD1f44E2779321",
51
+ STEALTH_ANNOUNCER_ADDRESS: "0xD0CDbD17E4f2DDCE27B51721095048302768434f",
52
+ BUBBLE_REGISTRY_ADDRESS: "0xc70DC231B9690D9dA988f6D4E518356eE9e45cd9"
53
+ }
54
+ };
55
+
56
+ /** @type {boolean} */
57
+ const isLocalEnvironment = process.env.NODE_ENV === 'development';
58
+
59
+ /**
60
+ * Ottiene le configurazioni degli indirizzi per una specifica chain
61
+ * @param {string} [chain='localhost'] - Nome della chain
62
+ * @returns {ChainConfig} Configurazione degli indirizzi per la chain specificata
63
+ * @throws {Error} Se la configurazione della chain non viene trovata
64
+ */
65
+ function getAddressesForChain(chain = 'localhost') {
66
+ const config = chainConfigs[chain];
67
+ if (!config) {
68
+ throw new Error(`Chain configuration not found for: ${chain}`);
69
+ }
70
+ return config;
71
+ }
72
+
73
+ // Esporta gli indirizzi di default (localhost)
74
+ const {
75
+ PROOF_OF_INTEGRITY_ADDRESS,
76
+ STEALTH_ANNOUNCER_ADDRESS,
77
+ BUBBLE_REGISTRY_ADDRESS
78
+ } = getAddressesForChain('localhost');
79
+
80
+ /**
81
+ * @typedef {Object|string} KeyPair
82
+ * @property {string} [epriv] - Private encryption key
83
+ * @property {string} [epub] - Public encryption key
84
+ * @property {string} [pub] - Optional public key
85
+ * @property {string} [priv] - Optional private key
86
+ */
87
+
88
+ /**
89
+ * Encrypts data using SEA encryption
90
+ * @param {string|Object} data - Data to encrypt
91
+ * @param {KeyPair} keypair - Keypair for encryption
92
+ * @returns {Promise<string>} Encrypted data
93
+ * @throws {Error} If encryption fails
94
+ */
95
+ async function encrypt(data, keypair) {
96
+ try {
97
+ const dataToEncrypt = typeof data === 'object' ? JSON.stringify(data) : data;
98
+ const encrypted = await SEA$1.encrypt(dataToEncrypt, keypair);
99
+ if (!encrypted) {
100
+ throw new Error('Encryption failed');
101
+ }
102
+ return encrypted;
103
+ } catch (error) {
104
+ console.error('Encryption error:', error);
105
+ throw error;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Decrypts data using SEA decryption
111
+ * @param {string} data - Encrypted data to decrypt
112
+ * @param {KeyPair} keypair - Keypair for decryption
113
+ * @returns {Promise<string|Object>} Decrypted data
114
+ * @throws {Error} If decryption fails
115
+ */
116
+ async function decrypt(data, keypair) {
117
+ try {
118
+ const decrypted = await SEA$1.decrypt(data, keypair);
119
+ if (!decrypted) {
120
+ console.log("Decryption returned null");
121
+ throw new Error('Decryption failed');
122
+ }
123
+ try {
124
+ return typeof decrypted === 'string' ? JSON.parse(decrypted) : decrypted;
125
+ } catch {
126
+ return decrypted;
127
+ }
128
+ } catch (error) {
129
+ console.error('Decryption error:', error);
130
+ console.error('Data:', data);
131
+ console.error('Keypair:', {
132
+ hasEpriv: !!keypair?.epriv,
133
+ hasEpub: !!keypair?.epub,
134
+ hasPub: !!keypair?.pub,
135
+ hasPriv: !!keypair?.priv
136
+ });
137
+ throw error;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Derives a shared secret key between two keypairs
143
+ * @param {string} recipientEpub - Recipient's public encryption key
144
+ * @param {KeyPair} senderKeypair - Sender's keypair
145
+ * @returns {Promise<string>} Derived shared key
146
+ * @throws {Error} If key derivation fails
147
+ */
148
+ async function deriveSharedKey(recipientEpub, senderKeypair) {
149
+ try {
150
+ if (!recipientEpub || !senderKeypair || !senderKeypair.epriv) {
151
+ throw new Error('Invalid parameters for shared key derivation');
152
+ }
153
+ const sharedKey = await SEA$1.secret(recipientEpub, senderKeypair);
154
+ if (!sharedKey) {
155
+ throw new Error('Failed to derive shared key');
156
+ }
157
+ return sharedKey;
158
+ } catch (error) {
159
+ console.error('Error deriving shared key:', error);
160
+ throw error;
161
+ }
162
+ }
163
+
164
+ // @ts-check
165
+
166
+
167
+ /**
168
+ * Extended Gun with additional methods
169
+ * @typedef {import('gun').IGun & {
170
+ * get: (path: string) => any,
171
+ * verifySignature: (message: string, signature: string) => Promise<string>,
172
+ * put: (data: any) => void,
173
+ * set: (data: any) => void,
174
+ * map: () => any,
175
+ * once: (callback: (data: any, key: string) => void) => void
176
+ * }} ExtendedGun
177
+ */
178
+
179
+ /**
180
+ * @typedef {Object} StealthAddressResult
181
+ * @property {string} stealthPrivateKey - The private key of the stealth address
182
+ * @property {string} stealthAddress - The stealth address
183
+ * @property {ethers.Wallet} wallet - The stealth wallet
184
+ */
185
+
186
+ /**
187
+ * @typedef {Object} StealthAddressGenerationResult
188
+ * @property {string} stealthAddress - The generated stealth address
189
+ * @property {string} senderPublicKey - The public key of the sender
190
+ * @property {string} spendingPublicKey - The spending public key of the recipient
191
+ */
192
+
193
+ /**
194
+ * @typedef {Object} StealthPaymentOptions
195
+ * @property {boolean} [onChain] - If true, announce on-chain, otherwise off-chain
196
+ * @property {string} [chain] - The chain to announce on (if onChain=true)
197
+ */
198
+
199
+ /**
200
+ * @typedef {Object} StealthPaymentResult
201
+ * @property {ethers.Wallet} wallet - The recovered wallet
202
+ * @property {string} address - The recovered address
203
+ */
204
+
205
+ /**
206
+ * @typedef {Object} PublicKeysResult
207
+ * @property {string} viewingPublicKey - The viewing public key
208
+ * @property {string} spendingPublicKey - The spending public key
209
+ */
210
+
211
+ /**
212
+ * Configuration for StealthChain
213
+ * @typedef {Object} StealthChainConfig
214
+ * @property {string} [contractAddress] - Address of deployed StealthAnnouncer contract
215
+ * @property {string} [abi] - ABI of the contract (optional, defaults to STEALTH_ANNOUNCER_ABI)
216
+ */
217
+
218
+ class StealthChain {
219
+ /** @type {ExtendedGun} */
220
+ gun;
221
+ /** @type {string | undefined} */
222
+ contractAddress;
223
+ /** @type {any} */
224
+ contractAbi;
225
+
226
+ /**
227
+ * Creates a new StealthChain instance
228
+ * @param {ExtendedGun} gun - Gun instance
229
+ * @param {StealthChainConfig} [config] - Optional configuration
230
+ */
231
+ constructor(gun, config = {}) {
232
+ this.gun = gun;
233
+ this.contractAddress = config.contractAddress;
234
+ this.contractAbi = config.abi || STEALTH_ANNOUNCER_ABI;
235
+ }
236
+
237
+ /**
238
+ * Gets contract instance for the specified chain
239
+ * @param {string} chain - Chain identifier
240
+ * @returns {Promise<ethers.Contract>} Contract instance
241
+ */
242
+ async getContract(chain) {
243
+ const signer = await getSigner();
244
+ const address = this.contractAddress || getContractAddresses(chain).STEALTH_ANNOUNCER_ADDRESS;
245
+ return new ethers.ethers.Contract(address, this.contractAbi, signer);
246
+ }
247
+
248
+ /**
249
+ * Derives a stealth address from shared secret and spending public key
250
+ * @param {string} sharedSecret - The shared secret
251
+ * @param {string} spendingPublicKey - The spending public key
252
+ * @returns {StealthAddressResult} The derived stealth address details
253
+ */
254
+ deriveStealthAddress(sharedSecret, spendingPublicKey) {
255
+ try {
256
+ // Base64 to hex conversion function
257
+ const base64ToHex = base64 => {
258
+ // Remove everything after the dot (if present)
259
+ const cleanBase64 = base64.split('.')[0];
260
+ // Remove 0x prefix if present
261
+ const withoutPrefix = cleanBase64.replace('0x', '');
262
+ // Convert from base64 to hex
263
+ const raw = atob(withoutPrefix.replace(/-/g, '+').replace(/_/g, '/'));
264
+ let hex = '';
265
+ for (let i = 0; i < raw.length; i++) {
266
+ const hexByte = raw.charCodeAt(i).toString(16);
267
+ hex += hexByte.length === 2 ? hexByte : '0' + hexByte;
268
+ }
269
+ return '0x' + hex;
270
+ };
271
+ console.log("Input values:", {
272
+ sharedSecret,
273
+ spendingPublicKey
274
+ });
275
+
276
+ // Convert both values to hex
277
+ const sharedSecretHex = base64ToHex(sharedSecret);
278
+ const spendingPublicKeyHex = base64ToHex(spendingPublicKey);
279
+ console.log("Converted values:", {
280
+ sharedSecretHex,
281
+ spendingPublicKeyHex
282
+ });
283
+
284
+ // Generate stealth private key
285
+ const stealthPrivateKey = ethers.ethers.keccak256(ethers.ethers.concat([ethers.ethers.getBytes(sharedSecretHex), ethers.ethers.getBytes(spendingPublicKeyHex)]));
286
+
287
+ // Create stealth wallet
288
+ const stealthWallet = new ethers.ethers.Wallet(stealthPrivateKey);
289
+ console.log("Generated stealth values:", {
290
+ stealthPrivateKey,
291
+ stealthAddress: stealthWallet.address
292
+ });
293
+ return {
294
+ stealthPrivateKey,
295
+ stealthAddress: stealthWallet.address,
296
+ wallet: stealthWallet
297
+ };
298
+ } catch (error) {
299
+ console.error("Error in deriveStealthAddress:", error);
300
+ throw error;
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Generates a stealth address for a recipient
306
+ * @param {string} recipientAddress - The recipient's address
307
+ * @param {string} signature - The signature
308
+ * @param {Object} options - Additional options
309
+ * @returns {Promise<StealthAddressGenerationResult>} The generated stealth address details
310
+ */
311
+ async generateStealthAddress(recipientAddress, signature, options = {}) {
312
+ try {
313
+ console.log("\nGenerating stealth address...");
314
+
315
+ // Get recipient's public keys
316
+ const recipientData = await this.gun.get("gun-eth").get("users").get(recipientAddress).get("publicKeys").then();
317
+ if (!recipientData?.viewingPublicKey || !recipientData?.spendingPublicKey) {
318
+ throw new Error("Recipient's public keys not found");
319
+ }
320
+ console.log("Retrieved recipient keys:", {
321
+ viewing: recipientData.viewingPublicKey.slice(0, 20) + "...",
322
+ spending: recipientData.spendingPublicKey.slice(0, 20) + "..."
323
+ });
324
+
325
+ // Get sender's keys
326
+ const senderAddress = await this.gun.verifySignature(MESSAGE_TO_SIGN, signature);
327
+ const password = generatePassword(signature);
328
+ const senderData = await this.gun.get("gun-eth").get("users").get(senderAddress).then();
329
+ if (!senderData?.s_pair) {
330
+ throw new Error("Sender's keys not found");
331
+ }
332
+
333
+ // Decrypt sender's spending pair
334
+ let spendingKeyPair;
335
+ try {
336
+ const decryptedData = await decrypt(senderData.s_pair, password);
337
+ spendingKeyPair = typeof decryptedData === 'string' ? JSON.parse(decryptedData) : decryptedData;
338
+ } catch (error) {
339
+ console.error("Error decrypting spending pair:", error);
340
+ throw new Error("Unable to decrypt spending pair");
341
+ }
342
+
343
+ // Generate shared secret using SEA ECDH
344
+ const sharedSecret = await deriveSharedKey(recipientData.viewingPublicKey, spendingKeyPair);
345
+ if (!sharedSecret) {
346
+ throw new Error("Unable to generate shared secret");
347
+ }
348
+ console.log("Generated shared secret");
349
+
350
+ // Derive stealth address
351
+ const {
352
+ stealthAddress
353
+ } = this.deriveStealthAddress(sharedSecret, recipientData.spendingPublicKey);
354
+ return {
355
+ stealthAddress,
356
+ senderPublicKey: spendingKeyPair.epub,
357
+ spendingPublicKey: recipientData.spendingPublicKey
358
+ };
359
+ } catch (error) {
360
+ console.error("Error generating stealth address:", error);
361
+ throw error;
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Announces a stealth payment on-chain or off-chain
367
+ * @param {string} stealthAddress - The stealth address of the recipient
368
+ * @param {string} senderPublicKey - The sender's public key
369
+ * @param {string} spendingPublicKey - The recipient's spending public key
370
+ * @param {string} signature - The sender's signature
371
+ * @param {StealthPaymentOptions} options - Additional options
372
+ * @returns {Promise<{stealthAddress: string, senderPublicKey: string, spendingPublicKey: string, timestamp: number, source: string}>}
373
+ */
374
+ async announceStealthPayment(stealthAddress, senderPublicKey, spendingPublicKey, signature, options = {}) {
375
+ try {
376
+ const {
377
+ chain = 'localhost',
378
+ onChain = false
379
+ } = options;
380
+
381
+ // Verifica la firma
382
+ await this.gun.verifySignature(MESSAGE_TO_SIGN, signature);
383
+ if (onChain) {
384
+ const contract = await this.getContract(chain);
385
+
386
+ // Ottieni la devFee dal contratto
387
+ let devFee;
388
+ try {
389
+ devFee = await contract.devFee();
390
+ } catch (error) {
391
+ console.warn("Errore nel leggere devFee, uso il valore di default:", error);
392
+ devFee = ethers.ethers.parseEther("0.0001"); // 0.0001 ETH come default
393
+ }
394
+
395
+ // Esegui l'annuncio on-chain
396
+ const tx = await contract.announcePayment(senderPublicKey, spendingPublicKey, stealthAddress, {
397
+ value: devFee
398
+ });
399
+ await tx.wait();
400
+ console.log("Payment announced on-chain, tx:", tx.hash);
401
+ }
402
+
403
+ // Salva anche off-chain
404
+ const announcement = {
405
+ stealthAddress,
406
+ senderPublicKey,
407
+ spendingPublicKey,
408
+ timestamp: Date.now(),
409
+ source: onChain ? 'both' : 'off-chain'
410
+ };
411
+ this.gun.get('gun-eth').get('stealth').get('announcements').set(announcement);
412
+ return announcement;
413
+ } catch (error) {
414
+ console.error("Error announcing stealth payment:", error);
415
+ throw error;
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Retrieves stealth payments
421
+ * @param {string} signature - The signature
422
+ * @param {Object} options - Additional options
423
+ * @returns {Promise<Array>} The list of stealth payments
424
+ */
425
+ async getStealthPayments(signature, options = {}) {
426
+ try {
427
+ const payments = [];
428
+
429
+ // Set default options
430
+ const defaultOptions = {
431
+ source: 'both',
432
+ chain: 'localhost' // Default to localhost
433
+ };
434
+ options = {
435
+ ...defaultOptions,
436
+ ...options
437
+ };
438
+ if (options.source === 'onChain' || options.source === 'both') {
439
+ const contract = await this.getContract(options.chain);
440
+ const totalAnnouncements = await contract.getAnnouncementsCount();
441
+ const totalCount = Number(totalAnnouncements.toString());
442
+ if (totalCount > 0) {
443
+ const batchSize = 100;
444
+ const lastIndex = totalCount - 1;
445
+ for (let i = 0; i <= lastIndex; i += batchSize) {
446
+ const toIndex = Math.min(i + batchSize - 1, lastIndex);
447
+ const batch = await contract.getAnnouncementsInRange(i, toIndex);
448
+ for (const announcement of batch) {
449
+ try {
450
+ // Verify announcement is valid
451
+ if (!announcement || !announcement.stealthAddress) continue;
452
+
453
+ // Format announcement
454
+ const formattedAnnouncement = {
455
+ stealthAddress: announcement.stealthAddress,
456
+ senderPublicKey: announcement.senderPublicKey,
457
+ spendingPublicKey: announcement.spendingPublicKey,
458
+ timestamp: Number(announcement.timestamp || 0)
459
+ };
460
+
461
+ // Try to recover funds
462
+ try {
463
+ const recoveredWallet = await this.recoverStealthFunds(formattedAnnouncement.stealthAddress, formattedAnnouncement.senderPublicKey, signature, formattedAnnouncement.spendingPublicKey);
464
+
465
+ // If recovery succeeds, add announcement
466
+ payments.push({
467
+ ...formattedAnnouncement,
468
+ source: 'onChain',
469
+ wallet: recoveredWallet
470
+ });
471
+ } catch (e) {
472
+ // If recovery fails, announcement wasn't for this user
473
+ console.log(`Skipping announcement: ${e.message}`);
474
+ continue;
475
+ }
476
+ } catch (e) {
477
+ console.log(`Error processing announcement: ${e.message}`);
478
+ continue;
479
+ }
480
+ }
481
+ }
482
+ }
483
+ }
484
+ if (options.source === 'offChain' || options.source === 'both') {
485
+ const offChainPayments = await new Promise(resolve => {
486
+ const p = [];
487
+ this.gun.get("gun-eth").get("stealth-payments").map().once((payment, id) => {
488
+ if (payment?.stealthAddress) {
489
+ p.push({
490
+ ...payment,
491
+ id,
492
+ source: 'offChain'
493
+ });
494
+ }
495
+ });
496
+ setTimeout(() => resolve(p), 2000);
497
+ });
498
+
499
+ // Filter and process off-chain payments
500
+ for (const payment of offChainPayments) {
501
+ try {
502
+ const recoveredWallet = await this.recoverStealthFunds(payment.stealthAddress, payment.senderPublicKey, signature, payment.spendingPublicKey);
503
+ payments.push({
504
+ ...payment,
505
+ wallet: recoveredWallet
506
+ });
507
+ } catch (e) {
508
+ console.log(`Skipping off-chain payment: ${e.message}`);
509
+ continue;
510
+ }
511
+ }
512
+ }
513
+ return payments;
514
+ } catch (error) {
515
+ console.error("Error retrieving stealth payments:", error);
516
+ throw error;
517
+ }
518
+ }
519
+
520
+ /**
521
+ * Recovers stealth funds
522
+ * @param {string} stealthAddress - The stealth address
523
+ * @param {string} senderPublicKey - The sender's public key
524
+ * @param {string} signature - The signature
525
+ * @param {string} spendingPublicKey - The spending public key
526
+ * @returns {Promise<StealthPaymentResult>} The recovered funds details
527
+ */
528
+ async recoverStealthFunds(stealthAddress, senderPublicKey, signature, spendingPublicKey) {
529
+ try {
530
+ const gun = this.gun;
531
+ const password = generatePassword(signature);
532
+
533
+ // Get own key pairs
534
+ const myAddress = await verifySignature(MESSAGE_TO_SIGN, signature);
535
+ const encryptedData = await gun.get("gun-eth").get("users").get(myAddress).then();
536
+ if (!encryptedData?.v_pair) {
537
+ throw new Error("Keys not found");
538
+ }
539
+
540
+ // Decrypt viewing pair
541
+ let viewingKeyPair;
542
+ try {
543
+ const decryptedViewingData = await decrypt(encryptedData.v_pair, password);
544
+ viewingKeyPair = typeof decryptedViewingData === 'string' ? JSON.parse(decryptedViewingData) : decryptedViewingData;
545
+ } catch (error) {
546
+ throw new Error("Unable to decrypt keys");
547
+ }
548
+
549
+ // Generate shared secret using SEA ECDH
550
+ const sharedSecret = await SEA$1.secret(senderPublicKey, viewingKeyPair);
551
+ if (!sharedSecret) {
552
+ throw new Error("Unable to generate shared secret");
553
+ }
554
+ const {
555
+ wallet,
556
+ stealthAddress: recoveredAddress
557
+ } = this.deriveStealthAddress(sharedSecret, spendingPublicKey);
558
+
559
+ // Verify address matches
560
+ if (recoveredAddress.toLowerCase() !== stealthAddress.toLowerCase()) {
561
+ throw new Error("Recovered stealth address does not match");
562
+ }
563
+ return {
564
+ wallet,
565
+ address: recoveredAddress
566
+ };
567
+ } catch (error) {
568
+ console.error("Error recovering stealth funds:", error);
569
+ throw error;
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Publishes user's stealth keys
575
+ * @param {string} signature - The signature
576
+ * @returns {Promise<PublicKeysResult>} The published public keys
577
+ */
578
+ async publishStealthKeys(signature) {
579
+ try {
580
+ // Verifica la firma
581
+ const signer = await getSigner();
582
+ if (!signer) {
583
+ throw new Error("No signer available");
584
+ }
585
+ const address = await signer.getAddress();
586
+ const recoveredAddress = await verifySignature(MESSAGE_TO_SIGN, signature);
587
+ if (!recoveredAddress || recoveredAddress.toLowerCase() !== address.toLowerCase()) {
588
+ throw new Error("Invalid signature");
589
+ }
590
+
591
+ // Genera la password dal signature
592
+ const password = generatePassword(signature);
593
+ if (!password) {
594
+ throw new Error("No encryption key.");
595
+ }
596
+
597
+ // Genera key pairs
598
+ const viewingKeyPair = await SEA$1.pair();
599
+ const spendingKeyPair = await SEA$1.pair();
600
+
601
+ // Encrypt key pairs
602
+ const encryptedViewingPair = await SEA$1.encrypt(JSON.stringify(viewingKeyPair), password);
603
+ const encryptedSpendingPair = await SEA$1.encrypt(JSON.stringify(spendingKeyPair), password);
604
+
605
+ // Save public keys and encrypted pairs
606
+ await this.gun.get('gun-eth').get('users').get(address).put({
607
+ publicKeys: {
608
+ viewingPublicKey: viewingKeyPair.epub,
609
+ spendingPublicKey: spendingKeyPair.epub
610
+ },
611
+ v_pair: encryptedViewingPair,
612
+ s_pair: encryptedSpendingPair
613
+ });
614
+ return {
615
+ viewingPublicKey: viewingKeyPair.epub,
616
+ spendingPublicKey: spendingKeyPair.epub
617
+ };
618
+ } catch (error) {
619
+ console.error("Error publishing stealth keys:", error);
620
+ throw error;
621
+ }
622
+ }
623
+ }
624
+
625
+ // @ts-nocheck
626
+ let contractAddresses$2 = {
627
+ PROOF_OF_INTEGRITY_ADDRESS,
628
+ STEALTH_ANNOUNCER_ADDRESS
629
+ };
630
+
631
+ /**
632
+ * @typedef {import('ethers').Eip1193Provider} EthereumProvider
633
+ */
634
+
635
+ /** @typedef {Window & { ethereum?: EthereumProvider }} WindowWithEthereum */
636
+
637
+ const window$1 = globalThis.window;
638
+
639
+ // Singleton for signer management
640
+ class SignerManager {
641
+ static instance = null;
642
+ static provider = null;
643
+ static signer = null;
644
+ static rpcUrl = "";
645
+ static privateKey = "";
646
+ static getInstance() {
647
+ if (!SignerManager.instance) {
648
+ SignerManager.instance = new SignerManager();
649
+ }
650
+ return SignerManager.instance;
651
+ }
652
+ static async getSigner() {
653
+ if (SignerManager.signer) {
654
+ return SignerManager.signer;
655
+ }
656
+ if (SignerManager.rpcUrl !== "" && SignerManager.privateKey !== "") {
657
+ SignerManager.provider = new ethers.ethers.JsonRpcProvider(SignerManager.rpcUrl);
658
+ const wallet = new ethers.ethers.Wallet(SignerManager.privateKey, SignerManager.provider);
659
+ // Create a proxy instead of modifying the wallet directly
660
+ SignerManager.signer = new Proxy(wallet, {
661
+ get(target, prop) {
662
+ if (prop === 'address') {
663
+ return target.address;
664
+ }
665
+ if (prop === 'privateKey') {
666
+ return SignerManager.privateKey;
667
+ }
668
+ return target[prop];
669
+ }
670
+ });
671
+ return SignerManager.signer;
672
+ }
673
+ if (typeof window$1 !== "undefined" && window$1?.ethereum) {
674
+ /** @type {WindowWithEthereum} */
675
+ const windowWithEthereum = window$1;
676
+ await windowWithEthereum.ethereum?.request({
677
+ method: "eth_requestAccounts"
678
+ });
679
+ const browserProvider = new ethers.ethers.BrowserProvider(windowWithEthereum.ethereum);
680
+ const signer = await browserProvider.getSigner();
681
+ // Create a proxy for the browser signer as well
682
+ SignerManager.signer = new Proxy(signer, {
683
+ get(target, prop) {
684
+ if (prop === 'address') {
685
+ return target.getAddress();
686
+ }
687
+ if (prop === 'privateKey') {
688
+ return '';
689
+ }
690
+ return target[prop];
691
+ }
692
+ });
693
+ return SignerManager.signer;
694
+ }
695
+ throw new Error("No valid Ethereum provider found. Call setSigner first.");
696
+ }
697
+ static setSigner(newRpcUrl, newPrivateKey) {
698
+ SignerManager.rpcUrl = newRpcUrl;
699
+ SignerManager.privateKey = newPrivateKey;
700
+ SignerManager.provider = new ethers.ethers.JsonRpcProvider(newRpcUrl);
701
+ const wallet = new ethers.ethers.Wallet(newPrivateKey, SignerManager.provider);
702
+ // Create a proxy for the new signer
703
+ SignerManager.signer = new Proxy(wallet, {
704
+ get(target, prop) {
705
+ if (prop === 'address') {
706
+ return target.address;
707
+ }
708
+ if (prop === 'privateKey') {
709
+ return SignerManager.privateKey;
710
+ }
711
+ return target[prop];
712
+ }
713
+ });
714
+ console.log("Signer configured with address:", wallet.address);
715
+ return SignerManager.instance;
716
+ }
717
+ }
718
+ function generateRandomId() {
719
+ return ethers.ethers.hexlify(ethers.ethers.randomBytes(32));
720
+ }
721
+ function getContractAddresses(chain = 'localhost') {
722
+ if (isLocalEnvironment && contractAddresses$2) {
723
+ console.log('Using local contract addresses:', contractAddresses$2);
724
+ return contractAddresses$2;
725
+ }
726
+ const chainConfig = getAddressesForChain(chain);
727
+ console.log(`Using ${chain} configuration:`, chainConfig);
728
+ return {
729
+ PROOF_OF_INTEGRITY_ADDRESS: chainConfig.PROOF_OF_INTEGRITY_ADDRESS,
730
+ STEALTH_ANNOUNCER_ADDRESS: chainConfig.STEALTH_ANNOUNCER_ADDRESS
731
+ };
732
+ }
733
+ function setSigner$1(newRpcUrl, newPrivateKey) {
734
+ return SignerManager.setSigner(newRpcUrl, newPrivateKey);
735
+ }
736
+ async function getSigner$1(chain = 'localhost') {
737
+ return SignerManager.getSigner();
738
+ }
739
+
740
+ // @ts-check
741
+
742
+
743
+ /**
744
+ * Result of proof verification
745
+ * @typedef {Object} ProofResult
746
+ * @property {boolean} isValid - Whether the data is valid
747
+ * @property {number} timestamp - Verification timestamp
748
+ * @property {string} updater - Address of last update
749
+ */
750
+
751
+ /**
752
+ * Latest record
753
+ * @typedef {Object} LatestRecord
754
+ * @property {string} contentHash - Content hash
755
+ * @property {number} timestamp - Last update timestamp
756
+ * @property {string} updater - Address of last updater
757
+ */
758
+
759
+ /**
760
+ * Callback for proof operations
761
+ * @typedef {Object} ProofCallback
762
+ * @property {boolean} [ok] - Whether operation was successful
763
+ * @property {string} [message] - Success/error message
764
+ * @property {string} [err] - Error message
765
+ * @property {number} [timestamp] - Operation timestamp
766
+ * @property {string} [updater] - Updater address
767
+ * @property {LatestRecord} [latestRecord] - Latest record
768
+ * @property {string} [nodeId] - Created node ID
769
+ * @property {string} [txHash] - Transaction hash
770
+ */
771
+
772
+ /**
773
+ * Options for proof transactions
774
+ * @typedef {Object} ProofOptions
775
+ * @property {number} [gasLimit] - Gas limit for transaction
776
+ * @property {number} [gasPrice] - Gas price
777
+ */
778
+
779
+ /**
780
+ * Extended Gun with additional methods
781
+ * @typedef {import('gun').IGun & { get: (path: string) => any }} ExtendedGun
782
+ */
783
+
784
+ /**
785
+ * Extended transaction with additional methods
786
+ * @typedef {import('ethers').ContractTransaction & { wait: () => Promise<any>, hash: string }} ExtendedTransaction
787
+ */
788
+
789
+ /**
790
+ * Configuration for ProofChain
791
+ * @typedef {Object} ProofChainConfig
792
+ * @property {string} [contractAddress] - Address of deployed ProofOfIntegrity contract
793
+ * @property {string} [abi] - ABI of the contract (optional, defaults to PROOF_OF_INTEGRITY_ABI)
794
+ */
795
+
796
+ /**
797
+ * Class for handling blockchain proof of integrity
798
+ * @class
799
+ */
800
+ class ProofChain {
801
+ /**
802
+ * Creates a new ProofChain instance
803
+ * @param {Gun} gun - Gun instance
804
+ * @param {ProofChainConfig} [config] - Optional configuration
805
+ */
806
+ constructor(gun, config = {}) {
807
+ /** @type {ExtendedGun} */
808
+ this.gun = /** @type {ExtendedGun} */gun;
809
+ this.contractAddress = config.contractAddress;
810
+ this.contractAbi = config.abi || PROOF_OF_INTEGRITY_ABI;
811
+ }
812
+
813
+ /**
814
+ * Gets contract instance for the specified chain
815
+ * @param {string} chain - Chain identifier
816
+ * @returns {Promise<Contract>} Contract instance
817
+ */
818
+ async getContract(chain) {
819
+ const signer = await getSigner$1(chain);
820
+ const address = this.contractAddress || getContractAddresses(chain).PROOF_OF_INTEGRITY_ADDRESS;
821
+ return new ethers.ethers.Contract(address, this.contractAbi, signer);
822
+ }
823
+
824
+ /**
825
+ * Converts an ID to bytes32
826
+ * @param {ethers.BytesLike} id - ID to convert
827
+ * @returns {string} ID converted to bytes32
828
+ */
829
+ convertToBytes32(id) {
830
+ if (typeof id === "string") {
831
+ // If id is already in hex format with 0x
832
+ if (id.startsWith("0x")) {
833
+ return ethers.ethers.zeroPadValue(id, 32);
834
+ }
835
+ // If it's a hex string without 0x, add 0x
836
+ if (/^[0-9a-fA-F]{64}$/.test(id)) {
837
+ return ethers.ethers.zeroPadValue(`0x${id}`, 32);
838
+ }
839
+ // Otherwise encode string as UTF8 and calculate keccak256
840
+ return ethers.ethers.keccak256(ethers.ethers.toUtf8Bytes(id));
841
+ }
842
+ // If already BytesLike, ensure correct length
843
+ return ethers.ethers.zeroPadValue(id, 32);
844
+ }
845
+
846
+ /**
847
+ * Verifies data on-chain
848
+ * @param {string} chain - Chain identifier
849
+ * @param {string} nodeId - Node ID to verify
850
+ * @param {string} contentHash - Content hash to verify
851
+ * @returns {Promise<ProofResult>} Verification result
852
+ */
853
+ async verifyOnChain(chain, nodeId, contentHash) {
854
+ try {
855
+ const contract = await this.getContract(chain);
856
+ const nodeIdBytes = this.convertToBytes32(nodeId);
857
+
858
+ // If no contentHash, get latest record
859
+ if (!contentHash) {
860
+ const record = await this.getLatestRecord(chain, nodeId);
861
+ contentHash = record.contentHash;
862
+ }
863
+
864
+ // Ensure contentHash is in correct format
865
+ const contentHashBytes = typeof contentHash === "string" && contentHash.startsWith("0x") ? ethers.ethers.zeroPadValue(contentHash, 32) : ethers.ethers.zeroPadValue(`0x${contentHash}`, 32);
866
+ const [isValid, timestamp, updater] = await contract.verifyData(nodeIdBytes, contentHashBytes);
867
+ return {
868
+ isValid,
869
+ timestamp: Number(timestamp),
870
+ updater
871
+ };
872
+ } catch (error) {
873
+ console.error("Error verifying data:", error);
874
+ throw error;
875
+ }
876
+ }
877
+
878
+ /**
879
+ * Writes data on-chain
880
+ * @param {string} chain - Chain identifier
881
+ * @param {string} nodeId - Node ID to write
882
+ * @param {string} contentHash - Content hash to write
883
+ * @param {Object} options - Transaction options
884
+ * @returns {Promise<ExtendedTransaction>} Resulting transaction
885
+ */
886
+ async writeOnChain(chain, nodeId, contentHash, options = {}) {
887
+ try {
888
+ const nodeIdBytes = this.convertToBytes32(nodeId);
889
+ const contentHashBytes = typeof contentHash === "string" && contentHash.startsWith("0x") ? ethers.ethers.zeroPadValue(contentHash, 32) : ethers.ethers.zeroPadValue(`0x${contentHash}`, 32);
890
+ const contract = await this.getContract(chain);
891
+ const tx = await contract.updateData(nodeIdBytes, contentHashBytes, options);
892
+ return tx;
893
+ } catch (error) {
894
+ console.error("Error writing to chain:", error);
895
+ throw error;
896
+ }
897
+ }
898
+
899
+ /**
900
+ * Gets the latest record
901
+ * @param {string} chain - Chain name
902
+ * @param {ethers.BytesLike} nodeId - Node ID
903
+ * @returns {Promise<LatestRecord>} Latest record
904
+ */
905
+ async getLatestRecord(chain, nodeId) {
906
+ const contract = await this.getContract(chain);
907
+ const nodeIdBytes = this.convertToBytes32(nodeId);
908
+ const [contentHash, timestamp, updater] = await contract.getLatestRecord(nodeIdBytes);
909
+ return {
910
+ contentHash,
911
+ timestamp,
912
+ updater
913
+ };
914
+ }
915
+
916
+ /**
917
+ * Verifies or writes data with proof of integrity
918
+ * @param {string} chain - Chain name
919
+ * @param {string} nodeId - Node ID (for verification)
920
+ * @param {object} data - Data to write
921
+ * @param {function} callback - Callback function
922
+ * @param {object} options - Transaction options
923
+ * @returns {Promise<this>} Current instance
924
+ */
925
+ async proof(chain, nodeId, data, callback, options = {}) {
926
+ try {
927
+ if (!data) {
928
+ // Verify
929
+ const result = await this.verifyOnChain(chain, nodeId, null);
930
+ const latestRecord = await this.getLatestRecord(chain, nodeId);
931
+ callback({
932
+ ok: result.isValid,
933
+ ...result,
934
+ latestRecord: {
935
+ contentHash: latestRecord.contentHash,
936
+ timestamp: Number(latestRecord.timestamp),
937
+ updater: latestRecord.updater
938
+ }
939
+ });
940
+
941
+ // Verify current data in GunDB
942
+ this.gun.get(nodeId).once(gunData => {
943
+ if (gunData) {
944
+ console.log("\n🔄 Re-analyzing current data state...");
945
+ const currentData = {
946
+ ...gunData
947
+ };
948
+ delete currentData._;
949
+ const storedHash = currentData._contentHash;
950
+ delete currentData._contentHash;
951
+ const currentDataString = JSON.stringify(currentData);
952
+ const currentHash = ethers.ethers.keccak256(ethers.ethers.toUtf8Bytes(currentDataString));
953
+ console.log("Current data state:", currentData);
954
+ console.log("Calculated new hash:", currentHash);
955
+ console.log("Original stored hash:", storedHash);
956
+ if (currentHash !== storedHash) {
957
+ console.log("\n⚠️ WARNING: Data has been tampered!");
958
+ } else {
959
+ console.log("\n✅ Data integrity check passed: No tampering detected");
960
+ }
961
+ }
962
+ });
963
+ return this;
964
+ }
965
+
966
+ // Calculate content hash
967
+ const dataString = JSON.stringify(data);
968
+ const contentHash = ethers.ethers.keccak256(ethers.ethers.toUtf8Bytes(dataString));
969
+
970
+ // If no nodeId, generate a new one
971
+ if (!nodeId) {
972
+ nodeId = generateRandomId();
973
+ }
974
+
975
+ // Save data to GUN
976
+ data._contentHash = contentHash;
977
+ this.gun.get(nodeId).put(data);
978
+
979
+ // Write hash to blockchain
980
+ const tx = await this.writeOnChain(chain, nodeId, contentHash, options);
981
+ await tx.wait();
982
+ callback({
983
+ ok: true,
984
+ nodeId,
985
+ txHash: tx.hash
986
+ });
987
+ } catch (error) {
988
+ callback({
989
+ err: error.message
990
+ });
991
+ }
992
+ return this;
993
+ }
994
+
995
+ /**
996
+ * Extends Gun with proof methods
997
+ * @param {typeof Gun} Gun - Gun constructor
998
+ */
999
+ static extendGun(Gun) {
1000
+ // @ts-ignore
1001
+ Gun.chain.proof = function (/** @type {string} */chain, /** @type {string} */nodeId, /** @type {any} */data, /** @type {Function} */callback, /** @type {ProofChainConfig & any} */options) {
1002
+ // @ts-ignore
1003
+ const proofChain = new ProofChain(this, options);
1004
+ return proofChain.proof(chain, nodeId, data, callback, options);
1005
+ };
1006
+ }
1007
+ }
1008
+
1009
+ let contractAddresses$1 = {};
1010
+ if (typeof window === 'undefined') {
1011
+ try {
1012
+ const __filename = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('gun-eth.cjs', document.baseURI).href)));
1013
+ const __dirname = path.dirname(__filename);
1014
+ const rawdata = fs.readFileSync(path.join(__dirname, '../config/contract-address.json'), 'utf8');
1015
+ contractAddresses$1 = JSON.parse(rawdata);
1016
+ console.log("Loaded contract addresses:", contractAddresses$1);
1017
+ } catch (error) {
1018
+ console.warn("Warning: contract-address.json not found or invalid");
1019
+ }
1020
+ }
1021
+ const LOCAL_CONFIG = {
1022
+ STEALTH_ANNOUNCER_ADDRESS: contractAddresses$1.StealthAnnouncer || '0x5FbDB2315678afecb367f032d93F642f64180aa3',
1023
+ PROOF_OF_INTEGRITY_ADDRESS: contractAddresses$1.ProofOfIntegrity || '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512',
1024
+ BUBBLE_REGISTRY_ADDRESS: contractAddresses$1.BubbleRegistry || '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0',
1025
+ RPC_URL: 'http://127.0.0.1:8545',
1026
+ CHAIN_ID: 31337
1027
+ };
1028
+
1029
+ // @ts-check
1030
+
1031
+
1032
+ /**
1033
+ * @typedef {import('gun').IGunChain<any, any, any, any>} IGunChain
1034
+ * @typedef {import('gun').IGunInstance} IGunInstance
1035
+ * @typedef {import('gun').IGun} IGun
1036
+ * @typedef {import('gun').GunOptions} GunOptions
1037
+ * @typedef {import('gun').GunSchema} GunSchema
1038
+ * @typedef {import('gun').GunHookCallbackCreate} GunHookCallbackCreate
1039
+ * @typedef {import('gun').GunHookCallbackOpt} GunHookCallbackOpt
1040
+ */
1041
+
1042
+ /**
1043
+ * @typedef {Object} GunMethods
1044
+ * @property {(path: string) => IGunInstance} get - Get method
1045
+ * @property {(data: any) => IGunInstance} put - Put method
1046
+ * @property {(data: any) => IGunInstance} set - Set method
1047
+ * @property {() => IGunInstance} map - Map method
1048
+ * @property {() => IGunInstance} back - Back method
1049
+ * @property {() => IGunInstance} off - Off method
1050
+ * @property {{
1051
+ * (event: "create", callback: GunHookCallbackCreate): void;
1052
+ * (event: "opt", callback: GunHookCallbackOpt): void;
1053
+ * (event: string, callback: Function): IGunInstance;
1054
+ * }} on - On method
1055
+ * @property {(callback: (data: any, key: string) => void) => IGunInstance} once - Once method
1056
+ */
1057
+
1058
+ /**
1059
+ * @typedef {Object} GunBase
1060
+ * @property {Object} state - Gun state
1061
+ * @property {IGunChain & IGunInstance} chain - Gun chain
1062
+ * @property {Object} SEA - Gun SEA
1063
+ * @property {(message: string, signature: string) => Promise<string>} verifySignature - Verify signature method
1064
+ * @property {Object} _ - Gun internal properties
1065
+ * @property {Object} user - Gun user instance
1066
+ * @property {Object} opt - Gun options
1067
+ */
1068
+
1069
+ /**
1070
+ * @typedef {GunBase & GunMethods & {
1071
+ * (options?: GunOptions): IGunInstance;
1072
+ * new (options?: GunOptions): IGunInstance;
1073
+ * }} GunExtended
1074
+ */
1075
+
1076
+ /**
1077
+ * @typedef {Object} StealthMethodsBase
1078
+ * @property {function(this: IGunInstance, string, string, Object): Promise<any>} generateStealthAddress
1079
+ * @property {function(this: IGunInstance, string, string, string, string, Object): Promise<{stealthAddress: string, senderPublicKey: string, spendingPublicKey: string, timestamp: number, source: string}>} announceStealthPayment
1080
+ * @property {function(this: IGunInstance, string, Object): Promise<Array<any>>} getStealthPayments
1081
+ * @property {function(this: IGunInstance, string, string, string, string): Promise<any>} recoverStealthFunds
1082
+ * @property {function(this: IGunInstance, string): Promise<any>} publishStealthKeys
1083
+ * @property {function(this: IGunInstance, string, ...any[]): Promise<any>} stealth
1084
+ * @property {function(this: IGunInstance, function(any): void): any} monitorStealthEvents
1085
+ */
1086
+
1087
+ /**
1088
+ * @typedef {Object} ExtendedSigner
1089
+ * @property {string} address - Ethereum address
1090
+ * @property {string} privateKey - Private key
1091
+ * @property {function(string): Promise<string>} signMessage - Signs a message
1092
+ * @property {function(): Promise<string>} getAddress - Gets the signer address
1093
+ * @property {Object} provider - Provider instance
1094
+ */
1095
+
1096
+ /**
1097
+ * @typedef {Object} BaseMethods
1098
+ * @property {string} MESSAGE_TO_SIGN
1099
+ * @property {function} setSigner
1100
+ * @property {function} getSigner
1101
+ * @property {function} verifySignature
1102
+ * @property {function} generatePassword
1103
+ * @property {function} createSignature
1104
+ * @property {function} createAndStoreEncryptedPair
1105
+ * @property {function} getAndDecryptPair
1106
+ * @property {function} ethToGunAccount
1107
+ * @property {function} gunToEthAccount
1108
+ * @property {function} getAddressesForChain
1109
+ */
1110
+
1111
+ /**
1112
+ * @typedef {StealthMethodsBase & IGunInstance} StealthMethods
1113
+ */
1114
+
1115
+ /**
1116
+ * @typedef {Object} ExtendedGunBase
1117
+ * @property {Object} state - Gun state
1118
+ * @property {IGunChain & IGunInstance} chain - Gun chain
1119
+ * @property {Object} SEA - Gun SEA
1120
+ * @property {(path: string) => any} get - Get method
1121
+ * @property {(data: any) => void} put - Put method
1122
+ * @property {(data: any) => void} set - Set method
1123
+ * @property {() => any} map - Map method
1124
+ * @property {(event: string, callback: Function) => any} on - On method
1125
+ * @property {(callback: (data: any, key: string) => void) => void} once - Once method
1126
+ * @property {(message: string, signature: string) => Promise<string>} verifySignature - Verify signature method
1127
+ * @property {Object} _ - Gun internal properties
1128
+ * @property {Object} user - Gun user instance
1129
+ * @property {Object} opt - Gun options
1130
+ */
1131
+
1132
+ /**
1133
+ * @typedef {ExtendedGunBase & IGun} ExtendedGun
1134
+ */
1135
+
1136
+ /**
1137
+ * @typedef {ExtendedGun & IGunInstance} FullGunInstance
1138
+ */
1139
+
1140
+ /**
1141
+ * @typedef {Object} GunHookCallbacks
1142
+ * @property {function} create - Create callback
1143
+ * @property {function} put - Put callback
1144
+ * @property {function} get - Get callback
1145
+ * @property {function} opt - Opt callback
1146
+ */
1147
+
1148
+ const MESSAGE_TO_SIGN = "Access GunDB with Ethereum";
1149
+ let contractAddresses = {
1150
+ PROOF_OF_INTEGRITY_ADDRESS,
1151
+ STEALTH_ANNOUNCER_ADDRESS
1152
+ };
1153
+
1154
+ // =============================================
1155
+ // INITIALIZATION
1156
+ // =============================================
1157
+
1158
+ async function initializeTextEncoder() {
1159
+ if (typeof window === "undefined") {
1160
+ const util = await import('util');
1161
+ global.TextEncoder = util.TextEncoder;
1162
+ global.TextDecoder = util.TextDecoder;
1163
+ }
1164
+ }
1165
+ async function initialize(chain = 'localhost') {
1166
+ await initializeTextEncoder();
1167
+ contractAddresses = isLocalEnvironment ? getContractAddresses(chain) : getContractAddresses(chain);
1168
+ return contractAddresses;
1169
+ }
1170
+
1171
+ // =============================================
1172
+ // UTILITY FUNCTIONS
1173
+ // =============================================
1174
+
1175
+ /**
1176
+ * @param {string} newRpcUrl
1177
+ * @param {string} newPrivateKey
1178
+ */
1179
+ function setSigner(newRpcUrl, newPrivateKey) {
1180
+ return setSigner$1(newRpcUrl, newPrivateKey);
1181
+ }
1182
+ async function getSigner(chain = 'localhost') {
1183
+ return getSigner$1(chain);
1184
+ }
1185
+
1186
+ /**
1187
+ * @param {ethers.BytesLike} signature
1188
+ */
1189
+ function generatePassword(signature) {
1190
+ try {
1191
+ const hexSignature = ethers.ethers.hexlify(signature);
1192
+ const hash = ethers.ethers.keccak256(hexSignature);
1193
+ console.log("Generated password:", hash);
1194
+ return hash;
1195
+ } catch (error) {
1196
+ console.error("Error generating password:", error);
1197
+ return null;
1198
+ }
1199
+ }
1200
+
1201
+ /**
1202
+ * @param {string | Uint8Array} message
1203
+ * @param {ethers.SignatureLike} signature
1204
+ */
1205
+ async function verifySignature(message, signature) {
1206
+ try {
1207
+ const recoveredAddress = ethers.ethers.verifyMessage(message, signature);
1208
+ return recoveredAddress;
1209
+ } catch (error) {
1210
+ console.error("Error verifying signature:", error);
1211
+ return null;
1212
+ }
1213
+ }
1214
+
1215
+ /**
1216
+ * @param {string} gunPrivateKey
1217
+ */
1218
+ async function gunToEthAccount(gunPrivateKey) {
1219
+ const base64UrlToHex = (/** @type {string} */base64url) => {
1220
+ const padding = "=".repeat((4 - base64url.length % 4) % 4);
1221
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
1222
+ const binary = atob(base64);
1223
+ return Array.from(binary, char => char.charCodeAt(0).toString(16).padStart(2, "0")).join("");
1224
+ };
1225
+ const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
1226
+ const wallet = new ethers.ethers.Wallet(hexPrivateKey);
1227
+
1228
+ // Genera nuove coppie di chiavi SEA
1229
+ const pair = await SEA$1.pair();
1230
+ const v_pair = await SEA$1.pair();
1231
+ const s_pair = await SEA$1.pair();
1232
+
1233
+ // Genera password e cifra le coppie
1234
+ const signature = await wallet.signMessage(MESSAGE_TO_SIGN);
1235
+ const password = generatePassword(signature);
1236
+ const encryptedPair = await encrypt(pair, password);
1237
+ const encryptedV_pair = await encrypt(v_pair, password);
1238
+ const encryptedS_pair = await encrypt(s_pair, password);
1239
+
1240
+ // Genera gli account di viewing e spending
1241
+ const viewingAccount = await gunToEthAccount(v_pair.priv);
1242
+ const spendingAccount = await gunToEthAccount(s_pair.priv);
1243
+ return {
1244
+ account: wallet,
1245
+ publicKey: wallet.address,
1246
+ privateKey: hexPrivateKey,
1247
+ pair: pair,
1248
+ v_pair: v_pair,
1249
+ s_pair: s_pair,
1250
+ ethAddress: wallet.address,
1251
+ ethPrivateKey: hexPrivateKey,
1252
+ env_pair: encryptedPair,
1253
+ env_v_pair: encryptedV_pair,
1254
+ env_s_pair: encryptedS_pair,
1255
+ publicKeys: {
1256
+ viewingPublicKey: v_pair.epub,
1257
+ spendingPublicKey: spendingAccount.publicKey,
1258
+ ethViewingAddress: viewingAccount.publicKey
1259
+ }
1260
+ };
1261
+ }
1262
+
1263
+ /**
1264
+ * @param {string} encryptedPair
1265
+ * @param {any} password
1266
+ */
1267
+ async function decryptPair(encryptedPair, password) {
1268
+ try {
1269
+ const keypair = {
1270
+ epriv: password,
1271
+ epub: password
1272
+ };
1273
+ return await decrypt(encryptedPair, keypair);
1274
+ } catch (error) {
1275
+ console.error("Error decrypting key pair:", error);
1276
+ return null;
1277
+ }
1278
+ }
1279
+
1280
+ /**
1281
+ * @param {string} encryptedPair
1282
+ * @param {any} password
1283
+ */
1284
+ function decryptPairFromPassword(encryptedPair, password) {
1285
+ const encryptionKeypair = {
1286
+ epriv: password,
1287
+ epub: password
1288
+ };
1289
+ return decrypt(encryptedPair, encryptionKeypair);
1290
+ }
1291
+ async function ethToGunAccount() {
1292
+ const signer = /** @type {ExtendedSigner} */await getSigner();
1293
+ console.log("Signer:", signer);
1294
+ const signature = await signer.signMessage(MESSAGE_TO_SIGN);
1295
+ console.log("Signature:", signature);
1296
+ const password = generatePassword(signature);
1297
+ console.log("Password:", password);
1298
+ const pair = await SEA$1.pair();
1299
+ const v_pair = await SEA$1.pair();
1300
+ const s_pair = await SEA$1.pair();
1301
+ const encryptedPair = await encrypt(pair, password);
1302
+ const encryptedV_pair = await encrypt(v_pair, password);
1303
+ const encryptedS_pair = await encrypt(s_pair, password);
1304
+ const viewingAccount = await gunToEthAccount(v_pair.priv);
1305
+ const spendingAccount = await gunToEthAccount(s_pair.priv);
1306
+ return {
1307
+ pair: pair,
1308
+ v_pair: v_pair,
1309
+ s_pair: s_pair,
1310
+ ethAddress: signer.address,
1311
+ ethPrivateKey: signer.privateKey,
1312
+ env_pair: encryptedPair,
1313
+ env_v_pair: encryptedV_pair,
1314
+ env_s_pair: encryptedS_pair,
1315
+ publicKeys: {
1316
+ viewingPublicKey: v_pair.epub,
1317
+ spendingPublicKey: spendingAccount.publicKey,
1318
+ ethViewingAddress: viewingAccount.publicKey
1319
+ }
1320
+ };
1321
+ }
1322
+
1323
+ /**
1324
+ * @param {any} address
1325
+ */
1326
+ async function createAndStoreEncryptedPair(address) {
1327
+ const gun = this;
1328
+ try {
1329
+ const {
1330
+ pair,
1331
+ v_pair,
1332
+ s_pair,
1333
+ publicKeys
1334
+ } = await ethToGunAccount();
1335
+ gun.get("gun-eth").get("users").get(address).put({
1336
+ pair,
1337
+ v_pair,
1338
+ s_pair,
1339
+ publicKeys
1340
+ });
1341
+ console.log("Encrypted pairs and public keys stored for:", address);
1342
+ } catch (error) {
1343
+ console.error("Error creating and storing encrypted pair:", error);
1344
+ throw error;
1345
+ }
1346
+ }
1347
+
1348
+ /**
1349
+ * @param {any} address
1350
+ * @param {any} signature
1351
+ */
1352
+ async function getAndDecryptPair(address, signature) {
1353
+ try {
1354
+ const gun = this;
1355
+ const encryptedData = await gun.get("gun-eth").get("users").get(address).get("pair").then();
1356
+ if (!encryptedData) {
1357
+ throw new Error("No encrypted data found for this address");
1358
+ }
1359
+ const password = generatePassword(signature);
1360
+ const decryptedPair = await decrypt(encryptedData, {
1361
+ epriv: password,
1362
+ epub: password
1363
+ });
1364
+ console.log(decryptedPair);
1365
+ return decryptedPair;
1366
+ } catch (error) {
1367
+ console.error("Error retrieving and decrypting pair:", error);
1368
+ return null;
1369
+ }
1370
+ }
1371
+
1372
+ /**
1373
+ * Crea una firma utilizzando il signer corrente
1374
+ * @param {string} message - Messaggio da firmare
1375
+ * @returns {Promise<string>} Firma generata
1376
+ */
1377
+ async function createSignature(message) {
1378
+ const signer = /** @type {ExtendedSigner} */await getSigner();
1379
+ return signer.signMessage(message);
1380
+ }
1381
+
1382
+ // =============================================
1383
+ // GUN EXTENSIONS
1384
+ // =============================================
1385
+
1386
+ /**
1387
+ * @param {{ chain: any; }} Gun
1388
+ */
1389
+ function extendGunWithStealth(Gun) {
1390
+ /** @type {StealthMethodsBase} */
1391
+ const stealthMethods = {
1392
+ stealth: async function (method, ...args) {
1393
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this),
1394
+ methods = {
1395
+ generate: stealth.generateStealthAddress.bind(stealth),
1396
+ announce: stealth.announceStealthPayment.bind(stealth),
1397
+ getPayments: stealth.getStealthPayments.bind(stealth),
1398
+ recover: stealth.recoverStealthFunds.bind(stealth),
1399
+ publish: stealth.publishStealthKeys.bind(stealth)
1400
+ };
1401
+ if (!(method in methods)) {
1402
+ throw new Error(`Unknown stealth method: ${method}`);
1403
+ }
1404
+ return methods[method](...args);
1405
+ },
1406
+ generateStealthAddress: async function (recipientAddress, signature, options = {}) {
1407
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1408
+ return stealth.generateStealthAddress(recipientAddress, signature, options);
1409
+ },
1410
+ announceStealthPayment: async function (stealthAddress, senderPublicKey, spendingPublicKey, signature, options = {}) {
1411
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1412
+ return stealth.announceStealthPayment(stealthAddress, senderPublicKey, spendingPublicKey, signature, options);
1413
+ },
1414
+ getStealthPayments: async function (signature, options = {}) {
1415
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1416
+ return stealth.getStealthPayments(signature, options);
1417
+ },
1418
+ recoverStealthFunds: async function (stealthAddress, senderPublicKey, signature, spendingPublicKey) {
1419
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1420
+ return stealth.recoverStealthFunds(stealthAddress, senderPublicKey, signature, spendingPublicKey);
1421
+ },
1422
+ publishStealthKeys: async function (signature) {
1423
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1424
+ return stealth.publishStealthKeys(signature);
1425
+ },
1426
+ monitorStealthEvents: function (callback) {
1427
+ new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1428
+ const gun = /** @type {GunExtended} */ /** @type {unknown} */this;
1429
+ const chain = gun.get('gun-eth').get('stealth-payments').map().on((payment, id) => {
1430
+ if (payment) {
1431
+ callback({
1432
+ type: 'offChain',
1433
+ event: 'announcement',
1434
+ data: {
1435
+ ...payment,
1436
+ id
1437
+ }
1438
+ });
1439
+ }
1440
+ });
1441
+ (async () => {
1442
+ try {
1443
+ const signer = /** @type {ExtendedSigner} */await getSigner(),
1444
+ chainConfig = getContractAddresses('localhost'),
1445
+ contract = new ethers.ethers.Contract(chainConfig.STEALTH_ANNOUNCER_ADDRESS, STEALTH_ANNOUNCER_ABI, signer);
1446
+ contract.on('PaymentAnnounced', function (sender, recipient, stealthAddress, event) {
1447
+ callback({
1448
+ type: 'onChain',
1449
+ event: 'PaymentAnnounced',
1450
+ data: {
1451
+ sender: sender,
1452
+ recipient: recipient,
1453
+ stealthAddress: stealthAddress,
1454
+ blockNumber: event.blockNumber,
1455
+ transactionHash: event.transactionHash
1456
+ }
1457
+ });
1458
+ });
1459
+ } catch (error) {
1460
+ console.warn('Failed to setup on-chain event monitoring:', error);
1461
+ }
1462
+ })();
1463
+ return chain;
1464
+ }
1465
+ };
1466
+ Object.assign(Gun.chain, stealthMethods);
1467
+ }
1468
+
1469
+ /**
1470
+ * @param {import("gun").IGun} Gun
1471
+ */
1472
+ function extendGun(Gun) {
1473
+ /** @type {BaseMethods} */
1474
+ const baseMethods = {
1475
+ MESSAGE_TO_SIGN,
1476
+ setSigner: function (/** @type {string} */newRpcUrl, /** @type {string} */newPrivateKey) {
1477
+ console.log("Signer configuration set");
1478
+ return this;
1479
+ },
1480
+ getSigner,
1481
+ verifySignature,
1482
+ generatePassword,
1483
+ createSignature,
1484
+ createAndStoreEncryptedPair,
1485
+ getAndDecryptPair,
1486
+ ethToGunAccount,
1487
+ gunToEthAccount,
1488
+ getAddressesForChain
1489
+ };
1490
+ Object.assign(Gun.chain, baseMethods);
1491
+
1492
+ // Extend with proof functionality
1493
+ ProofChain.extendGun(Gun);
1494
+
1495
+ // Extend with stealth functionality
1496
+ extendGunWithStealth(Gun);
1497
+
1498
+ // Add retry mechanism
1499
+ const methods = ['stealth', 'generateStealthAddress', 'announceStealthPayment', 'getStealthPayments', 'recoverStealthFunds', 'publishStealthKeys', 'createAndStoreEncryptedPair', 'getAndDecryptPair', 'ethToGunAccount', 'gunToEthAccount', 'createSignature', 'setSigner'];
1500
+ methods.forEach(method => {
1501
+ const original = Gun.chain[method];
1502
+ if (original) {
1503
+ Gun.chain[method] = async function (/** @type {any} */...args) {
1504
+ const maxRetries = 3;
1505
+ let lastError;
1506
+ for (let i = 0; i < maxRetries; i++) {
1507
+ try {
1508
+ return await original.apply(this, args);
1509
+ } catch (error) {
1510
+ console.warn(`Attempt ${i + 1}/${maxRetries} failed:`, error);
1511
+ lastError = error;
1512
+ await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
1513
+ }
1514
+ }
1515
+ throw lastError;
1516
+ };
1517
+ }
1518
+ });
1519
+ }
1520
+
1521
+ /**
1522
+ * Inizializza Gun con le estensioni e le opzioni specificate
1523
+ * @param {string} chain - Chain da utilizzare
1524
+ * @param {Object} [options] - Opzioni di configurazione per Gun
1525
+ * @returns {Promise<IGunInstance>} Istanza di Gun configurata
1526
+ */
1527
+ async function initializeGun(chain = 'localhost', options = {}) {
1528
+ await initialize(chain);
1529
+ extendGun(Gun);
1530
+ const gun = /** @type {IGunInstance} */Gun(options);
1531
+ return gun;
1532
+ }
1533
+
1534
+ /**
1535
+ * Classe principale per l'integrazione di Gun con Ethereum
1536
+ */
1537
+ class GunEth {
1538
+ static keypair = null;
1539
+ static v_keypair = null;
1540
+ static s_keypair = null;
1541
+ static async init(chain = 'localhost') {
1542
+ await initialize(chain);
1543
+ return this;
1544
+ }
1545
+
1546
+ // Metodi statici
1547
+ static generateRandomId = generateRandomId;
1548
+ static generatePassword = generatePassword;
1549
+ static getSigner = getSigner;
1550
+ static verifySignature = verifySignature;
1551
+ static MESSAGE_TO_SIGN = MESSAGE_TO_SIGN;
1552
+ static getContractAddresses = getContractAddresses;
1553
+ static extendGun = extendGun;
1554
+ static initializeGun = initializeGun;
1555
+ static setSigner = setSigner;
1556
+ static gunToEthAccount = gunToEthAccount;
1557
+ static decryptPair = decryptPair;
1558
+ static decryptPairFromPassword = decryptPairFromPassword;
1559
+ static ethToGunAccount = ethToGunAccount;
1560
+ static createAndStoreEncryptedPair = createAndStoreEncryptedPair;
1561
+ static getAndDecryptPair = getAndDecryptPair;
1562
+ static createSignature = createSignature;
1563
+ static LOCAL_CONFIG = LOCAL_CONFIG;
1564
+ static contractAddresses = contractAddresses;
1565
+ }
1566
+
1567
+ // @ts-nocheck
1568
+
1569
+
1570
+ /**
1571
+ * @typedef {Object} BubbleProviderOptions
1572
+ * @property {string} rpcUrl - RPC URL for the provider
1573
+ * @property {string} chain - Chain name (e.g. 'localhost', 'optimismSepolia')
1574
+ * @property {Object} gun - Gun instance
1575
+ * @property {Object} keypair - Keypair for encryption
1576
+ * @property {string} keypair.epub - Public encryption key
1577
+ * @property {string} keypair.epriv - Private encryption key
1578
+ * @property {string} [contractAddress] - Optional custom contract address
1579
+ * @property {Object} [contractAbi] - Optional custom contract ABI
1580
+ */
1581
+
1582
+ /**
1583
+ * @typedef {Object} BubbleDetails
1584
+ * @property {string} id - Bubble ID
1585
+ * @property {string} name - Bubble name
1586
+ * @property {string} owner - Owner's Ethereum address
1587
+ * @property {boolean} isPrivate - Whether the bubble is private
1588
+ * @property {number} createdAt - Creation timestamp
1589
+ */
1590
+
1591
+ /**
1592
+ * @typedef {Object} FileMetadata
1593
+ * @property {string} name - File name
1594
+ * @property {string} owner - File owner
1595
+ * @property {string} filePath - File path
1596
+ * @property {number} created - Creation timestamp
1597
+ * @property {number} updated - Last update timestamp
1598
+ * @property {number} size - File size in bytes
1599
+ * @property {Object} encryptionInfo - Encryption info
1600
+ * @property {string} encryptionInfo.ownerEpub - Owner's public key
1601
+ * @property {string} encryptionInfo.ownerAddress - Owner's address
1602
+ */
1603
+
1604
+ /**
1605
+ * @typedef {Object} DeleteResult
1606
+ * @property {boolean} success - Whether deletion was successful
1607
+ * @property {string} [message] - Optional status message
1608
+ */
1609
+
1610
+ /**
1611
+ * @typedef {Object} DeleteBubbleResult
1612
+ * @property {boolean} success - Whether deletion was successful
1613
+ * @property {string} [message] - Optional status message
1614
+ */
1615
+
1616
+ /**
1617
+ * Base class for bubble storage providers
1618
+ */
1619
+ class BaseBubbleProvider {
1620
+ /**
1621
+ * Creates a new bubble provider instance
1622
+ * @param {BubbleProviderOptions} options - Provider configuration options
1623
+ */
1624
+ constructor(options) {
1625
+ const {
1626
+ rpcUrl,
1627
+ chain,
1628
+ gun,
1629
+ keypair,
1630
+ contractAddress,
1631
+ contractAbi
1632
+ } = options;
1633
+ if (!rpcUrl) throw new Error("RPC URL required");
1634
+ if (!chain) throw new Error("Chain name required");
1635
+ if (!gun) throw new Error("Gun instance required");
1636
+ if (!keypair || !keypair.epub || !keypair.epriv) {
1637
+ throw new Error("Valid keypair required");
1638
+ }
1639
+ this.provider = new ethers.ethers.JsonRpcProvider(rpcUrl);
1640
+
1641
+ // Use custom contract address and ABI if provided, otherwise use defaults
1642
+ const addresses = getAddressesForChain(chain);
1643
+ const contractAddr = contractAddress || addresses?.BUBBLE_REGISTRY_ADDRESS;
1644
+ const abi = contractAbi || BUBBLE_REGISTRY_ABI;
1645
+ if (!contractAddr) {
1646
+ throw new Error(`No contract address found for chain: ${chain}`);
1647
+ }
1648
+
1649
+ /** @type {BubbleRegistryContract} */
1650
+ this.contract = new ethers.ethers.Contract(contractAddr, abi, this.provider);
1651
+
1652
+ // Initialize Gun and keypair in base class
1653
+ this.gun = gun;
1654
+ this.keypair = keypair;
1655
+ this.bubbleRoot = this.gun.get("bubbles");
1656
+
1657
+ // Key cache
1658
+ this.keyPairs = new Map();
1659
+ }
1660
+
1661
+ /**
1662
+ * Verifies request signature
1663
+ * @param {string} address - Ethereum address of the requester
1664
+ * @param {string} message - Message that was signed
1665
+ * @param {string} signature - Signature of the message
1666
+ * @returns {Promise<boolean>} - Whether the signature is valid
1667
+ */
1668
+ async verifyRequest(address, message, signature) {
1669
+ try {
1670
+ const recoveredAddress = ethers.ethers.verifyMessage(message, signature);
1671
+ return recoveredAddress.toLowerCase() === address.toLowerCase();
1672
+ } catch (error) {
1673
+ return false;
1674
+ }
1675
+ }
1676
+
1677
+ /**
1678
+ * Manages user keys
1679
+ * @param {string} address - Ethereum address of the user
1680
+ * @returns {Promise<Object>} - Key pair of the user
1681
+ */
1682
+ async getUserKeyPair(address) {
1683
+ const normalizedAddress = address.toLowerCase();
1684
+ let pair = this.keyPairs.get(normalizedAddress);
1685
+ if (!pair) {
1686
+ pair = await SEA.pair();
1687
+ this.keyPairs.set(normalizedAddress, pair);
1688
+ }
1689
+ return pair;
1690
+ }
1691
+
1692
+ /**
1693
+ * Verifies bubble access
1694
+ * @param {string} bubbleId - ID of the bubble
1695
+ * @param {string} userAddress - Ethereum address of the user
1696
+ * @returns {Promise<boolean>} - Whether the user has access to the bubble
1697
+ */
1698
+ async verifyBubbleAccess(bubbleId, userAddress) {
1699
+ console.log("\n=== Checking bubble access ===");
1700
+ console.log("Bubble ID:", bubbleId);
1701
+ console.log("User Address:", userAddress);
1702
+ const hasAccess = await this.contract.hasAccess(bubbleId, userAddress);
1703
+ console.log("Access check result:", hasAccess);
1704
+ if (!hasAccess) {
1705
+ console.error("Access denied");
1706
+ throw new Error("No access to bubble");
1707
+ }
1708
+ console.log("Access verified successfully");
1709
+ return true;
1710
+ }
1711
+
1712
+ /**
1713
+ * Verifies bubble ownership
1714
+ * @param {string} bubbleId - ID of the bubble
1715
+ * @param {string} ownerAddress - Ethereum address of the owner
1716
+ * @returns {Promise<boolean>} - Whether the user is the owner of the bubble
1717
+ */
1718
+ async verifyBubbleOwnership(bubbleId, ownerAddress) {
1719
+ console.log("\n=== Verifying bubble ownership ===");
1720
+ console.log("Bubble ID:", bubbleId);
1721
+ console.log("Owner address:", ownerAddress);
1722
+ console.log("Getting bubble data from contract...");
1723
+ const bubbleData = await this.contract.getBubbleDetails(bubbleId);
1724
+ const onChainOwner = bubbleData[1]; // owner is the second returned element
1725
+
1726
+ console.log("On-chain owner:", onChainOwner);
1727
+ console.log("Expected owner:", ownerAddress);
1728
+ if (onChainOwner.toLowerCase() !== ownerAddress.toLowerCase()) {
1729
+ throw new Error("Not bubble owner");
1730
+ }
1731
+ console.log("Ownership verified successfully");
1732
+ return true;
1733
+ }
1734
+
1735
+ /**
1736
+ * Grants on-chain access to a target address
1737
+ * @param {string} bubbleId - ID of the bubble
1738
+ * @param {string} targetAddress - Ethereum address to grant access to
1739
+ * @returns {Promise<void>}
1740
+ */
1741
+ async grantOnChainAccess(bubbleId, targetAddress) {
1742
+ console.log("\n=== Starting grantOnChainAccess ===");
1743
+ console.log("Getting signer...");
1744
+ const signer = await getSigner();
1745
+ console.log("Signer obtained");
1746
+ console.log("Connecting contract with signer...");
1747
+ const contractWithSigner = this.contract.connect(signer);
1748
+ console.log("Contract connected");
1749
+ console.log("Sending grantAccess transaction...");
1750
+ console.log("Bubble ID:", bubbleId);
1751
+ console.log("Target Address:", targetAddress);
1752
+ const tx = await contractWithSigner.grantAccess(bubbleId, targetAddress);
1753
+ console.log("Transaction sent:", tx.hash);
1754
+ console.log("Waiting for transaction confirmation...");
1755
+ await tx.wait();
1756
+ console.log("Transaction confirmed:", tx.hash);
1757
+ console.log("=== grantOnChainAccess completed successfully ===");
1758
+ }
1759
+
1760
+ /**
1761
+ * Puts data into GunDB
1762
+ * @param {string} path - Path in GunDB
1763
+ * @param {Object} data - Data to put
1764
+ * @param {number} [maxRetries=3] - Maximum number of retries
1765
+ * @returns {Promise<void>}
1766
+ */
1767
+ async putGunData(path, data, maxRetries = 3) {
1768
+ let retries = 0;
1769
+ while (retries < maxRetries) {
1770
+ try {
1771
+ await new Promise((resolve, reject) => {
1772
+ this.gun.get(path).put(data, ack => {
1773
+ if (ack.err) reject(new Error(ack.err));else resolve();
1774
+ });
1775
+ });
1776
+ return;
1777
+ } catch (error) {
1778
+ retries++;
1779
+ if (retries === maxRetries) throw error;
1780
+ await new Promise(resolve => setTimeout(resolve, 1000));
1781
+ }
1782
+ }
1783
+ }
1784
+
1785
+ /**
1786
+ * Gets a node from GunDB
1787
+ * @typedef {Object} GunNode
1788
+ * @property {Object} data - Data of the node (if any)
1789
+ * @property {string} err - Error message if any
1790
+ * @property {any} content - Content of the node
1791
+ * @param {string} path - Path in GunDB
1792
+ * @param {number} [maxRetries=3] - Maximum number of retries
1793
+ * @returns {Promise<GunNode>} - Node data
1794
+ */
1795
+ async getGunNode(path, maxRetries = 3) {
1796
+ let retries = 0;
1797
+ let node = null;
1798
+ while (retries < maxRetries) {
1799
+ try {
1800
+ node = await new Promise((resolve, reject) => {
1801
+ const timeoutId = setTimeout(() => {
1802
+ reject(new Error("Gun node fetch timeout"));
1803
+ }, 5000);
1804
+ this.gun.get(path).once(data => {
1805
+ clearTimeout(timeoutId);
1806
+ resolve(data);
1807
+ });
1808
+ });
1809
+ if (node) break;
1810
+ retries++;
1811
+ await new Promise(resolve => setTimeout(resolve, 1000));
1812
+ } catch (error) {
1813
+ console.error(`Gun node fetch attempt ${retries + 1} failed:`, error);
1814
+ retries++;
1815
+ if (retries === maxRetries) throw error;
1816
+ await new Promise(resolve => setTimeout(resolve, 1000));
1817
+ }
1818
+ }
1819
+ return node;
1820
+ }
1821
+
1822
+ /**
1823
+ * Handles bubble creation
1824
+ * @param {Object} params - Parameters for bubble creation
1825
+ * @param {string} params.name - Name of the bubble
1826
+ * @param {boolean} params.isPrivate - Whether the bubble is private
1827
+ * @param {string} params.userAddress - Ethereum address of the user
1828
+ * @returns {Promise<BubbleDetails>} - Metadata of the created bubble
1829
+ */
1830
+ async handleCreateBubble(params) {
1831
+ const {
1832
+ name,
1833
+ isPrivate,
1834
+ userAddress
1835
+ } = params;
1836
+ if (!name || typeof isPrivate !== "boolean" || !userAddress) {
1837
+ throw new Error("Invalid parameters for bubble creation");
1838
+ }
1839
+ try {
1840
+ console.log("Starting bubble creation...");
1841
+ console.log("Parameters:", params);
1842
+ const signer = await getSigner();
1843
+ const contractWithSigner = this.contract.connect(signer);
1844
+ const tx = await contractWithSigner.createBubble(name, isPrivate);
1845
+ const receipt = await tx.wait();
1846
+ const event = receipt.logs.find(log => {
1847
+ try {
1848
+ const parsedLog = this.contract.interface.parseLog(log);
1849
+ return parsedLog.name === "BubbleCreated";
1850
+ } catch (e) {
1851
+ return false;
1852
+ }
1853
+ });
1854
+ if (!event) {
1855
+ throw new Error("Bubble creation event not found");
1856
+ }
1857
+ const parsedEvent = this.contract.interface.parseLog(event);
1858
+ const bubbleId = parsedEvent.args[0];
1859
+ const metadata = {
1860
+ id: bubbleId,
1861
+ name,
1862
+ owner: userAddress,
1863
+ isPrivate,
1864
+ createdAt: Date.now()
1865
+ };
1866
+ await this.putGunData(`bubbles/${bubbleId}`, metadata);
1867
+ return metadata;
1868
+ } catch (error) {
1869
+ console.error("Error creating bubble:", error);
1870
+ throw error;
1871
+ }
1872
+ }
1873
+
1874
+ /**
1875
+ * Handles granting permission to a bubble
1876
+ * @param {string} bubbleId - ID of the bubble
1877
+ * @param {string} targetAddress - Ethereum address of the target user
1878
+ * @param {string} granterAddress - Ethereum address of the granter
1879
+ * @param {Object} options - Additional options
1880
+ * @param {string} options.granteeEpub - Public encryption key of the grantee
1881
+ * @returns {Promise<Object>} - Result of the permission grant
1882
+ */
1883
+ async handleGrantPermission(bubbleId, targetAddress, granterAddress, options) {
1884
+ try {
1885
+ if (!options?.granteeEpub) {
1886
+ throw new Error("Grantee epub key is required");
1887
+ }
1888
+ await this.verifyBubbleOwnership(bubbleId, granterAddress);
1889
+ await this.grantOnChainAccess(bubbleId, targetAddress);
1890
+ return {
1891
+ success: true
1892
+ };
1893
+ } catch (error) {
1894
+ console.error("Error in handleGrantPermission:", error);
1895
+ throw error;
1896
+ }
1897
+ }
1898
+
1899
+ /**
1900
+ * Handles file upload to a bubble
1901
+ * @abstract
1902
+ * @param {string} bubbleId - ID of the bubble
1903
+ * @param {string} fileName - Name of the file
1904
+ * @param {string} content - File content
1905
+ * @param {string} userAddress - Ethereum address of the uploader
1906
+ * @returns {Promise<FileMetadata>} - Metadata of the uploaded file
1907
+ */
1908
+ async handleFileUpload(bubbleId, fileName, content, userAddress) {
1909
+ throw new Error('handleFileUpload must be implemented');
1910
+ }
1911
+
1912
+ /**
1913
+ * Handles file deletion from a bubble
1914
+ * @abstract
1915
+ * @param {string} bubbleId - ID of the bubble
1916
+ * @param {string} fileName - Name of the file
1917
+ * @param {string} userAddress - Ethereum address of the user
1918
+ * @returns {Promise<DeleteResult>} - Result of the deletion
1919
+ */
1920
+ async handleDeleteFile(bubbleId, fileName, userAddress) {
1921
+ throw new Error('handleDeleteFile must be implemented');
1922
+ }
1923
+
1924
+ /**
1925
+ * Handles file download from a bubble
1926
+ * @abstract
1927
+ * @param {string} bubbleId - ID of the bubble
1928
+ * @param {string} fileName - Name of the file
1929
+ * @param {string} userAddress - Ethereum address of the user
1930
+ * @returns {Promise<{content: any, metadata: Object}>} - File content and metadata
1931
+ */
1932
+ async handleFileDownload(bubbleId, fileName, userAddress) {
1933
+ throw new Error('handleFileDownload must be implemented');
1934
+ }
1935
+
1936
+ /**
1937
+ * Handles bubble deletion
1938
+ * @abstract
1939
+ * @param {string} bubbleId - ID of the bubble
1940
+ * @param {string} userAddress - Ethereum address of the user
1941
+ * @returns {Promise<DeleteBubbleResult>} - Result of the deletion
1942
+ */
1943
+ async handleDeleteBubble(bubbleId, userAddress) {
1944
+ throw new Error('handleDeleteBubble must be implemented');
1945
+ }
1946
+ }
1947
+
1948
+ /**
1949
+ * @typedef {Object} FileMetadata
1950
+ * @property {string} name - Name of the file
1951
+ * @property {string} owner - Address of file owner
1952
+ * @property {number} created - Creation timestamp
1953
+ * @property {number} updated - Last update timestamp
1954
+ * @property {string} filePath - File path
1955
+ * @property {number} size - File size
1956
+ * @property {Object} encryptionInfo - Encryption info
1957
+ * @property {string} encryptionInfo.ownerEpub - Owner's public key
1958
+ * @property {string} encryptionInfo.ownerAddress - Owner's address
1959
+ * @property {boolean} readOnly - Whether file is read-only
1960
+ */
1961
+
1962
+ class GUNBubbleProvider extends BaseBubbleProvider {
1963
+ constructor(options) {
1964
+ super(options);
1965
+
1966
+ // Configura il Gun user
1967
+ this.user = this.gun.user();
1968
+ this.user.auth(this.keypair);
1969
+
1970
+ // Debug log
1971
+ console.log("GUNBubbleProvider initialized:", {
1972
+ hasGun: !!this.gun,
1973
+ hasUser: !!this.user,
1974
+ hasKeypair: !!this.keypair,
1975
+ hasBubbleRoot: !!this.bubbleRoot,
1976
+ gunOpts: this.gun._.opt
1977
+ });
1978
+
1979
+ // Verifica che Gun sia pronto
1980
+ this.gun.on('hi', peer => {
1981
+ console.log('Connected to peer:', peer);
1982
+ });
1983
+ this.gun.on('error', error => {
1984
+ console.error('Gun error:', error);
1985
+ });
1986
+ }
1987
+
1988
+ /**
1989
+ * @typedef {Object} FileDownloadParams
1990
+ * @property {string} bubbleId - ID of the bubble
1991
+ * @property {string} fileName - Name of the file to download
1992
+ * @property {string} userAddress - Ethereum address of the user
1993
+ */
1994
+
1995
+ /**
1996
+ * Handles file download
1997
+ * @param {string} bubbleId - ID of the bubble
1998
+ * @param {string} fileName - Name of the file to download
1999
+ * @param {string} userAddress - Ethereum address of the user
2000
+ */
2001
+ async handleFileDownload(bubbleId, fileName, userAddress) {
2002
+ try {
2003
+ if (!this.keypair) throw new Error('Keypair not initialized');
2004
+ console.log("\n=== Starting file download ===");
2005
+ console.log("Bubble ID:", bubbleId);
2006
+ console.log("File name:", fileName);
2007
+ console.log("User address:", userAddress);
2008
+ await this.verifyBubbleAccess(bubbleId, userAddress);
2009
+ const filePath = `bubbles/${bubbleId}/files/${fileName}`;
2010
+ console.log("Reading from path:", filePath);
2011
+ const fileData = await new Promise(resolve => {
2012
+ this.gun.get(filePath).once(data => {
2013
+ console.log("File data retrieved:", data);
2014
+ resolve(data);
2015
+ });
2016
+ });
2017
+ if (!fileData) {
2018
+ console.error("File not found at path:", filePath);
2019
+ throw new Error('File not found');
2020
+ }
2021
+ let content;
2022
+ const normalizedUserAddress = userAddress.toLowerCase();
2023
+ if (fileData.owner.toLowerCase() === normalizedUserAddress) {
2024
+ console.log("User is owner, decrypting with owner key");
2025
+ content = await decrypt(fileData.content, this.keypair);
2026
+ } else {
2027
+ console.log("User is not owner, looking for shared content");
2028
+ const sharedPath = `${filePath}/sharedWith/${normalizedUserAddress}`;
2029
+ console.log("Looking for shared data at:", sharedPath);
2030
+ const sharedData = await new Promise(resolve => {
2031
+ this.gun.get(sharedPath).once(data => {
2032
+ console.log("Shared data loaded:", data);
2033
+ resolve(data);
2034
+ });
2035
+ });
2036
+ if (!sharedData || !sharedData.content || !sharedData.ownerEpub) {
2037
+ console.error("Shared data not found or incomplete:", sharedData);
2038
+ throw new Error('Shared content not found');
2039
+ }
2040
+ try {
2041
+ // Use deriveSharedKey to get the shared key
2042
+ const sharedKeypair = await deriveSharedKey(sharedData.ownerEpub, this.keypair);
2043
+ console.log("Shared key derived");
2044
+
2045
+ // Decrypt the content using the shared key
2046
+ content = await decrypt(sharedData.content, sharedKeypair);
2047
+ if (!content) {
2048
+ throw new Error('Failed to decrypt content');
2049
+ }
2050
+ console.log("Content decrypted successfully");
2051
+ } catch (error) {
2052
+ console.error("Decryption error:", {
2053
+ phase: error.message.includes('key') ? 'key derivation' : 'content decryption',
2054
+ error: error.message,
2055
+ sharedData: {
2056
+ hasContent: !!sharedData.content,
2057
+ hasOwnerEpub: !!sharedData.ownerEpub,
2058
+ contentLength: sharedData.content?.length
2059
+ }
2060
+ });
2061
+ throw error;
2062
+ }
2063
+ }
2064
+ return {
2065
+ content,
2066
+ metadata: {
2067
+ name: fileData.name,
2068
+ owner: fileData.owner,
2069
+ filePath: fileData.filePath,
2070
+ created: fileData.created,
2071
+ updated: fileData.updated,
2072
+ size: fileData.size,
2073
+ readOnly: fileData.readOnly || false,
2074
+ encryptionInfo: fileData.encryptionInfo
2075
+ }
2076
+ };
2077
+ } catch (error) {
2078
+ console.error('Error downloading file:', error);
2079
+ throw error;
2080
+ }
2081
+ }
2082
+
2083
+ /**
2084
+ * @typedef {Object} FileUploadOptions
2085
+ * @property {string} bubbleId - ID of the bubble
2086
+ * @property {string} fileName - Name of the file
2087
+ * @property {string} content - Content of the file
2088
+ * @property {string} userAddress - Address of the user
2089
+ */
2090
+ async handleFileUpload(bubbleId, fileName, content, userAddress) {
2091
+ try {
2092
+ console.log("\n=== Starting file upload ===");
2093
+ console.log("Bubble ID:", bubbleId);
2094
+ console.log("File name:", fileName);
2095
+ console.log("User address:", userAddress);
2096
+ if (!this.keypair) {
2097
+ throw new Error('Keypair not initialized');
2098
+ }
2099
+ await this.verifyBubbleAccess(bubbleId, userAddress);
2100
+
2101
+ // Encrypt the content
2102
+ console.log("Encrypting content...");
2103
+ const encryptedContent = await encrypt(content, this.keypair);
2104
+ console.log("Content encrypted successfully");
2105
+
2106
+ // Create the file metadata
2107
+ const fileMetadata = {
2108
+ name: fileName,
2109
+ owner: userAddress,
2110
+ content: encryptedContent,
2111
+ created: Date.now(),
2112
+ updated: Date.now(),
2113
+ readOnly: false,
2114
+ sharedWith: {}
2115
+ };
2116
+ console.log("Saving to GUN...");
2117
+
2118
+ // Use a more direct approach with GUN
2119
+ return new Promise((resolve, reject) => {
2120
+ // Create the full path for the file
2121
+ const filePath = `bubbles/${bubbleId}/files/${fileName}`;
2122
+ console.log("File path:", filePath);
2123
+
2124
+ // Save directly to the path as an object
2125
+ this.gun.get(filePath).put(fileMetadata, ack => {
2126
+ if (ack.err) {
2127
+ console.error("Save error:", ack.err);
2128
+ reject(new Error(ack.err));
2129
+ return;
2130
+ }
2131
+ console.log("Initial save successful, verifying...");
2132
+
2133
+ // Verify the save
2134
+ this.gun.get(filePath).once(data => {
2135
+ if (!data || !data.content) {
2136
+ console.error("Verification failed:", data);
2137
+ reject(new Error('File verification failed'));
2138
+ return;
2139
+ }
2140
+ console.log("File saved and verified successfully:", {
2141
+ name: data.name,
2142
+ owner: data.owner,
2143
+ hasContent: !!data.content,
2144
+ created: new Date(data.created).toISOString()
2145
+ });
2146
+ resolve(fileMetadata);
2147
+ });
2148
+ });
2149
+
2150
+ // Set a timeout for the verification
2151
+ setTimeout(() => {
2152
+ this.gun.get(filePath).once(data => {
2153
+ if (data && data.content) {
2154
+ console.log("Save operation taking too long, checking state...");
2155
+ console.log("Data found after timeout, resolving...");
2156
+ resolve(fileMetadata);
2157
+ }
2158
+ });
2159
+ }, 5000);
2160
+ });
2161
+ } catch (error) {
2162
+ console.error('Error uploading file:', error);
2163
+ throw error;
2164
+ }
2165
+ }
2166
+
2167
+ /**
2168
+ * @typedef {Object} GrantPermissionOptions
2169
+ * @property {string} granteeEpub - Public encryption key of grantee
2170
+ */
2171
+ async handleGrantPermission(bubbleId, targetAddress, granterAddress, options = {}) {
2172
+ try {
2173
+ console.log("\n=== Starting handleGrantPermission ===");
2174
+ if (!options.granteeEpub) {
2175
+ throw new Error("Grantee epub key is required");
2176
+ }
2177
+ await this.verifyBubbleOwnership(bubbleId, granterAddress);
2178
+ await this.grantOnChainAccess(bubbleId, targetAddress);
2179
+ const filesPath = `bubbles/${bubbleId}/files`;
2180
+ console.log("Files path:", filesPath);
2181
+ const fileData = await this.getGunNode(`${filesPath}/secret.txt`);
2182
+ console.log("File data:", fileData);
2183
+ if (!fileData || !fileData.content) {
2184
+ console.log("No file content found");
2185
+ return {
2186
+ success: true
2187
+ };
2188
+ }
2189
+ try {
2190
+ // Decrypt the original content
2191
+ const decryptedContent = await decrypt(fileData.content, this.keypair);
2192
+ console.log("Original content decrypted");
2193
+
2194
+ // Use deriveSharedKey to get the shared key
2195
+ const sharedKeypair = await deriveSharedKey(options.granteeEpub, this.keypair);
2196
+ console.log("Shared key derived successfully");
2197
+
2198
+ // Encrypt the content with the shared key
2199
+ const encryptedContent = await encrypt(decryptedContent, sharedKeypair);
2200
+ console.log("Content re-encrypted with shared key");
2201
+ const sharedPath = `${filesPath}/secret.txt/sharedWith/${targetAddress.toLowerCase()}`;
2202
+ console.log("Saving shared data at:", sharedPath);
2203
+ const sharedData = {
2204
+ address: targetAddress,
2205
+ grantedAt: Date.now(),
2206
+ content: encryptedContent,
2207
+ ownerEpub: this.keypair.epub
2208
+ };
2209
+ await this.putGunData(sharedPath, sharedData);
2210
+ console.log("Shared data saved successfully");
2211
+ return {
2212
+ success: true
2213
+ };
2214
+ } catch (error) {
2215
+ console.error("Error processing file:", error);
2216
+ throw error;
2217
+ }
2218
+ } catch (error) {
2219
+ console.error("Error in handleGrantPermission:", error);
2220
+ throw error;
2221
+ }
2222
+ }
2223
+
2224
+ /**
2225
+ * @typedef {Object} BubbleParams
2226
+ * @property {string} name - The name of the bubble
2227
+ * @property {boolean} isPrivate - Whether the bubble is private
2228
+ * @property {string} userAddress - The address of the user creating the bubble
2229
+ */
2230
+
2231
+ /**
2232
+ * Handles the creation of a new bubble.
2233
+ * @param {BubbleParams} params - The parameters for bubble creation
2234
+ */
2235
+ async handleCreateBubble(params) {
2236
+ const {
2237
+ name,
2238
+ isPrivate,
2239
+ userAddress
2240
+ } = params;
2241
+ if (!name || typeof isPrivate !== 'boolean' || !userAddress) {
2242
+ throw new Error('Invalid parameters for bubble creation');
2243
+ }
2244
+ try {
2245
+ console.log("Starting bubble creation...");
2246
+ console.log("Parameters:", params);
2247
+
2248
+ // Get the signer
2249
+ console.log("Getting signer...");
2250
+ const signer = await getSigner();
2251
+
2252
+ // Create the bubble on-chain
2253
+ console.log("Creating bubble on-chain...");
2254
+ const contractWithSigner = this.contract.connect(signer);
2255
+ const tx = await contractWithSigner.createBubble(name, isPrivate);
2256
+ console.log("Transaction sent:", tx.hash);
2257
+ console.log("Waiting for transaction...");
2258
+ const receipt = await tx.wait();
2259
+
2260
+ // Extract the bubble ID from the event
2261
+ console.log("Parsing event...");
2262
+ const event = receipt.logs.find(log => {
2263
+ try {
2264
+ const parsedLog = this.contract.interface.parseLog(log);
2265
+ return parsedLog.name === 'BubbleCreated';
2266
+ } catch (e) {
2267
+ return false;
2268
+ }
2269
+ });
2270
+ if (!event) {
2271
+ throw new Error('Bubble creation event not found');
2272
+ }
2273
+ const parsedEvent = this.contract.interface.parseLog(event);
2274
+ const bubbleId = parsedEvent.args[0];
2275
+ console.log("Bubble ID:", bubbleId);
2276
+
2277
+ // Create the metadata
2278
+ const metadata = {
2279
+ id: bubbleId,
2280
+ name,
2281
+ owner: userAddress,
2282
+ isPrivate,
2283
+ createdAt: Date.now()
2284
+ };
2285
+
2286
+ // Save the metadata to GUN
2287
+ console.log("Saving metadata to GUN...");
2288
+ await new Promise((resolve, reject) => {
2289
+ this.gun.get('bubbles').get(bubbleId).put(metadata, ack => {
2290
+ if (ack.err) {
2291
+ console.error("GUN save error:", ack.err);
2292
+ reject(new Error(ack.err));
2293
+ } else {
2294
+ console.log("GUN save successful");
2295
+ resolve();
2296
+ }
2297
+ });
2298
+ });
2299
+ console.log("Metadata saved successfully:", metadata);
2300
+ return metadata;
2301
+ } catch (error) {
2302
+ console.error('Error creating bubble:', error);
2303
+ throw error;
2304
+ }
2305
+ }
2306
+
2307
+ /**
2308
+ * @typedef {Object} DeleteFileOptions
2309
+ * @property {string} bubbleId - The ID of the bubble
2310
+ * @property {string} fileName - The name of the file to delete
2311
+ * @property {string} userAddress - The address of the user requesting the deletion
2312
+ */
2313
+ async handleDeleteFile(bubbleId, fileName, userAddress) {
2314
+ try {
2315
+ console.log("\n=== Starting file deletion ===");
2316
+ console.log("Bubble ID:", bubbleId);
2317
+ console.log("File name:", fileName);
2318
+ console.log("User address:", userAddress);
2319
+
2320
+ // Verify access
2321
+ await this.verifyBubbleAccess(bubbleId, userAddress);
2322
+
2323
+ // Build the file path
2324
+ const filePath = `bubbles/${bubbleId}/files/${fileName}`;
2325
+ console.log("Deleting file at path:", filePath);
2326
+
2327
+ // Load the file data to verify ownership
2328
+ const fileData = await new Promise(resolve => {
2329
+ this.gun.get(filePath).once(resolve);
2330
+ });
2331
+ if (!fileData) {
2332
+ throw new Error('File not found');
2333
+ }
2334
+
2335
+ // Verify that the user is the owner
2336
+ if (fileData.owner.toLowerCase() !== userAddress.toLowerCase()) {
2337
+ throw new Error('Only file owner can delete files');
2338
+ }
2339
+
2340
+ // Delete the file and its metadata
2341
+ return new Promise((resolve, reject) => {
2342
+ // First delete the shared data
2343
+ const sharedWithNode = this.gun.get(filePath).get('sharedWith');
2344
+ sharedWithNode.map().once((data, key) => {
2345
+ if (key !== '_') {
2346
+ sharedWithNode.get(key).put(null);
2347
+ }
2348
+ });
2349
+
2350
+ // Then delete the file itself by setting all fields to null
2351
+ const fileNode = this.gun.get(filePath);
2352
+ const nullData = {
2353
+ name: null,
2354
+ owner: null,
2355
+ content: null,
2356
+ created: null,
2357
+ updated: null,
2358
+ readOnly: null,
2359
+ sharedWith: null
2360
+ };
2361
+ fileNode.put(nullData, ack => {
2362
+ if (ack.err) {
2363
+ console.error("Error nullifying file data:", ack.err);
2364
+ reject(new Error(ack.err));
2365
+ return;
2366
+ }
2367
+
2368
+ // Remove the file reference from the bubble's file list
2369
+ this.gun.get(`bubbles/${bubbleId}/files`).get(fileName).put(null, ack => {
2370
+ if (ack.err) {
2371
+ console.error("Error removing file reference:", ack.err);
2372
+ reject(new Error(ack.err));
2373
+ return;
2374
+ }
2375
+ console.log("File deleted successfully");
2376
+ resolve({
2377
+ success: true
2378
+ });
2379
+ });
2380
+ });
2381
+ });
2382
+ } catch (error) {
2383
+ console.error('Error deleting file:', error);
2384
+ throw error;
2385
+ }
2386
+ }
2387
+
2388
+ /**
2389
+ * @typedef {Object} DeleteBubbleResult
2390
+ * @property {boolean} success - Indicates if the bubble was successfully deleted
2391
+ */
2392
+
2393
+ /**
2394
+ * Deletes a bubble and all its contents.
2395
+ * @param {string} bubbleId - The ID of the bubble to delete.
2396
+ * @param {string} userAddress - The address of the user requesting the deletion.
2397
+ * @returns {Promise<DeleteBubbleResult>} - The result of the deletion operation.
2398
+ */
2399
+ async handleDeleteBubble(bubbleId, userAddress) {
2400
+ try {
2401
+ console.log("\n=== Starting bubble deletion ===");
2402
+ console.log("Bubble ID:", bubbleId);
2403
+ console.log("User address:", userAddress);
2404
+
2405
+ // Verify bubble ownership
2406
+ await this.verifyBubbleOwnership(bubbleId, userAddress);
2407
+
2408
+ // Build the bubble path
2409
+ const bubblePath = `bubbles/${bubbleId}`;
2410
+ console.log("Deleting bubble at path:", bubblePath);
2411
+
2412
+ // Delete the bubble and all its contents
2413
+ return new Promise((resolve, reject) => {
2414
+ // First delete all files in the bubble
2415
+ this.gun.get(bubblePath).get('files').map().once((fileData, fileName) => {
2416
+ if (fileName === '_') return;
2417
+
2418
+ // Delete shared data for each file
2419
+ this.gun.get(bubblePath).get('files').get(fileName).get('sharedWith').map().once((sharedData, sharedAddress) => {
2420
+ if (sharedAddress === '_') return;
2421
+
2422
+ // Set shared data to null
2423
+ this.gun.get(bubblePath).get('files').get(fileName).get('sharedWith').get(sharedAddress).put(null);
2424
+ });
2425
+
2426
+ // Set file metadata to null
2427
+ this.gun.get(bubblePath).get('files').get(fileName).put({
2428
+ name: null,
2429
+ owner: null,
2430
+ content: null,
2431
+ created: null,
2432
+ updated: null,
2433
+ readOnly: null,
2434
+ sharedWith: null
2435
+ });
2436
+ });
2437
+
2438
+ // Then set bubble metadata to null
2439
+ this.gun.get(bubblePath).put({
2440
+ id: null,
2441
+ name: null,
2442
+ owner: null,
2443
+ isPrivate: null,
2444
+ createdAt: null,
2445
+ files: null
2446
+ }, ack => {
2447
+ if (ack.err) {
2448
+ console.error("Error nullifying bubble data:", ack.err);
2449
+ reject(new Error(ack.err));
2450
+ return;
2451
+ }
2452
+ console.log("Bubble data nullified successfully");
2453
+ resolve({
2454
+ success: true
2455
+ });
2456
+ });
2457
+ });
2458
+ } catch (error) {
2459
+ console.error('Error deleting bubble:', error);
2460
+ throw error;
2461
+ }
2462
+ }
2463
+ }
2464
+
2465
+ exports.GUNBubbleProvider = GUNBubbleProvider;
2466
+ exports.GunEth = GunEth;
2467
+ exports.ProofChain = ProofChain;
2468
+ exports.StealthChain = StealthChain;