gun-eth 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +386 -18
  2. package/dist/gun-eth.bundle.js +6806 -0
  3. package/dist/gun-eth.cjs +2465 -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,2465 @@
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
+ const signature = await signer.signMessage(MESSAGE_TO_SIGN);
1294
+ const password = generatePassword(signature);
1295
+ const pair = await SEA$1.pair();
1296
+ const v_pair = await SEA$1.pair();
1297
+ const s_pair = await SEA$1.pair();
1298
+ const encryptedPair = await encrypt(pair, password);
1299
+ const encryptedV_pair = await encrypt(v_pair, password);
1300
+ const encryptedS_pair = await encrypt(s_pair, password);
1301
+ const viewingAccount = await gunToEthAccount(v_pair.priv);
1302
+ const spendingAccount = await gunToEthAccount(s_pair.priv);
1303
+ return {
1304
+ pair: pair,
1305
+ v_pair: v_pair,
1306
+ s_pair: s_pair,
1307
+ ethAddress: signer.address,
1308
+ ethPrivateKey: signer.privateKey,
1309
+ env_pair: encryptedPair,
1310
+ env_v_pair: encryptedV_pair,
1311
+ env_s_pair: encryptedS_pair,
1312
+ publicKeys: {
1313
+ viewingPublicKey: v_pair.epub,
1314
+ spendingPublicKey: spendingAccount.publicKey,
1315
+ ethViewingAddress: viewingAccount.publicKey
1316
+ }
1317
+ };
1318
+ }
1319
+
1320
+ /**
1321
+ * @param {any} address
1322
+ */
1323
+ async function createAndStoreEncryptedPair(address) {
1324
+ const gun = this;
1325
+ try {
1326
+ const {
1327
+ pair,
1328
+ v_pair,
1329
+ s_pair,
1330
+ publicKeys
1331
+ } = await ethToGunAccount();
1332
+ gun.get("gun-eth").get("users").get(address).put({
1333
+ pair,
1334
+ v_pair,
1335
+ s_pair,
1336
+ publicKeys
1337
+ });
1338
+ console.log("Encrypted pairs and public keys stored for:", address);
1339
+ } catch (error) {
1340
+ console.error("Error creating and storing encrypted pair:", error);
1341
+ throw error;
1342
+ }
1343
+ }
1344
+
1345
+ /**
1346
+ * @param {any} address
1347
+ * @param {any} signature
1348
+ */
1349
+ async function getAndDecryptPair(address, signature) {
1350
+ try {
1351
+ const gun = this;
1352
+ const encryptedData = await gun.get("gun-eth").get("users").get(address).get("pair").then();
1353
+ if (!encryptedData) {
1354
+ throw new Error("No encrypted data found for this address");
1355
+ }
1356
+ const password = generatePassword(signature);
1357
+ const decryptedPair = await decrypt(encryptedData, {
1358
+ epriv: password,
1359
+ epub: password
1360
+ });
1361
+ console.log(decryptedPair);
1362
+ return decryptedPair;
1363
+ } catch (error) {
1364
+ console.error("Error retrieving and decrypting pair:", error);
1365
+ return null;
1366
+ }
1367
+ }
1368
+
1369
+ /**
1370
+ * Crea una firma utilizzando il signer corrente
1371
+ * @param {string} message - Messaggio da firmare
1372
+ * @returns {Promise<string>} Firma generata
1373
+ */
1374
+ async function createSignature(message) {
1375
+ const signer = /** @type {ExtendedSigner} */await getSigner();
1376
+ return signer.signMessage(message);
1377
+ }
1378
+
1379
+ // =============================================
1380
+ // GUN EXTENSIONS
1381
+ // =============================================
1382
+
1383
+ /**
1384
+ * @param {{ chain: any; }} Gun
1385
+ */
1386
+ function extendGunWithStealth(Gun) {
1387
+ /** @type {StealthMethodsBase} */
1388
+ const stealthMethods = {
1389
+ stealth: async function (method, ...args) {
1390
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this),
1391
+ methods = {
1392
+ generate: stealth.generateStealthAddress.bind(stealth),
1393
+ announce: stealth.announceStealthPayment.bind(stealth),
1394
+ getPayments: stealth.getStealthPayments.bind(stealth),
1395
+ recover: stealth.recoverStealthFunds.bind(stealth),
1396
+ publish: stealth.publishStealthKeys.bind(stealth)
1397
+ };
1398
+ if (!(method in methods)) {
1399
+ throw new Error(`Unknown stealth method: ${method}`);
1400
+ }
1401
+ return methods[method](...args);
1402
+ },
1403
+ generateStealthAddress: async function (recipientAddress, signature, options = {}) {
1404
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1405
+ return stealth.generateStealthAddress(recipientAddress, signature, options);
1406
+ },
1407
+ announceStealthPayment: async function (stealthAddress, senderPublicKey, spendingPublicKey, signature, options = {}) {
1408
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1409
+ return stealth.announceStealthPayment(stealthAddress, senderPublicKey, spendingPublicKey, signature, options);
1410
+ },
1411
+ getStealthPayments: async function (signature, options = {}) {
1412
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1413
+ return stealth.getStealthPayments(signature, options);
1414
+ },
1415
+ recoverStealthFunds: async function (stealthAddress, senderPublicKey, signature, spendingPublicKey) {
1416
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1417
+ return stealth.recoverStealthFunds(stealthAddress, senderPublicKey, signature, spendingPublicKey);
1418
+ },
1419
+ publishStealthKeys: async function (signature) {
1420
+ const stealth = new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1421
+ return stealth.publishStealthKeys(signature);
1422
+ },
1423
+ monitorStealthEvents: function (callback) {
1424
+ new StealthChain(/** @type {GunExtended} */ /** @type {unknown} */this);
1425
+ const gun = /** @type {GunExtended} */ /** @type {unknown} */this;
1426
+ const chain = gun.get('gun-eth').get('stealth-payments').map().on((payment, id) => {
1427
+ if (payment) {
1428
+ callback({
1429
+ type: 'offChain',
1430
+ event: 'announcement',
1431
+ data: {
1432
+ ...payment,
1433
+ id
1434
+ }
1435
+ });
1436
+ }
1437
+ });
1438
+ (async () => {
1439
+ try {
1440
+ const signer = /** @type {ExtendedSigner} */await getSigner(),
1441
+ chainConfig = getContractAddresses('localhost'),
1442
+ contract = new ethers.ethers.Contract(chainConfig.STEALTH_ANNOUNCER_ADDRESS, STEALTH_ANNOUNCER_ABI, signer);
1443
+ contract.on('PaymentAnnounced', function (sender, recipient, stealthAddress, event) {
1444
+ callback({
1445
+ type: 'onChain',
1446
+ event: 'PaymentAnnounced',
1447
+ data: {
1448
+ sender: sender,
1449
+ recipient: recipient,
1450
+ stealthAddress: stealthAddress,
1451
+ blockNumber: event.blockNumber,
1452
+ transactionHash: event.transactionHash
1453
+ }
1454
+ });
1455
+ });
1456
+ } catch (error) {
1457
+ console.warn('Failed to setup on-chain event monitoring:', error);
1458
+ }
1459
+ })();
1460
+ return chain;
1461
+ }
1462
+ };
1463
+ Object.assign(Gun.chain, stealthMethods);
1464
+ }
1465
+
1466
+ /**
1467
+ * @param {import("gun").IGun} Gun
1468
+ */
1469
+ function extendGun(Gun) {
1470
+ /** @type {BaseMethods} */
1471
+ const baseMethods = {
1472
+ MESSAGE_TO_SIGN,
1473
+ setSigner: function (/** @type {string} */newRpcUrl, /** @type {string} */newPrivateKey) {
1474
+ console.log("Signer configuration set");
1475
+ return this;
1476
+ },
1477
+ getSigner,
1478
+ verifySignature,
1479
+ generatePassword,
1480
+ createSignature,
1481
+ createAndStoreEncryptedPair,
1482
+ getAndDecryptPair,
1483
+ ethToGunAccount,
1484
+ gunToEthAccount,
1485
+ getAddressesForChain
1486
+ };
1487
+ Object.assign(Gun.chain, baseMethods);
1488
+
1489
+ // Extend with proof functionality
1490
+ ProofChain.extendGun(Gun);
1491
+
1492
+ // Extend with stealth functionality
1493
+ extendGunWithStealth(Gun);
1494
+
1495
+ // Add retry mechanism
1496
+ const methods = ['stealth', 'generateStealthAddress', 'announceStealthPayment', 'getStealthPayments', 'recoverStealthFunds', 'publishStealthKeys', 'createAndStoreEncryptedPair', 'getAndDecryptPair', 'ethToGunAccount', 'gunToEthAccount', 'createSignature', 'setSigner'];
1497
+ methods.forEach(method => {
1498
+ const original = Gun.chain[method];
1499
+ if (original) {
1500
+ Gun.chain[method] = async function (/** @type {any} */...args) {
1501
+ const maxRetries = 3;
1502
+ let lastError;
1503
+ for (let i = 0; i < maxRetries; i++) {
1504
+ try {
1505
+ return await original.apply(this, args);
1506
+ } catch (error) {
1507
+ console.warn(`Attempt ${i + 1}/${maxRetries} failed:`, error);
1508
+ lastError = error;
1509
+ await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
1510
+ }
1511
+ }
1512
+ throw lastError;
1513
+ };
1514
+ }
1515
+ });
1516
+ }
1517
+
1518
+ /**
1519
+ * Inizializza Gun con le estensioni e le opzioni specificate
1520
+ * @param {string} chain - Chain da utilizzare
1521
+ * @param {Object} [options] - Opzioni di configurazione per Gun
1522
+ * @returns {Promise<IGunInstance>} Istanza di Gun configurata
1523
+ */
1524
+ async function initializeGun(chain = 'localhost', options = {}) {
1525
+ await initialize(chain);
1526
+ extendGun(Gun);
1527
+ const gun = /** @type {IGunInstance} */Gun(options);
1528
+ return gun;
1529
+ }
1530
+
1531
+ /**
1532
+ * Classe principale per l'integrazione di Gun con Ethereum
1533
+ */
1534
+ class GunEth {
1535
+ static keypair = null;
1536
+ static v_keypair = null;
1537
+ static s_keypair = null;
1538
+ static async init(chain = 'localhost') {
1539
+ await initialize(chain);
1540
+ return this;
1541
+ }
1542
+
1543
+ // Metodi statici
1544
+ static generateRandomId = generateRandomId;
1545
+ static generatePassword = generatePassword;
1546
+ static getSigner = getSigner;
1547
+ static verifySignature = verifySignature;
1548
+ static MESSAGE_TO_SIGN = MESSAGE_TO_SIGN;
1549
+ static getContractAddresses = getContractAddresses;
1550
+ static extendGun = extendGun;
1551
+ static initializeGun = initializeGun;
1552
+ static setSigner = setSigner;
1553
+ static gunToEthAccount = gunToEthAccount;
1554
+ static decryptPair = decryptPair;
1555
+ static decryptPairFromPassword = decryptPairFromPassword;
1556
+ static ethToGunAccount = ethToGunAccount;
1557
+ static createAndStoreEncryptedPair = createAndStoreEncryptedPair;
1558
+ static getAndDecryptPair = getAndDecryptPair;
1559
+ static createSignature = createSignature;
1560
+ static LOCAL_CONFIG = LOCAL_CONFIG;
1561
+ static contractAddresses = contractAddresses;
1562
+ }
1563
+
1564
+ // @ts-nocheck
1565
+
1566
+
1567
+ /**
1568
+ * @typedef {Object} BubbleProviderOptions
1569
+ * @property {string} rpcUrl - RPC URL for the provider
1570
+ * @property {string} chain - Chain name (e.g. 'localhost', 'optimismSepolia')
1571
+ * @property {Object} gun - Gun instance
1572
+ * @property {Object} keypair - Keypair for encryption
1573
+ * @property {string} keypair.epub - Public encryption key
1574
+ * @property {string} keypair.epriv - Private encryption key
1575
+ * @property {string} [contractAddress] - Optional custom contract address
1576
+ * @property {Object} [contractAbi] - Optional custom contract ABI
1577
+ */
1578
+
1579
+ /**
1580
+ * @typedef {Object} BubbleDetails
1581
+ * @property {string} id - Bubble ID
1582
+ * @property {string} name - Bubble name
1583
+ * @property {string} owner - Owner's Ethereum address
1584
+ * @property {boolean} isPrivate - Whether the bubble is private
1585
+ * @property {number} createdAt - Creation timestamp
1586
+ */
1587
+
1588
+ /**
1589
+ * @typedef {Object} FileMetadata
1590
+ * @property {string} name - File name
1591
+ * @property {string} owner - File owner
1592
+ * @property {string} filePath - File path
1593
+ * @property {number} created - Creation timestamp
1594
+ * @property {number} updated - Last update timestamp
1595
+ * @property {number} size - File size in bytes
1596
+ * @property {Object} encryptionInfo - Encryption info
1597
+ * @property {string} encryptionInfo.ownerEpub - Owner's public key
1598
+ * @property {string} encryptionInfo.ownerAddress - Owner's address
1599
+ */
1600
+
1601
+ /**
1602
+ * @typedef {Object} DeleteResult
1603
+ * @property {boolean} success - Whether deletion was successful
1604
+ * @property {string} [message] - Optional status message
1605
+ */
1606
+
1607
+ /**
1608
+ * @typedef {Object} DeleteBubbleResult
1609
+ * @property {boolean} success - Whether deletion was successful
1610
+ * @property {string} [message] - Optional status message
1611
+ */
1612
+
1613
+ /**
1614
+ * Base class for bubble storage providers
1615
+ */
1616
+ class BaseBubbleProvider {
1617
+ /**
1618
+ * Creates a new bubble provider instance
1619
+ * @param {BubbleProviderOptions} options - Provider configuration options
1620
+ */
1621
+ constructor(options) {
1622
+ const {
1623
+ rpcUrl,
1624
+ chain,
1625
+ gun,
1626
+ keypair,
1627
+ contractAddress,
1628
+ contractAbi
1629
+ } = options;
1630
+ if (!rpcUrl) throw new Error("RPC URL required");
1631
+ if (!chain) throw new Error("Chain name required");
1632
+ if (!gun) throw new Error("Gun instance required");
1633
+ if (!keypair || !keypair.epub || !keypair.epriv) {
1634
+ throw new Error("Valid keypair required");
1635
+ }
1636
+ this.provider = new ethers.ethers.JsonRpcProvider(rpcUrl);
1637
+
1638
+ // Use custom contract address and ABI if provided, otherwise use defaults
1639
+ const addresses = getAddressesForChain(chain);
1640
+ const contractAddr = contractAddress || addresses?.BUBBLE_REGISTRY_ADDRESS;
1641
+ const abi = contractAbi || BUBBLE_REGISTRY_ABI;
1642
+ if (!contractAddr) {
1643
+ throw new Error(`No contract address found for chain: ${chain}`);
1644
+ }
1645
+
1646
+ /** @type {BubbleRegistryContract} */
1647
+ this.contract = new ethers.ethers.Contract(contractAddr, abi, this.provider);
1648
+
1649
+ // Initialize Gun and keypair in base class
1650
+ this.gun = gun;
1651
+ this.keypair = keypair;
1652
+ this.bubbleRoot = this.gun.get("bubbles");
1653
+
1654
+ // Key cache
1655
+ this.keyPairs = new Map();
1656
+ }
1657
+
1658
+ /**
1659
+ * Verifies request signature
1660
+ * @param {string} address - Ethereum address of the requester
1661
+ * @param {string} message - Message that was signed
1662
+ * @param {string} signature - Signature of the message
1663
+ * @returns {Promise<boolean>} - Whether the signature is valid
1664
+ */
1665
+ async verifyRequest(address, message, signature) {
1666
+ try {
1667
+ const recoveredAddress = ethers.ethers.verifyMessage(message, signature);
1668
+ return recoveredAddress.toLowerCase() === address.toLowerCase();
1669
+ } catch (error) {
1670
+ return false;
1671
+ }
1672
+ }
1673
+
1674
+ /**
1675
+ * Manages user keys
1676
+ * @param {string} address - Ethereum address of the user
1677
+ * @returns {Promise<Object>} - Key pair of the user
1678
+ */
1679
+ async getUserKeyPair(address) {
1680
+ const normalizedAddress = address.toLowerCase();
1681
+ let pair = this.keyPairs.get(normalizedAddress);
1682
+ if (!pair) {
1683
+ pair = await SEA.pair();
1684
+ this.keyPairs.set(normalizedAddress, pair);
1685
+ }
1686
+ return pair;
1687
+ }
1688
+
1689
+ /**
1690
+ * Verifies bubble access
1691
+ * @param {string} bubbleId - ID of the bubble
1692
+ * @param {string} userAddress - Ethereum address of the user
1693
+ * @returns {Promise<boolean>} - Whether the user has access to the bubble
1694
+ */
1695
+ async verifyBubbleAccess(bubbleId, userAddress) {
1696
+ console.log("\n=== Checking bubble access ===");
1697
+ console.log("Bubble ID:", bubbleId);
1698
+ console.log("User Address:", userAddress);
1699
+ const hasAccess = await this.contract.hasAccess(bubbleId, userAddress);
1700
+ console.log("Access check result:", hasAccess);
1701
+ if (!hasAccess) {
1702
+ console.error("Access denied");
1703
+ throw new Error("No access to bubble");
1704
+ }
1705
+ console.log("Access verified successfully");
1706
+ return true;
1707
+ }
1708
+
1709
+ /**
1710
+ * Verifies bubble ownership
1711
+ * @param {string} bubbleId - ID of the bubble
1712
+ * @param {string} ownerAddress - Ethereum address of the owner
1713
+ * @returns {Promise<boolean>} - Whether the user is the owner of the bubble
1714
+ */
1715
+ async verifyBubbleOwnership(bubbleId, ownerAddress) {
1716
+ console.log("\n=== Verifying bubble ownership ===");
1717
+ console.log("Bubble ID:", bubbleId);
1718
+ console.log("Owner address:", ownerAddress);
1719
+ console.log("Getting bubble data from contract...");
1720
+ const bubbleData = await this.contract.getBubbleDetails(bubbleId);
1721
+ const onChainOwner = bubbleData[1]; // owner is the second returned element
1722
+
1723
+ console.log("On-chain owner:", onChainOwner);
1724
+ console.log("Expected owner:", ownerAddress);
1725
+ if (onChainOwner.toLowerCase() !== ownerAddress.toLowerCase()) {
1726
+ throw new Error("Not bubble owner");
1727
+ }
1728
+ console.log("Ownership verified successfully");
1729
+ return true;
1730
+ }
1731
+
1732
+ /**
1733
+ * Grants on-chain access to a target address
1734
+ * @param {string} bubbleId - ID of the bubble
1735
+ * @param {string} targetAddress - Ethereum address to grant access to
1736
+ * @returns {Promise<void>}
1737
+ */
1738
+ async grantOnChainAccess(bubbleId, targetAddress) {
1739
+ console.log("\n=== Starting grantOnChainAccess ===");
1740
+ console.log("Getting signer...");
1741
+ const signer = await getSigner();
1742
+ console.log("Signer obtained");
1743
+ console.log("Connecting contract with signer...");
1744
+ const contractWithSigner = this.contract.connect(signer);
1745
+ console.log("Contract connected");
1746
+ console.log("Sending grantAccess transaction...");
1747
+ console.log("Bubble ID:", bubbleId);
1748
+ console.log("Target Address:", targetAddress);
1749
+ const tx = await contractWithSigner.grantAccess(bubbleId, targetAddress);
1750
+ console.log("Transaction sent:", tx.hash);
1751
+ console.log("Waiting for transaction confirmation...");
1752
+ await tx.wait();
1753
+ console.log("Transaction confirmed:", tx.hash);
1754
+ console.log("=== grantOnChainAccess completed successfully ===");
1755
+ }
1756
+
1757
+ /**
1758
+ * Puts data into GunDB
1759
+ * @param {string} path - Path in GunDB
1760
+ * @param {Object} data - Data to put
1761
+ * @param {number} [maxRetries=3] - Maximum number of retries
1762
+ * @returns {Promise<void>}
1763
+ */
1764
+ async putGunData(path, data, maxRetries = 3) {
1765
+ let retries = 0;
1766
+ while (retries < maxRetries) {
1767
+ try {
1768
+ await new Promise((resolve, reject) => {
1769
+ this.gun.get(path).put(data, ack => {
1770
+ if (ack.err) reject(new Error(ack.err));else resolve();
1771
+ });
1772
+ });
1773
+ return;
1774
+ } catch (error) {
1775
+ retries++;
1776
+ if (retries === maxRetries) throw error;
1777
+ await new Promise(resolve => setTimeout(resolve, 1000));
1778
+ }
1779
+ }
1780
+ }
1781
+
1782
+ /**
1783
+ * Gets a node from GunDB
1784
+ * @typedef {Object} GunNode
1785
+ * @property {Object} data - Data of the node (if any)
1786
+ * @property {string} err - Error message if any
1787
+ * @property {any} content - Content of the node
1788
+ * @param {string} path - Path in GunDB
1789
+ * @param {number} [maxRetries=3] - Maximum number of retries
1790
+ * @returns {Promise<GunNode>} - Node data
1791
+ */
1792
+ async getGunNode(path, maxRetries = 3) {
1793
+ let retries = 0;
1794
+ let node = null;
1795
+ while (retries < maxRetries) {
1796
+ try {
1797
+ node = await new Promise((resolve, reject) => {
1798
+ const timeoutId = setTimeout(() => {
1799
+ reject(new Error("Gun node fetch timeout"));
1800
+ }, 5000);
1801
+ this.gun.get(path).once(data => {
1802
+ clearTimeout(timeoutId);
1803
+ resolve(data);
1804
+ });
1805
+ });
1806
+ if (node) break;
1807
+ retries++;
1808
+ await new Promise(resolve => setTimeout(resolve, 1000));
1809
+ } catch (error) {
1810
+ console.error(`Gun node fetch attempt ${retries + 1} failed:`, error);
1811
+ retries++;
1812
+ if (retries === maxRetries) throw error;
1813
+ await new Promise(resolve => setTimeout(resolve, 1000));
1814
+ }
1815
+ }
1816
+ return node;
1817
+ }
1818
+
1819
+ /**
1820
+ * Handles bubble creation
1821
+ * @param {Object} params - Parameters for bubble creation
1822
+ * @param {string} params.name - Name of the bubble
1823
+ * @param {boolean} params.isPrivate - Whether the bubble is private
1824
+ * @param {string} params.userAddress - Ethereum address of the user
1825
+ * @returns {Promise<BubbleDetails>} - Metadata of the created bubble
1826
+ */
1827
+ async handleCreateBubble(params) {
1828
+ const {
1829
+ name,
1830
+ isPrivate,
1831
+ userAddress
1832
+ } = params;
1833
+ if (!name || typeof isPrivate !== "boolean" || !userAddress) {
1834
+ throw new Error("Invalid parameters for bubble creation");
1835
+ }
1836
+ try {
1837
+ console.log("Starting bubble creation...");
1838
+ console.log("Parameters:", params);
1839
+ const signer = await getSigner();
1840
+ const contractWithSigner = this.contract.connect(signer);
1841
+ const tx = await contractWithSigner.createBubble(name, isPrivate);
1842
+ const receipt = await tx.wait();
1843
+ const event = receipt.logs.find(log => {
1844
+ try {
1845
+ const parsedLog = this.contract.interface.parseLog(log);
1846
+ return parsedLog.name === "BubbleCreated";
1847
+ } catch (e) {
1848
+ return false;
1849
+ }
1850
+ });
1851
+ if (!event) {
1852
+ throw new Error("Bubble creation event not found");
1853
+ }
1854
+ const parsedEvent = this.contract.interface.parseLog(event);
1855
+ const bubbleId = parsedEvent.args[0];
1856
+ const metadata = {
1857
+ id: bubbleId,
1858
+ name,
1859
+ owner: userAddress,
1860
+ isPrivate,
1861
+ createdAt: Date.now()
1862
+ };
1863
+ await this.putGunData(`bubbles/${bubbleId}`, metadata);
1864
+ return metadata;
1865
+ } catch (error) {
1866
+ console.error("Error creating bubble:", error);
1867
+ throw error;
1868
+ }
1869
+ }
1870
+
1871
+ /**
1872
+ * Handles granting permission to a bubble
1873
+ * @param {string} bubbleId - ID of the bubble
1874
+ * @param {string} targetAddress - Ethereum address of the target user
1875
+ * @param {string} granterAddress - Ethereum address of the granter
1876
+ * @param {Object} options - Additional options
1877
+ * @param {string} options.granteeEpub - Public encryption key of the grantee
1878
+ * @returns {Promise<Object>} - Result of the permission grant
1879
+ */
1880
+ async handleGrantPermission(bubbleId, targetAddress, granterAddress, options) {
1881
+ try {
1882
+ if (!options?.granteeEpub) {
1883
+ throw new Error("Grantee epub key is required");
1884
+ }
1885
+ await this.verifyBubbleOwnership(bubbleId, granterAddress);
1886
+ await this.grantOnChainAccess(bubbleId, targetAddress);
1887
+ return {
1888
+ success: true
1889
+ };
1890
+ } catch (error) {
1891
+ console.error("Error in handleGrantPermission:", error);
1892
+ throw error;
1893
+ }
1894
+ }
1895
+
1896
+ /**
1897
+ * Handles file upload to a bubble
1898
+ * @abstract
1899
+ * @param {string} bubbleId - ID of the bubble
1900
+ * @param {string} fileName - Name of the file
1901
+ * @param {string} content - File content
1902
+ * @param {string} userAddress - Ethereum address of the uploader
1903
+ * @returns {Promise<FileMetadata>} - Metadata of the uploaded file
1904
+ */
1905
+ async handleFileUpload(bubbleId, fileName, content, userAddress) {
1906
+ throw new Error('handleFileUpload must be implemented');
1907
+ }
1908
+
1909
+ /**
1910
+ * Handles file deletion from a bubble
1911
+ * @abstract
1912
+ * @param {string} bubbleId - ID of the bubble
1913
+ * @param {string} fileName - Name of the file
1914
+ * @param {string} userAddress - Ethereum address of the user
1915
+ * @returns {Promise<DeleteResult>} - Result of the deletion
1916
+ */
1917
+ async handleDeleteFile(bubbleId, fileName, userAddress) {
1918
+ throw new Error('handleDeleteFile must be implemented');
1919
+ }
1920
+
1921
+ /**
1922
+ * Handles file download from a bubble
1923
+ * @abstract
1924
+ * @param {string} bubbleId - ID of the bubble
1925
+ * @param {string} fileName - Name of the file
1926
+ * @param {string} userAddress - Ethereum address of the user
1927
+ * @returns {Promise<{content: any, metadata: Object}>} - File content and metadata
1928
+ */
1929
+ async handleFileDownload(bubbleId, fileName, userAddress) {
1930
+ throw new Error('handleFileDownload must be implemented');
1931
+ }
1932
+
1933
+ /**
1934
+ * Handles bubble deletion
1935
+ * @abstract
1936
+ * @param {string} bubbleId - ID of the bubble
1937
+ * @param {string} userAddress - Ethereum address of the user
1938
+ * @returns {Promise<DeleteBubbleResult>} - Result of the deletion
1939
+ */
1940
+ async handleDeleteBubble(bubbleId, userAddress) {
1941
+ throw new Error('handleDeleteBubble must be implemented');
1942
+ }
1943
+ }
1944
+
1945
+ /**
1946
+ * @typedef {Object} FileMetadata
1947
+ * @property {string} name - Name of the file
1948
+ * @property {string} owner - Address of file owner
1949
+ * @property {number} created - Creation timestamp
1950
+ * @property {number} updated - Last update timestamp
1951
+ * @property {string} filePath - File path
1952
+ * @property {number} size - File size
1953
+ * @property {Object} encryptionInfo - Encryption info
1954
+ * @property {string} encryptionInfo.ownerEpub - Owner's public key
1955
+ * @property {string} encryptionInfo.ownerAddress - Owner's address
1956
+ * @property {boolean} readOnly - Whether file is read-only
1957
+ */
1958
+
1959
+ class GUNBubbleProvider extends BaseBubbleProvider {
1960
+ constructor(options) {
1961
+ super(options);
1962
+
1963
+ // Configura il Gun user
1964
+ this.user = this.gun.user();
1965
+ this.user.auth(this.keypair);
1966
+
1967
+ // Debug log
1968
+ console.log("GUNBubbleProvider initialized:", {
1969
+ hasGun: !!this.gun,
1970
+ hasUser: !!this.user,
1971
+ hasKeypair: !!this.keypair,
1972
+ hasBubbleRoot: !!this.bubbleRoot,
1973
+ gunOpts: this.gun._.opt
1974
+ });
1975
+
1976
+ // Verifica che Gun sia pronto
1977
+ this.gun.on('hi', peer => {
1978
+ console.log('Connected to peer:', peer);
1979
+ });
1980
+ this.gun.on('error', error => {
1981
+ console.error('Gun error:', error);
1982
+ });
1983
+ }
1984
+
1985
+ /**
1986
+ * @typedef {Object} FileDownloadParams
1987
+ * @property {string} bubbleId - ID of the bubble
1988
+ * @property {string} fileName - Name of the file to download
1989
+ * @property {string} userAddress - Ethereum address of the user
1990
+ */
1991
+
1992
+ /**
1993
+ * Handles file download
1994
+ * @param {string} bubbleId - ID of the bubble
1995
+ * @param {string} fileName - Name of the file to download
1996
+ * @param {string} userAddress - Ethereum address of the user
1997
+ */
1998
+ async handleFileDownload(bubbleId, fileName, userAddress) {
1999
+ try {
2000
+ if (!this.keypair) throw new Error('Keypair not initialized');
2001
+ console.log("\n=== Starting file download ===");
2002
+ console.log("Bubble ID:", bubbleId);
2003
+ console.log("File name:", fileName);
2004
+ console.log("User address:", userAddress);
2005
+ await this.verifyBubbleAccess(bubbleId, userAddress);
2006
+ const filePath = `bubbles/${bubbleId}/files/${fileName}`;
2007
+ console.log("Reading from path:", filePath);
2008
+ const fileData = await new Promise(resolve => {
2009
+ this.gun.get(filePath).once(data => {
2010
+ console.log("File data retrieved:", data);
2011
+ resolve(data);
2012
+ });
2013
+ });
2014
+ if (!fileData) {
2015
+ console.error("File not found at path:", filePath);
2016
+ throw new Error('File not found');
2017
+ }
2018
+ let content;
2019
+ const normalizedUserAddress = userAddress.toLowerCase();
2020
+ if (fileData.owner.toLowerCase() === normalizedUserAddress) {
2021
+ console.log("User is owner, decrypting with owner key");
2022
+ content = await decrypt(fileData.content, this.keypair);
2023
+ } else {
2024
+ console.log("User is not owner, looking for shared content");
2025
+ const sharedPath = `${filePath}/sharedWith/${normalizedUserAddress}`;
2026
+ console.log("Looking for shared data at:", sharedPath);
2027
+ const sharedData = await new Promise(resolve => {
2028
+ this.gun.get(sharedPath).once(data => {
2029
+ console.log("Shared data loaded:", data);
2030
+ resolve(data);
2031
+ });
2032
+ });
2033
+ if (!sharedData || !sharedData.content || !sharedData.ownerEpub) {
2034
+ console.error("Shared data not found or incomplete:", sharedData);
2035
+ throw new Error('Shared content not found');
2036
+ }
2037
+ try {
2038
+ // Use deriveSharedKey to get the shared key
2039
+ const sharedKeypair = await deriveSharedKey(sharedData.ownerEpub, this.keypair);
2040
+ console.log("Shared key derived");
2041
+
2042
+ // Decrypt the content using the shared key
2043
+ content = await decrypt(sharedData.content, sharedKeypair);
2044
+ if (!content) {
2045
+ throw new Error('Failed to decrypt content');
2046
+ }
2047
+ console.log("Content decrypted successfully");
2048
+ } catch (error) {
2049
+ console.error("Decryption error:", {
2050
+ phase: error.message.includes('key') ? 'key derivation' : 'content decryption',
2051
+ error: error.message,
2052
+ sharedData: {
2053
+ hasContent: !!sharedData.content,
2054
+ hasOwnerEpub: !!sharedData.ownerEpub,
2055
+ contentLength: sharedData.content?.length
2056
+ }
2057
+ });
2058
+ throw error;
2059
+ }
2060
+ }
2061
+ return {
2062
+ content,
2063
+ metadata: {
2064
+ name: fileData.name,
2065
+ owner: fileData.owner,
2066
+ filePath: fileData.filePath,
2067
+ created: fileData.created,
2068
+ updated: fileData.updated,
2069
+ size: fileData.size,
2070
+ readOnly: fileData.readOnly || false,
2071
+ encryptionInfo: fileData.encryptionInfo
2072
+ }
2073
+ };
2074
+ } catch (error) {
2075
+ console.error('Error downloading file:', error);
2076
+ throw error;
2077
+ }
2078
+ }
2079
+
2080
+ /**
2081
+ * @typedef {Object} FileUploadOptions
2082
+ * @property {string} bubbleId - ID of the bubble
2083
+ * @property {string} fileName - Name of the file
2084
+ * @property {string} content - Content of the file
2085
+ * @property {string} userAddress - Address of the user
2086
+ */
2087
+ async handleFileUpload(bubbleId, fileName, content, userAddress) {
2088
+ try {
2089
+ console.log("\n=== Starting file upload ===");
2090
+ console.log("Bubble ID:", bubbleId);
2091
+ console.log("File name:", fileName);
2092
+ console.log("User address:", userAddress);
2093
+ if (!this.keypair) {
2094
+ throw new Error('Keypair not initialized');
2095
+ }
2096
+ await this.verifyBubbleAccess(bubbleId, userAddress);
2097
+
2098
+ // Encrypt the content
2099
+ console.log("Encrypting content...");
2100
+ const encryptedContent = await encrypt(content, this.keypair);
2101
+ console.log("Content encrypted successfully");
2102
+
2103
+ // Create the file metadata
2104
+ const fileMetadata = {
2105
+ name: fileName,
2106
+ owner: userAddress,
2107
+ content: encryptedContent,
2108
+ created: Date.now(),
2109
+ updated: Date.now(),
2110
+ readOnly: false,
2111
+ sharedWith: {}
2112
+ };
2113
+ console.log("Saving to GUN...");
2114
+
2115
+ // Use a more direct approach with GUN
2116
+ return new Promise((resolve, reject) => {
2117
+ // Create the full path for the file
2118
+ const filePath = `bubbles/${bubbleId}/files/${fileName}`;
2119
+ console.log("File path:", filePath);
2120
+
2121
+ // Save directly to the path as an object
2122
+ this.gun.get(filePath).put(fileMetadata, ack => {
2123
+ if (ack.err) {
2124
+ console.error("Save error:", ack.err);
2125
+ reject(new Error(ack.err));
2126
+ return;
2127
+ }
2128
+ console.log("Initial save successful, verifying...");
2129
+
2130
+ // Verify the save
2131
+ this.gun.get(filePath).once(data => {
2132
+ if (!data || !data.content) {
2133
+ console.error("Verification failed:", data);
2134
+ reject(new Error('File verification failed'));
2135
+ return;
2136
+ }
2137
+ console.log("File saved and verified successfully:", {
2138
+ name: data.name,
2139
+ owner: data.owner,
2140
+ hasContent: !!data.content,
2141
+ created: new Date(data.created).toISOString()
2142
+ });
2143
+ resolve(fileMetadata);
2144
+ });
2145
+ });
2146
+
2147
+ // Set a timeout for the verification
2148
+ setTimeout(() => {
2149
+ this.gun.get(filePath).once(data => {
2150
+ if (data && data.content) {
2151
+ console.log("Save operation taking too long, checking state...");
2152
+ console.log("Data found after timeout, resolving...");
2153
+ resolve(fileMetadata);
2154
+ }
2155
+ });
2156
+ }, 5000);
2157
+ });
2158
+ } catch (error) {
2159
+ console.error('Error uploading file:', error);
2160
+ throw error;
2161
+ }
2162
+ }
2163
+
2164
+ /**
2165
+ * @typedef {Object} GrantPermissionOptions
2166
+ * @property {string} granteeEpub - Public encryption key of grantee
2167
+ */
2168
+ async handleGrantPermission(bubbleId, targetAddress, granterAddress, options = {}) {
2169
+ try {
2170
+ console.log("\n=== Starting handleGrantPermission ===");
2171
+ if (!options.granteeEpub) {
2172
+ throw new Error("Grantee epub key is required");
2173
+ }
2174
+ await this.verifyBubbleOwnership(bubbleId, granterAddress);
2175
+ await this.grantOnChainAccess(bubbleId, targetAddress);
2176
+ const filesPath = `bubbles/${bubbleId}/files`;
2177
+ console.log("Files path:", filesPath);
2178
+ const fileData = await this.getGunNode(`${filesPath}/secret.txt`);
2179
+ console.log("File data:", fileData);
2180
+ if (!fileData || !fileData.content) {
2181
+ console.log("No file content found");
2182
+ return {
2183
+ success: true
2184
+ };
2185
+ }
2186
+ try {
2187
+ // Decrypt the original content
2188
+ const decryptedContent = await decrypt(fileData.content, this.keypair);
2189
+ console.log("Original content decrypted");
2190
+
2191
+ // Use deriveSharedKey to get the shared key
2192
+ const sharedKeypair = await deriveSharedKey(options.granteeEpub, this.keypair);
2193
+ console.log("Shared key derived successfully");
2194
+
2195
+ // Encrypt the content with the shared key
2196
+ const encryptedContent = await encrypt(decryptedContent, sharedKeypair);
2197
+ console.log("Content re-encrypted with shared key");
2198
+ const sharedPath = `${filesPath}/secret.txt/sharedWith/${targetAddress.toLowerCase()}`;
2199
+ console.log("Saving shared data at:", sharedPath);
2200
+ const sharedData = {
2201
+ address: targetAddress,
2202
+ grantedAt: Date.now(),
2203
+ content: encryptedContent,
2204
+ ownerEpub: this.keypair.epub
2205
+ };
2206
+ await this.putGunData(sharedPath, sharedData);
2207
+ console.log("Shared data saved successfully");
2208
+ return {
2209
+ success: true
2210
+ };
2211
+ } catch (error) {
2212
+ console.error("Error processing file:", error);
2213
+ throw error;
2214
+ }
2215
+ } catch (error) {
2216
+ console.error("Error in handleGrantPermission:", error);
2217
+ throw error;
2218
+ }
2219
+ }
2220
+
2221
+ /**
2222
+ * @typedef {Object} BubbleParams
2223
+ * @property {string} name - The name of the bubble
2224
+ * @property {boolean} isPrivate - Whether the bubble is private
2225
+ * @property {string} userAddress - The address of the user creating the bubble
2226
+ */
2227
+
2228
+ /**
2229
+ * Handles the creation of a new bubble.
2230
+ * @param {BubbleParams} params - The parameters for bubble creation
2231
+ */
2232
+ async handleCreateBubble(params) {
2233
+ const {
2234
+ name,
2235
+ isPrivate,
2236
+ userAddress
2237
+ } = params;
2238
+ if (!name || typeof isPrivate !== 'boolean' || !userAddress) {
2239
+ throw new Error('Invalid parameters for bubble creation');
2240
+ }
2241
+ try {
2242
+ console.log("Starting bubble creation...");
2243
+ console.log("Parameters:", params);
2244
+
2245
+ // Get the signer
2246
+ console.log("Getting signer...");
2247
+ const signer = await getSigner();
2248
+
2249
+ // Create the bubble on-chain
2250
+ console.log("Creating bubble on-chain...");
2251
+ const contractWithSigner = this.contract.connect(signer);
2252
+ const tx = await contractWithSigner.createBubble(name, isPrivate);
2253
+ console.log("Transaction sent:", tx.hash);
2254
+ console.log("Waiting for transaction...");
2255
+ const receipt = await tx.wait();
2256
+
2257
+ // Extract the bubble ID from the event
2258
+ console.log("Parsing event...");
2259
+ const event = receipt.logs.find(log => {
2260
+ try {
2261
+ const parsedLog = this.contract.interface.parseLog(log);
2262
+ return parsedLog.name === 'BubbleCreated';
2263
+ } catch (e) {
2264
+ return false;
2265
+ }
2266
+ });
2267
+ if (!event) {
2268
+ throw new Error('Bubble creation event not found');
2269
+ }
2270
+ const parsedEvent = this.contract.interface.parseLog(event);
2271
+ const bubbleId = parsedEvent.args[0];
2272
+ console.log("Bubble ID:", bubbleId);
2273
+
2274
+ // Create the metadata
2275
+ const metadata = {
2276
+ id: bubbleId,
2277
+ name,
2278
+ owner: userAddress,
2279
+ isPrivate,
2280
+ createdAt: Date.now()
2281
+ };
2282
+
2283
+ // Save the metadata to GUN
2284
+ console.log("Saving metadata to GUN...");
2285
+ await new Promise((resolve, reject) => {
2286
+ this.gun.get('bubbles').get(bubbleId).put(metadata, ack => {
2287
+ if (ack.err) {
2288
+ console.error("GUN save error:", ack.err);
2289
+ reject(new Error(ack.err));
2290
+ } else {
2291
+ console.log("GUN save successful");
2292
+ resolve();
2293
+ }
2294
+ });
2295
+ });
2296
+ console.log("Metadata saved successfully:", metadata);
2297
+ return metadata;
2298
+ } catch (error) {
2299
+ console.error('Error creating bubble:', error);
2300
+ throw error;
2301
+ }
2302
+ }
2303
+
2304
+ /**
2305
+ * @typedef {Object} DeleteFileOptions
2306
+ * @property {string} bubbleId - The ID of the bubble
2307
+ * @property {string} fileName - The name of the file to delete
2308
+ * @property {string} userAddress - The address of the user requesting the deletion
2309
+ */
2310
+ async handleDeleteFile(bubbleId, fileName, userAddress) {
2311
+ try {
2312
+ console.log("\n=== Starting file deletion ===");
2313
+ console.log("Bubble ID:", bubbleId);
2314
+ console.log("File name:", fileName);
2315
+ console.log("User address:", userAddress);
2316
+
2317
+ // Verify access
2318
+ await this.verifyBubbleAccess(bubbleId, userAddress);
2319
+
2320
+ // Build the file path
2321
+ const filePath = `bubbles/${bubbleId}/files/${fileName}`;
2322
+ console.log("Deleting file at path:", filePath);
2323
+
2324
+ // Load the file data to verify ownership
2325
+ const fileData = await new Promise(resolve => {
2326
+ this.gun.get(filePath).once(resolve);
2327
+ });
2328
+ if (!fileData) {
2329
+ throw new Error('File not found');
2330
+ }
2331
+
2332
+ // Verify that the user is the owner
2333
+ if (fileData.owner.toLowerCase() !== userAddress.toLowerCase()) {
2334
+ throw new Error('Only file owner can delete files');
2335
+ }
2336
+
2337
+ // Delete the file and its metadata
2338
+ return new Promise((resolve, reject) => {
2339
+ // First delete the shared data
2340
+ const sharedWithNode = this.gun.get(filePath).get('sharedWith');
2341
+ sharedWithNode.map().once((data, key) => {
2342
+ if (key !== '_') {
2343
+ sharedWithNode.get(key).put(null);
2344
+ }
2345
+ });
2346
+
2347
+ // Then delete the file itself by setting all fields to null
2348
+ const fileNode = this.gun.get(filePath);
2349
+ const nullData = {
2350
+ name: null,
2351
+ owner: null,
2352
+ content: null,
2353
+ created: null,
2354
+ updated: null,
2355
+ readOnly: null,
2356
+ sharedWith: null
2357
+ };
2358
+ fileNode.put(nullData, ack => {
2359
+ if (ack.err) {
2360
+ console.error("Error nullifying file data:", ack.err);
2361
+ reject(new Error(ack.err));
2362
+ return;
2363
+ }
2364
+
2365
+ // Remove the file reference from the bubble's file list
2366
+ this.gun.get(`bubbles/${bubbleId}/files`).get(fileName).put(null, ack => {
2367
+ if (ack.err) {
2368
+ console.error("Error removing file reference:", ack.err);
2369
+ reject(new Error(ack.err));
2370
+ return;
2371
+ }
2372
+ console.log("File deleted successfully");
2373
+ resolve({
2374
+ success: true
2375
+ });
2376
+ });
2377
+ });
2378
+ });
2379
+ } catch (error) {
2380
+ console.error('Error deleting file:', error);
2381
+ throw error;
2382
+ }
2383
+ }
2384
+
2385
+ /**
2386
+ * @typedef {Object} DeleteBubbleResult
2387
+ * @property {boolean} success - Indicates if the bubble was successfully deleted
2388
+ */
2389
+
2390
+ /**
2391
+ * Deletes a bubble and all its contents.
2392
+ * @param {string} bubbleId - The ID of the bubble to delete.
2393
+ * @param {string} userAddress - The address of the user requesting the deletion.
2394
+ * @returns {Promise<DeleteBubbleResult>} - The result of the deletion operation.
2395
+ */
2396
+ async handleDeleteBubble(bubbleId, userAddress) {
2397
+ try {
2398
+ console.log("\n=== Starting bubble deletion ===");
2399
+ console.log("Bubble ID:", bubbleId);
2400
+ console.log("User address:", userAddress);
2401
+
2402
+ // Verify bubble ownership
2403
+ await this.verifyBubbleOwnership(bubbleId, userAddress);
2404
+
2405
+ // Build the bubble path
2406
+ const bubblePath = `bubbles/${bubbleId}`;
2407
+ console.log("Deleting bubble at path:", bubblePath);
2408
+
2409
+ // Delete the bubble and all its contents
2410
+ return new Promise((resolve, reject) => {
2411
+ // First delete all files in the bubble
2412
+ this.gun.get(bubblePath).get('files').map().once((fileData, fileName) => {
2413
+ if (fileName === '_') return;
2414
+
2415
+ // Delete shared data for each file
2416
+ this.gun.get(bubblePath).get('files').get(fileName).get('sharedWith').map().once((sharedData, sharedAddress) => {
2417
+ if (sharedAddress === '_') return;
2418
+
2419
+ // Set shared data to null
2420
+ this.gun.get(bubblePath).get('files').get(fileName).get('sharedWith').get(sharedAddress).put(null);
2421
+ });
2422
+
2423
+ // Set file metadata to null
2424
+ this.gun.get(bubblePath).get('files').get(fileName).put({
2425
+ name: null,
2426
+ owner: null,
2427
+ content: null,
2428
+ created: null,
2429
+ updated: null,
2430
+ readOnly: null,
2431
+ sharedWith: null
2432
+ });
2433
+ });
2434
+
2435
+ // Then set bubble metadata to null
2436
+ this.gun.get(bubblePath).put({
2437
+ id: null,
2438
+ name: null,
2439
+ owner: null,
2440
+ isPrivate: null,
2441
+ createdAt: null,
2442
+ files: null
2443
+ }, ack => {
2444
+ if (ack.err) {
2445
+ console.error("Error nullifying bubble data:", ack.err);
2446
+ reject(new Error(ack.err));
2447
+ return;
2448
+ }
2449
+ console.log("Bubble data nullified successfully");
2450
+ resolve({
2451
+ success: true
2452
+ });
2453
+ });
2454
+ });
2455
+ } catch (error) {
2456
+ console.error('Error deleting bubble:', error);
2457
+ throw error;
2458
+ }
2459
+ }
2460
+ }
2461
+
2462
+ exports.GUNBubbleProvider = GUNBubbleProvider;
2463
+ exports.GunEth = GunEth;
2464
+ exports.ProofChain = ProofChain;
2465
+ exports.StealthChain = StealthChain;