gun-eth 1.4.35 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
package/src/index.js DELETED
@@ -1,999 +0,0 @@
1
- import Gun from "gun";
2
- import { ethers } from "ethers";
3
- import {
4
- PROOF_OF_INTEGRITY_ABI,
5
- STEALTH_ANNOUNCER_ABI,
6
- getAddressesForChain,
7
- isLocalEnvironment,
8
- CHAIN_CONFIG,
9
- } from "./abis/abis.js";
10
- import { LOCAL_CONFIG } from "./config/local.js";
11
-
12
- import 'gun/sea.js'; // Questo modifica Gun globalmente
13
- const SEA = Gun.SEA;
14
-
15
- let PROOF_CONTRACT_ADDRESS;
16
- let contractAddresses = {
17
- PROOF_OF_INTEGRITY_ADDRESS:
18
- CHAIN_CONFIG.optimismSepolia.PROOF_OF_INTEGRITY_ADDRESS,
19
- STEALTH_ANNOUNCER_ADDRESS:
20
- CHAIN_CONFIG.optimismSepolia.STEALTH_ANNOUNCER_ADDRESS,
21
- };
22
-
23
- // Funzione di inizializzazione per Node.js
24
- const initNodeEnvironment = async () => {
25
- if (typeof window === "undefined") {
26
- try {
27
- // Importazioni dinamiche
28
- const { fileURLToPath } = await import("url");
29
- const { dirname } = await import("path");
30
- const { readFile } = await import("fs/promises");
31
- const { join } = await import("path");
32
-
33
- const __filename = fileURLToPath(import.meta.url);
34
- const __dirname = dirname(__filename);
35
-
36
- try {
37
- const rawdata = await readFile(
38
- join(__dirname, "contract-address.json"),
39
- "utf8"
40
- );
41
- contractAddresses = JSON.parse(rawdata);
42
- console.log("Loaded contract addresses:", contractAddresses);
43
- } catch (err) {
44
- console.warn("Warning: contract-address.json not found or invalid");
45
- }
46
- } catch (error) {
47
- console.error("Error loading Node.js modules:", error);
48
- }
49
- }
50
- };
51
-
52
- // Inizializza solo in ambiente Node.js
53
- if (typeof window === "undefined") {
54
- initNodeEnvironment();
55
- }
56
-
57
- // ... resto del codice ...
58
-
59
- // Esporta tutto ciò che serve
60
- export {
61
- Gun,
62
- initNodeEnvironment,
63
- // ... altre esportazioni ...
64
- };
65
-
66
- // Export default
67
- export default Gun;
68
-
69
- // =============================================
70
- // UTILITY FUNCTIONS
71
- // =============================================
72
- /**
73
- * Generates a random node ID for GunDB
74
- * @returns {string} A random hexadecimal string
75
- */
76
- export function generateRandomId() {
77
- const randomBytes = ethers.randomBytes(32);
78
- return ethers.hexlify(randomBytes).slice(2);
79
- }
80
-
81
- /**
82
- * Generates a password from a signature.
83
- * @param {string} signature - The signature to derive the password from.
84
- * @returns {string|null} The generated password or null if generation fails.
85
- */
86
- export function generatePassword(signature) {
87
- try {
88
- const signatureBytes = ethers.toUtf8Bytes(signature);
89
- const hash = ethers.keccak256(signatureBytes);
90
- console.log("Generated password:", hash);
91
- return hash;
92
- } catch (error) {
93
- console.error("Error generating password:", error);
94
- return null;
95
- }
96
- }
97
-
98
- /**
99
- * Converts a Gun private key to an Ethereum account.
100
- * @param {string} gunPrivateKey - The Gun private key in base64url format.
101
- * @returns {Object} An object containing the Ethereum account and public key.
102
- */
103
- export function gunToEthAccount(gunPrivateKey) {
104
- // Function to convert base64url to hex
105
- const base64UrlToHex = (base64url) => {
106
- const padding = "=".repeat((4 - (base64url.length % 4)) % 4);
107
- const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
108
- const binary = atob(base64);
109
- return Array.from(binary, (char) =>
110
- char.charCodeAt(0).toString(16).padStart(2, "0")
111
- ).join("");
112
- };
113
-
114
- // Convert Gun private key to hex format
115
- const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
116
-
117
- // Create an Ethereum wallet from the private key
118
- const wallet = new ethers.Wallet(hexPrivateKey);
119
-
120
- // Get the public address (public key)
121
- const publicKey = wallet.address;
122
-
123
- return {
124
- account: wallet,
125
- publicKey: publicKey,
126
- privateKey: hexPrivateKey,
127
- };
128
- }
129
-
130
- /**
131
- * Gets an Ethereum signer based on current configuration
132
- * @returns {Promise<ethers.Signer>} The configured signer
133
- * @throws {Error} If no valid provider is found
134
- */
135
- export const getSigner = async () => {
136
- if (rpcUrl && privateKey) {
137
- const provider = new ethers.JsonRpcProvider(rpcUrl, {
138
- chainId: LOCAL_CONFIG.CHAIN_ID,
139
- name: "localhost",
140
- });
141
- return new Wallet(privateKey, provider);
142
- } else if (
143
- typeof window !== "undefined" &&
144
- typeof window.ethereum !== "undefined"
145
- ) {
146
- await window.ethereum.request({ method: "eth_requestAccounts" });
147
- const provider = new ethers.BrowserProvider(window.ethereum);
148
- return provider.getSigner();
149
- } else {
150
- throw new Error("No valid Ethereum provider found");
151
- }
152
- };
153
-
154
- /**
155
- * Utility function to generate stealth address
156
- * @param {string} sharedSecret - The shared secret
157
- * @param {string} spendingPublicKey - The spending public key
158
- * @returns {Object} The stealth address and private key
159
- */
160
- export function deriveStealthAddress(sharedSecret, spendingPublicKey) {
161
- try {
162
- const sharedSecretBytes = ethers.toUtf8Bytes(sharedSecret);
163
- const spendingPublicKeyBytes = ethers.toUtf8Bytes(spendingPublicKey);
164
-
165
- const stealthPrivateKey = ethers.keccak256(
166
- ethers.concat([sharedSecretBytes, spendingPublicKeyBytes])
167
- );
168
-
169
- const stealthWallet = new Wallet(stealthPrivateKey);
170
-
171
- console.log("Debug deriveStealthAddress:", {
172
- sharedSecretHex: stealthPrivateKey,
173
- spendingPublicKey,
174
- stealthPrivateKey,
175
- stealthAddress: stealthWallet.address,
176
- });
177
-
178
- return {
179
- stealthPrivateKey,
180
- stealthAddress: stealthWallet.address,
181
- wallet: stealthWallet,
182
- };
183
- } catch (error) {
184
- console.error("Error in deriveStealthAddress:", error);
185
- throw error;
186
- }
187
- }
188
-
189
- // =============================================
190
- // BASIC GUN-ETH CHAIN METHODS
191
- // =============================================
192
-
193
- // Set the message to sign
194
- Gun.chain.MESSAGE_TO_SIGN = MESSAGE_TO_SIGN;
195
-
196
- /**
197
- * Sets standalone configuration for Gun.
198
- * @param {string} newRpcUrl - The new RPC URL.
199
- * @param {string} newPrivateKey - The new private key.
200
- * @returns {Gun} The Gun instance for chaining.
201
- */
202
- Gun.chain.setSigner = function (newRpcUrl, newPrivateKey) {
203
- rpcUrl = newRpcUrl;
204
- privateKey = newPrivateKey;
205
- console.log("Standalone configuration set");
206
- return this;
207
- };
208
-
209
- Gun.chain.getSigner = getSigner();
210
-
211
- /**
212
- * Verifies an Ethereum signature.
213
- * @param {string} message - The original message that was signed.
214
- * @param {string} signature - The signature to verify.
215
- * @returns {Promise<string|null>} The recovered address or null if verification fails.
216
- */
217
- Gun.chain.verifySignature = async function (message, signature) {
218
- try {
219
- const recoveredAddress = ethers.verifyMessage(message, signature);
220
- return recoveredAddress;
221
- } catch (error) {
222
- console.error("Error verifying signature:", error);
223
- return null;
224
- }
225
- };
226
-
227
- /**
228
- * Generates a password from a signature.
229
- * @param {string} signature - The signature to derive the password from.
230
- * @returns {string|null} The generated password or null if generation fails.
231
- */
232
- Gun.chain.generatePassword = function (signature) {
233
- return generatePassword(signature);
234
- };
235
-
236
- /**
237
- * Creates an Ethereum signature for a given message.
238
- * @param {string} message - The message to sign.
239
- * @returns {Promise<string|null>} The signature or null if signing fails.
240
- */
241
- Gun.chain.createSignature = async function (message) {
242
- try {
243
- // Check if message matches MESSAGE_TO_SIGN
244
- if (message !== MESSAGE_TO_SIGN) {
245
- throw new Error("Invalid message, valid message is: " + MESSAGE_TO_SIGN);
246
- }
247
- const signer = await getSigner();
248
- const signature = await signer.signMessage(message);
249
- console.log("Signature created:", signature);
250
- return signature;
251
- } catch (error) {
252
- console.error("Error creating signature:", error);
253
- return null;
254
- }
255
- };
256
-
257
- // =============================================
258
- // KEY PAIR MANAGEMENT
259
- // =============================================
260
- /**
261
- * Creates and stores an encrypted key pair for a given address.
262
- * @param {string} address - The Ethereum address to associate with the key pair.
263
- * @param {string} signature - The signature to use for encryption.
264
- * @returns {Promise<void>}
265
- */
266
- Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
267
- try {
268
- const gun = this;
269
- const pair = await SEA.pair();
270
- const v_pair = await SEA.pair();
271
- const s_pair = await SEA.pair();
272
- const password = generatePassword(signature);
273
-
274
- // Save original SEA pairs
275
- const encryptedPair = await SEA.encrypt(JSON.stringify(pair), password);
276
- const encryptedV_pair = await SEA.encrypt(JSON.stringify(v_pair), password);
277
- const encryptedS_pair = await SEA.encrypt(JSON.stringify(s_pair), password);
278
-
279
- // Convert only to get Ethereum addresses
280
- const viewingAccount = gunToEthAccount(v_pair.priv);
281
- const spendingAccount = gunToEthAccount(s_pair.priv);
282
-
283
- gun
284
- .get("gun-eth")
285
- .get("users")
286
- .get(address)
287
- .put({
288
- pair: encryptedPair,
289
- v_pair: encryptedV_pair,
290
- s_pair: encryptedS_pair,
291
- publicKeys: {
292
- viewingPublicKey: v_pair.epub, // Use SEA encryption public key
293
- viewingPublicKey: v_pair.epub, // Use SEA encryption public key
294
- spendingPublicKey: spendingAccount.publicKey, // Use Ethereum address
295
- ethViewingAddress: viewingAccount.publicKey, // Also save Ethereum address
296
- },
297
- });
298
-
299
- console.log("Encrypted pairs and public keys stored for:", address);
300
- } catch (error) {
301
- console.error("Error creating and storing encrypted pair:", error);
302
- throw error;
303
- }
304
- };
305
-
306
- /**
307
- * Retrieves and decrypts a stored key pair for a given address.
308
- * @param {string} address - The Ethereum address associated with the key pair.
309
- * @param {string} signature - The signature to use for decryption.
310
- * @returns {Promise<Object|null>} The decrypted key pair or null if retrieval fails.
311
- */
312
- Gun.chain.getAndDecryptPair = async function (address, signature) {
313
- try {
314
- const gun = this;
315
- const encryptedData = await gun
316
- .get("gun-eth")
317
- .get("users")
318
- .get(address)
319
- .get("pair")
320
- .then();
321
- if (!encryptedData) {
322
- throw new Error("No encrypted data found for this address");
323
- }
324
- const password = generatePassword(signature);
325
- const decryptedPair = await SEA.decrypt(encryptedData, password);
326
- console.log(decryptedPair);
327
- return decryptedPair;
328
- } catch (error) {
329
- console.error("Error retrieving and decrypting pair:", error);
330
- return null;
331
- }
332
- };
333
-
334
- // =============================================
335
- // PROOF OF INTEGRITY
336
- // =============================================
337
- /**
338
- * Proof of Integrity
339
- * @param {string} chain - The blockchain to use (e.g., "optimismSepolia").
340
- * @param {string} nodeId - The ID of the node to verify or write.
341
- * @param {Object} data - The data to write (if writing).
342
- * @param {Function} callback - Callback function to handle the result.
343
- * @returns {Gun} The Gun instance for chaining.
344
- */
345
- Gun.chain.proof = function (chain, nodeId, data, callback) {
346
- console.log("Proof plugin called with:", { chain, nodeId, data });
347
-
348
- if (typeof callback !== "function") {
349
- console.error("Callback must be a function");
350
- return this;
351
- }
352
-
353
- try {
354
- // Se siamo in localhost e in development, usa automaticamente la chain locale
355
- const targetChain = isLocalEnvironment() ? "localhost" : chain;
356
- const chainConfig = getAddressesForChain(targetChain);
357
-
358
- console.log(`Using ${targetChain} configuration:`, chainConfig);
359
-
360
- // Usa gli indirizzi dalla configurazione
361
- const contract = new ethers.Contract(
362
- chainConfig.PROOF_OF_INTEGRITY_ADDRESS,
363
- PROOF_OF_INTEGRITY_ABI,
364
- signer
365
- );
366
-
367
- // Funzione per verificare on-chain
368
- const verifyOnChain = async (nodeId, contentHash) => {
369
- console.log("Verifying on chain:", { nodeId, contentHash });
370
- const signer = await getSigner();
371
- const contract = new Contract(
372
- PROOF_CONTRACT_ADDRESS,
373
- PROOF_OF_INTEGRITY_ABI,
374
- signer
375
- );
376
- const [isValid, timestamp, updater] = await contract.verifyData(
377
- ethers.toUtf8Bytes(nodeId),
378
- contentHash
379
- );
380
- console.log("Verification result:", { isValid, timestamp, updater });
381
- return { isValid, timestamp, updater };
382
- };
383
-
384
- // Funzione per scrivere on-chain
385
- const writeOnChain = async (nodeId, contentHash) => {
386
- console.log("Writing on chain:", { nodeId, contentHash });
387
- const signer = await getSigner();
388
- const contract = new Contract(
389
- PROOF_CONTRACT_ADDRESS,
390
- PROOF_OF_INTEGRITY_ABI,
391
- signer
392
- );
393
- const tx = await contract.updateData(toUtf8Bytes(nodeId), contentHash);
394
- console.log("Transaction sent:", tx.hash);
395
- const receipt = await tx.wait();
396
- console.log("Transaction confirmed:", receipt);
397
- return tx;
398
- };
399
-
400
- // Funzione per ottenere l'ultimo record
401
- const getLatestRecord = async (nodeId) => {
402
- const signer = await getSigner();
403
- const contract = new Contract(
404
- PROOF_CONTRACT_ADDRESS,
405
- PROOF_OF_INTEGRITY_ABI,
406
- signer
407
- );
408
- const [contentHash, timestamp, updater] = await contract.getLatestRecord(
409
- toUtf8Bytes(nodeId)
410
- );
411
- console.log("Latest record from blockchain:", {
412
- nodeId,
413
- contentHash,
414
- timestamp,
415
- updater,
416
- });
417
- return { contentHash, timestamp, updater };
418
- };
419
-
420
- if (nodeId && !data) {
421
- // Case 1: User passes only node
422
- gun.get(nodeId).once(async (existingData) => {
423
- if (!existingData) {
424
- if (callback) callback({ err: "Node not found in GunDB" });
425
- return;
426
- }
427
-
428
- console.log("existingData", existingData);
429
-
430
- // Use stored contentHash instead of recalculating
431
- const contentHash = existingData._contentHash;
432
- console.log("contentHash", contentHash);
433
-
434
- if (!contentHash) {
435
- if (callback)
436
- callback({ err: "No content hash found for this node" });
437
- return;
438
- }
439
-
440
- try {
441
- const { isValid, timestamp, updater } = await verifyOnChain(
442
- nodeId,
443
- contentHash
444
- );
445
- const latestRecord = await getLatestRecord(nodeId);
446
-
447
- if (isValid) {
448
- if (callback)
449
- callback({
450
- ok: true,
451
- message: "Data verified on blockchain",
452
- timestamp,
453
- updater,
454
- latestRecord,
455
- });
456
- } else {
457
- if (callback)
458
- callback({
459
- ok: false,
460
- message: "Data not verified on blockchain",
461
- latestRecord,
462
- });
463
- }
464
- } catch (error) {
465
- if (callback) callback({ err: error.message });
466
- }
467
- });
468
- } else if (data && !nodeId) {
469
- // Case 2: User passes only text (data)
470
- const newNodeId = generateRandomId();
471
- const dataString = JSON.stringify(data);
472
- const contentHash = ethers.keccak256(ethers.toUtf8Bytes(dataString));
473
-
474
- gun
475
- .get(newNodeId)
476
- .put({ ...data, _contentHash: contentHash }, async (ack) => {
477
- console.log("ack", ack);
478
- if (ack.err) {
479
- if (callback) callback({ err: "Error saving data to GunDB" });
480
- return;
481
- }
482
-
483
- try {
484
- const tx = await writeOnChain(newNodeId, contentHash);
485
- if (callback)
486
- callback({
487
- ok: true,
488
- message: "Data written to GunDB and blockchain",
489
- nodeId: newNodeId,
490
- txHash: tx.hash,
491
- });
492
- } catch (error) {
493
- if (callback) callback({ err: error.message });
494
- }
495
- });
496
- } else {
497
- if (callback)
498
- callback({
499
- err: "Invalid input. Provide either nodeId or data, not both.",
500
- });
501
- }
502
-
503
- return gun;
504
- } catch (error) {
505
- callback({ err: error.message });
506
- return this;
507
- }
508
- };
509
-
510
- // =============================================
511
- // STEALTH ADDRESS CORE FUNCTIONS
512
- // =============================================
513
- /**
514
- * Converts a Gun private key to an Ethereum account.
515
- * @param {string} gunPrivateKey - The Gun private key in base64url format.
516
- * @returns {Object} An object containing the Ethereum account and public key.
517
- */
518
- Gun.chain.gunToEthAccount = function (gunPrivateKey) {
519
- return gunToEthAccount(gunPrivateKey);
520
- };
521
-
522
- /**
523
- * Generate a stealth key and related key pairs
524
- * @param {string} recipientAddress - The recipient's Ethereum address
525
- * @param {string} signature - The sender's signature to access their keys
526
- * @returns {Promise<Object>} Object containing stealth addresses and keys
527
- */
528
- Gun.chain.generateStealthAddress = async function (
529
- recipientAddress,
530
- signature
531
- ) {
532
- try {
533
- const gun = this;
534
-
535
- // Get recipient's public keys
536
- const recipientData = await gun
537
- .get("gun-eth")
538
- .get("users")
539
- .get(recipientAddress)
540
- .get("publicKeys")
541
- .then();
542
-
543
- if (
544
- !recipientData ||
545
- !recipientData.viewingPublicKey ||
546
- !recipientData.spendingPublicKey
547
- ) {
548
- throw new Error("Recipient's public keys not found");
549
- }
550
-
551
- // Get sender's keys
552
- const senderAddress = await this.verifySignature(
553
- MESSAGE_TO_SIGN,
554
- signature
555
- );
556
- const password = generatePassword(signature);
557
-
558
- const senderData = await gun
559
- .get("gun-eth")
560
- .get("users")
561
- .get(senderAddress)
562
- .then();
563
-
564
- if (!senderData || !senderData.s_pair) {
565
- throw new Error("Sender's keys not found");
566
- }
567
-
568
- // Decrypt sender's spending pair
569
- let spendingKeyPair;
570
- try {
571
- const decryptedData = await SEA.decrypt(senderData.s_pair, password);
572
- spendingKeyPair =
573
- typeof decryptedData === "string"
574
- ? JSON.parse(decryptedData)
575
- : decryptedData;
576
- } catch (error) {
577
- console.error("Error decrypting spending pair:", error);
578
- throw new Error("Unable to decrypt spending pair");
579
- }
580
-
581
- // Generate shared secret using SEA ECDH with encryption public key
582
- const sharedSecret = await SEA.secret(
583
- recipientData.viewingPublicKey,
584
- spendingKeyPair
585
- );
586
-
587
- if (!sharedSecret) {
588
- throw new Error("Unable to generate shared secret");
589
- }
590
-
591
- console.log("Generate shared secret:", sharedSecret);
592
-
593
- const { stealthAddress } = deriveStealthAddress(
594
- sharedSecret,
595
- recipientData.spendingPublicKey
596
- );
597
-
598
- return {
599
- stealthAddress,
600
- senderPublicKey: spendingKeyPair.epub, // Use encryption public key
601
- spendingPublicKey: recipientData.spendingPublicKey,
602
- };
603
- } catch (error) {
604
- console.error("Error generating stealth address:", error);
605
- throw error;
606
- }
607
- };
608
-
609
- /**
610
- * Publish public keys needed to receive stealth payments
611
- * @param {string} signature - The signature to authenticate the user
612
- * @returns {Promise<void>}
613
- */
614
- Gun.chain.publishStealthKeys = async function (signature) {
615
- try {
616
- const gun = this;
617
- const address = await this.verifySignature(MESSAGE_TO_SIGN, signature);
618
- const password = generatePassword(signature);
619
-
620
- // Get encrypted key pairs
621
- const encryptedData = await gun
622
- .get("gun-eth")
623
- .get("users")
624
- .get(address)
625
- .then();
626
-
627
- if (!encryptedData || !encryptedData.v_pair || !encryptedData.s_pair) {
628
- throw new Error("Keys not found");
629
- }
630
-
631
- // Decrypt viewing and spending pairs
632
- const viewingKeyPair = JSON.parse(
633
- await SEA.decrypt(encryptedData.v_pair, password)
634
- );
635
- const spendingKeyPair = JSON.parse(
636
- await SEA.decrypt(encryptedData.s_pair, password)
637
- );
638
-
639
- const viewingAccount = gunToEthAccount(viewingKeyPair.priv);
640
- const spendingAccount = gunToEthAccount(spendingKeyPair.priv);
641
-
642
- // Publish only public keys
643
- gun.get("gun-eth").get("users").get(address).get("publicKeys").put({
644
- viewingPublicKey: viewingAccount.publicKey,
645
- spendingPublicKey: spendingAccount.publicKey,
646
- });
647
-
648
- console.log("Stealth public keys published successfully");
649
- } catch (error) {
650
- console.error("Error publishing stealth keys:", error);
651
- throw error;
652
- }
653
- };
654
-
655
- // =============================================
656
- // STEALTH PAYMENT FUNCTIONS
657
- // =============================================
658
- /**
659
- * Recover funds from a stealth address
660
- * @param {string} stealthAddress - The stealth address to recover funds from
661
- * @param {string} senderPublicKey - The sender's public key used to generate the address
662
- * @param {string} signature - The signature to decrypt private keys
663
- * @returns {Promise<Object>} Object containing wallet to access funds
664
- */
665
- Gun.chain.recoverStealthFunds = async function (
666
- stealthAddress,
667
- senderPublicKey,
668
- signature,
669
- spendingPublicKey
670
- ) {
671
- try {
672
- const gun = this;
673
- const password = generatePassword(signature);
674
-
675
- // Get own key pairs
676
- const myAddress = await this.verifySignature(MESSAGE_TO_SIGN, signature);
677
- const encryptedData = await gun
678
- .get("gun-eth")
679
- .get("users")
680
- .get(myAddress)
681
- .then();
682
-
683
- if (!encryptedData || !encryptedData.v_pair || !encryptedData.s_pair) {
684
- throw new Error("Keys not found");
685
- }
686
-
687
- // Decrypt viewing and spending pairs
688
- let viewingKeyPair;
689
- try {
690
- const decryptedViewingData = await SEA.decrypt(
691
- encryptedData.v_pair,
692
- password
693
- );
694
- viewingKeyPair =
695
- typeof decryptedViewingData === "string"
696
- ? JSON.parse(decryptedViewingData)
697
- : decryptedViewingData;
698
- } catch (error) {
699
- console.error("Error decrypting keys:", error);
700
- throw new Error("Unable to decrypt keys");
701
- }
702
-
703
- // Generate shared secret using SEA ECDH
704
- const sharedSecret = await SEA.secret(senderPublicKey, viewingKeyPair);
705
-
706
- if (!sharedSecret) {
707
- throw new Error("Unable to generate shared secret");
708
- }
709
-
710
- console.log("Recover shared secret:", sharedSecret);
711
-
712
- const { wallet, stealthAddress: recoveredAddress } = deriveStealthAddress(
713
- sharedSecret,
714
- spendingPublicKey
715
- );
716
-
717
- // Verify address matches
718
- if (recoveredAddress.toLowerCase() !== stealthAddress.toLowerCase()) {
719
- console.error("Mismatch:", {
720
- recovered: recoveredAddress,
721
- expected: stealthAddress,
722
- sharedSecret,
723
- });
724
- throw new Error("Recovered stealth address does not match");
725
- }
726
-
727
- return {
728
- wallet,
729
- address: recoveredAddress,
730
- };
731
- } catch (error) {
732
- console.error("Error recovering stealth funds:", error);
733
- throw error;
734
- }
735
- };
736
-
737
- /**
738
- * Announce a stealth payment
739
- * @param {string} stealthAddress - The generated stealth address
740
- * @param {string} senderPublicKey - The sender's public key
741
- * @param {string} spendingPublicKey - The spending public key
742
- * @param {string} signature - The sender's signature
743
- * @returns {Promise<void>}
744
- */
745
- Gun.chain.announceStealthPayment = async function (
746
- stealthAddress,
747
- senderPublicKey,
748
- spendingPublicKey,
749
- signature,
750
- options = { onChain: false, chain: "optimismSepolia" }
751
- ) {
752
- try {
753
- const gun = this;
754
- const senderAddress = await this.verifySignature(
755
- MESSAGE_TO_SIGN,
756
- signature
757
- );
758
-
759
- if (options.onChain) {
760
- // On-chain announcement
761
- const signer = await getSigner();
762
- const chainConfig = getAddressesForChain(options.chain);
763
- const contractAddress = chainConfig.STEALTH_ANNOUNCER_ADDRESS;
764
-
765
- console.log("Using contract address:", contractAddress);
766
-
767
- const contract = new Contract(
768
- contractAddress,
769
- STEALTH_ANNOUNCER_ABI,
770
- signer
771
- );
772
-
773
- // Get dev fee from contract
774
- const devFee = await contract.devFee();
775
- console.log("Dev fee:", devFee.toString());
776
-
777
- // Call contract
778
- const tx = await contract.announcePayment(
779
- senderPublicKey,
780
- spendingPublicKey,
781
- stealthAddress,
782
- { value: devFee }
783
- );
784
-
785
- console.log("Transaction sent:", tx.hash);
786
- const receipt = await tx.wait();
787
- console.log("Transaction confirmed:", receipt.hash);
788
-
789
- console.log("Stealth payment announced on-chain (dev fee paid)");
790
- } else {
791
- // Off-chain announcement (GunDB)
792
- gun.get("gun-eth").get("stealth-payments").set({
793
- stealthAddress,
794
- senderAddress,
795
- senderPublicKey,
796
- spendingPublicKey,
797
- timestamp: Date.now(),
798
- });
799
- console.log("Stealth payment announced off-chain");
800
- }
801
- } catch (error) {
802
- console.error("Error announcing stealth payment:", error);
803
- console.error("Error details:", error.stack);
804
- throw error;
805
- }
806
- };
807
-
808
- /**
809
- * Get all stealth payments for an address
810
- * @param {string} signature - The signature to authenticate the user
811
- * @returns {Promise<Array>} List of stealth payments
812
- */
813
- Gun.chain.getStealthPayments = async function (
814
- signature,
815
- options = { source: "both" }
816
- ) {
817
- try {
818
- const payments = [];
819
-
820
- if (options.source === "onChain" || options.source === "both") {
821
- const signer = await getSigner();
822
- const chainConfig = getAddressesForChain(
823
- options.chain || "optimismSepolia"
824
- );
825
- const contractAddress = chainConfig.STEALTH_ANNOUNCER_ADDRESS;
826
-
827
- const contract = new Contract(
828
- contractAddress,
829
- STEALTH_ANNOUNCER_ABI,
830
- signer
831
- );
832
-
833
- try {
834
- // Get total number of announcements
835
- const totalAnnouncements = await contract.getAnnouncementsCount();
836
- const totalCount = Number(totalAnnouncements.toString());
837
- console.log("Total on-chain announcements:", totalCount);
838
-
839
- if (totalCount > 0) {
840
- // Get announcements in batches of 100
841
- const batchSize = 100;
842
- const lastIndex = totalCount - 1;
843
-
844
- for (let i = 0; i <= lastIndex; i += batchSize) {
845
- const toIndex = Math.min(i + batchSize - 1, lastIndex);
846
- const batch = await contract.getAnnouncementsInRange(i, toIndex);
847
-
848
- // For each announcement, try to decrypt
849
- for (const announcement of batch) {
850
- try {
851
- // Verify announcement is valid
852
- if (
853
- !announcement ||
854
- !announcement.stealthAddress ||
855
- !announcement.senderPublicKey ||
856
- !announcement.spendingPublicKey
857
- ) {
858
- console.log("Invalid announcement:", announcement);
859
- continue;
860
- }
861
-
862
- // Try to recover funds to verify if announcement is for us
863
- const recoveredWallet = await this.recoverStealthFunds(
864
- announcement.stealthAddress,
865
- announcement.senderPublicKey,
866
- signature,
867
- announcement.spendingPublicKey
868
- );
869
-
870
- // If no errors thrown, announcement is for us
871
- payments.push({
872
- stealthAddress: announcement.stealthAddress,
873
- senderPublicKey: announcement.senderPublicKey,
874
- spendingPublicKey: announcement.spendingPublicKey,
875
- timestamp: Number(announcement.timestamp),
876
- source: "onChain",
877
- wallet: recoveredWallet,
878
- });
879
- } catch (e) {
880
- // Not for us, continue
881
- console.log(
882
- `Announcement not for us: ${announcement.stealthAddress}`
883
- );
884
- continue;
885
- }
886
- }
887
- }
888
- }
889
- } catch (error) {
890
- console.error("Error retrieving on-chain announcements:", error);
891
- }
892
- }
893
-
894
- if (options.source === "offChain" || options.source === "both") {
895
- // Get off-chain payments
896
- const gun = this;
897
- const offChainPayments = await new Promise((resolve) => {
898
- const p = [];
899
- gun
900
- .get("gun-eth")
901
- .get("stealth-payments")
902
- .get(recipientAddress)
903
- .map()
904
- .once((payment, id) => {
905
- if (payment?.stealthAddress) {
906
- p.push({ ...payment, id, source: "offChain" });
907
- }
908
- });
909
- setTimeout(() => resolve(p), 2000);
910
- });
911
-
912
- payments.push(...offChainPayments);
913
- }
914
-
915
- console.log(`Found ${payments.length} stealth payments`);
916
- return payments;
917
- } catch (error) {
918
- console.error("Error retrieving stealth payments:", error);
919
- throw error;
920
- }
921
- };
922
-
923
- /**
924
- * Clean up old stealth payments
925
- * @param {string} recipientAddress - The recipient's address
926
- * @returns {Promise<void>}
927
- */
928
- Gun.chain.cleanStealthPayments = async function (recipientAddress) {
929
- try {
930
- const gun = this;
931
- const payments = await gun
932
- .get("gun-eth")
933
- .get("stealth-payments")
934
- .get(recipientAddress)
935
- .map()
936
- .once()
937
- .then();
938
-
939
- // Remove empty or invalid nodes
940
- if (payments) {
941
- Object.keys(payments).forEach(async (key) => {
942
- const payment = payments[key];
943
- if (
944
- !payment ||
945
- !payment.stealthAddress ||
946
- !payment.senderPublicKey ||
947
- !payment.spendingPublicKey
948
- ) {
949
- await gun
950
- .get("gun-eth")
951
- .get("stealth-payments")
952
- .get(recipientAddress)
953
- .get(key)
954
- .put(null);
955
- }
956
- });
957
- }
958
- } catch (error) {
959
- console.error("Error cleaning stealth payments:", error);
960
- }
961
- };
962
-
963
- // =============================================
964
- // EXPORTS
965
- // =============================================
966
-
967
- // Crea una classe GunEth che contiene tutti i metodi e le utility
968
- export class GunEth {
969
- // Static utility methods
970
- static generateRandomId = generateRandomId;
971
- static generatePassword = generatePassword;
972
- static gunToEthAccount = gunToEthAccount;
973
- static getSigner = getSigner;
974
- static deriveStealthAddress = deriveStealthAddress;
975
-
976
- // Chain methods
977
- static chainMethods = {
978
- setSigner: Gun.chain.setSigner,
979
- getSigner: Gun.chain.getSigner,
980
- verifySignature: Gun.chain.verifySignature,
981
- generatePassword: Gun.chain.generatePassword,
982
- createSignature: Gun.chain.createSignature,
983
- createAndStoreEncryptedPair: Gun.chain.createAndStoreEncryptedPair,
984
- getAndDecryptPair: Gun.chain.getAndDecryptPair,
985
- proof: Gun.chain.proof,
986
- gunToEthAccount: Gun.chain.gunToEthAccount,
987
- generateStealthAddress: Gun.chain.generateStealthAddress,
988
- publishStealthKeys: Gun.chain.publishStealthKeys,
989
- recoverStealthFunds: Gun.chain.recoverStealthFunds,
990
- announceStealthPayment: Gun.chain.announceStealthPayment,
991
- getStealthPayments: Gun.chain.getStealthPayments,
992
- cleanStealthPayments: Gun.chain.cleanStealthPayments,
993
- };
994
-
995
- // Constants
996
- static MESSAGE_TO_SIGN = MESSAGE_TO_SIGN;
997
- static PROOF_CONTRACT_ADDRESS = PROOF_CONTRACT_ADDRESS;
998
- static LOCAL_CONFIG = LOCAL_CONFIG;
999
- }