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