gun-eth 1.4.23 → 1.4.24

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1340 @@
1
+ import Gun from 'gun';
2
+ export { default } from 'gun';
3
+ import SEA from 'gun/sea.js';
4
+ import { ethers } from 'ethers';
5
+ import require$$0$1 from 'url';
6
+ import require$$1 from 'path';
7
+ import require$$2 from 'fs';
8
+
9
+ var STEALTH_ANNOUNCER_ADDRESS$1 = "";
10
+ var PROOF_OF_INTEGRITY_ADDRESS = "";
11
+ var require$$0 = {
12
+ STEALTH_ANNOUNCER_ADDRESS: STEALTH_ANNOUNCER_ADDRESS$1,
13
+ PROOF_OF_INTEGRITY_ADDRESS: PROOF_OF_INTEGRITY_ADDRESS
14
+ };
15
+
16
+ let contractAddresses = {
17
+ PROOF_OF_INTEGRITY_ADDRESS: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
18
+ STEALTH_ANNOUNCER_ADDRESS: "0x5FbDB2315678afecb367f032d93F642f64180aa3"
19
+ };
20
+
21
+ if (typeof window === 'undefined') {
22
+ const { fileURLToPath } = require$$0$1;
23
+ const { dirname } = require$$1;
24
+ const { readFileSync } = require$$2;
25
+ const { join } = require$$1;
26
+
27
+ try {
28
+ const __filename = fileURLToPath(import.meta.url);
29
+ const __dirname = dirname(__filename);
30
+ const rawdata = readFileSync(join(__dirname, 'contract-address.json'), 'utf8');
31
+ contractAddresses = JSON.parse(rawdata);
32
+ console.log("Loaded contract addresses:", contractAddresses);
33
+ } catch (error) {
34
+ console.warn("Warning: contract-address.json not found or invalid");
35
+ }
36
+ }
37
+
38
+ const LOCAL_CONFIG = {
39
+ CHAIN_ID: 1337,
40
+ PROOF_OF_INTEGRITY_ADDRESS: contractAddresses.PROOF_OF_INTEGRITY_ADDRESS,
41
+ STEALTH_ANNOUNCER_ADDRESS: contractAddresses.STEALTH_ANNOUNCER_ADDRESS,
42
+ RPC_URL: "http://127.0.0.1:8545",
43
+ GUN_PEER: "http://localhost:8765/gun"
44
+ };
45
+
46
+ // Indirizzi di produzione per diverse chain
47
+ const CHAIN_CONFIG = {
48
+ optimismSepolia: {
49
+ STEALTH_ANNOUNCER_ADDRESS: "0xD0F2e9DA59d2DFECFdE67CcF17300BB6093A72f8",
50
+ PROOF_OF_INTEGRITY_ADDRESS: "0x...",
51
+ RPC_URL: "https://sepolia.optimism.io",
52
+ CHAIN_ID: 11155420
53
+ },
54
+ arbitrumSepolia: {
55
+ STEALTH_ANNOUNCER_ADDRESS: "0x...",
56
+ PROOF_OF_INTEGRITY_ADDRESS: "0x...",
57
+ RPC_URL: "https://sepolia-rollup.arbitrum.io/rpc",
58
+ CHAIN_ID: 421614
59
+ },
60
+ localhost: {
61
+ RPC_URL: "http://127.0.0.1:8545",
62
+ CHAIN_ID: 1337
63
+ }
64
+ };
65
+
66
+ // Funzione per ottenere gli indirizzi corretti
67
+ function getAddressesForChain(chainName) {
68
+ let config;
69
+
70
+ // Se è localhost, prova a caricare gli indirizzi locali
71
+ if (chainName === 'localhost') {
72
+ try {
73
+ // Carica gli indirizzi dal file generato dal deploy locale
74
+ const localAddresses = require$$0;
75
+ config = {
76
+ ...CHAIN_CONFIG.localhost,
77
+ ...localAddresses
78
+ };
79
+ console.log("Using local addresses:", config);
80
+ return config;
81
+ } catch (err) {
82
+ console.warn('No local addresses found');
83
+ throw new Error('No local addresses found. Did you run local deployment?');
84
+ }
85
+ }
86
+
87
+ // Altrimenti usa gli indirizzi di produzione
88
+ config = CHAIN_CONFIG[chainName];
89
+ if (!config) {
90
+ throw new Error(`Chain ${chainName} not supported. Supported chains: ${Object.keys(CHAIN_CONFIG).join(', ')}`);
91
+ }
92
+
93
+ return config;
94
+ }
95
+
96
+ const STEALTH_ANNOUNCER_ABI = [
97
+ {
98
+ "inputs": [
99
+ {
100
+ "internalType": "address",
101
+ "name": "_devAddress",
102
+ "type": "address"
103
+ }
104
+ ],
105
+ "stateMutability": "nonpayable",
106
+ "type": "constructor"
107
+ },
108
+ {
109
+ "anonymous": false,
110
+ "inputs": [
111
+ {
112
+ "internalType": "string",
113
+ "name": "senderPublicKey",
114
+ "type": "string"
115
+ },
116
+ {
117
+ "internalType": "string",
118
+ "name": "spendingPublicKey",
119
+ "type": "string"
120
+ },
121
+ {
122
+ "internalType": "address",
123
+ "name": "stealthAddress",
124
+ "type": "address"
125
+ },
126
+ {
127
+ "internalType": "uint256",
128
+ "name": "timestamp",
129
+ "type": "uint256"
130
+ }
131
+ ],
132
+ "name": "StealthPaymentAnnounced",
133
+ "type": "event"
134
+ },
135
+ {
136
+ "anonymous": false,
137
+ "inputs": [
138
+ {
139
+ "internalType": "address",
140
+ "name": "newAddress",
141
+ "type": "address"
142
+ }
143
+ ],
144
+ "name": "DevAddressUpdated",
145
+ "type": "event"
146
+ },
147
+ {
148
+ "anonymous": false,
149
+ "inputs": [
150
+ {
151
+ "internalType": "uint256",
152
+ "name": "newFee",
153
+ "type": "uint256"
154
+ }
155
+ ],
156
+ "name": "DevFeeUpdated",
157
+ "type": "event"
158
+ },
159
+ {
160
+ "inputs": [
161
+ {
162
+ "internalType": "string",
163
+ "name": "senderPublicKey",
164
+ "type": "string"
165
+ },
166
+ {
167
+ "internalType": "string",
168
+ "name": "spendingPublicKey",
169
+ "type": "string"
170
+ },
171
+ {
172
+ "internalType": "address",
173
+ "name": "stealthAddress",
174
+ "type": "address"
175
+ }
176
+ ],
177
+ "name": "announcePayment",
178
+ "outputs": [],
179
+ "stateMutability": "payable",
180
+ "type": "function"
181
+ },
182
+ {
183
+ "inputs": [],
184
+ "name": "devAddress",
185
+ "outputs": [
186
+ {
187
+ "internalType": "address",
188
+ "name": "",
189
+ "type": "address"
190
+ }
191
+ ],
192
+ "stateMutability": "view",
193
+ "type": "function"
194
+ },
195
+ {
196
+ "inputs": [],
197
+ "name": "devFee",
198
+ "outputs": [
199
+ {
200
+ "internalType": "uint256",
201
+ "name": "",
202
+ "type": "uint256"
203
+ }
204
+ ],
205
+ "stateMutability": "view",
206
+ "type": "function"
207
+ },
208
+ {
209
+ "inputs": [],
210
+ "name": "getAnnouncementsCount",
211
+ "outputs": [
212
+ {
213
+ "internalType": "uint256",
214
+ "name": "",
215
+ "type": "uint256"
216
+ }
217
+ ],
218
+ "stateMutability": "view",
219
+ "type": "function"
220
+ },
221
+ {
222
+ "inputs": [
223
+ {
224
+ "internalType": "uint256",
225
+ "name": "fromIndex",
226
+ "type": "uint256"
227
+ },
228
+ {
229
+ "internalType": "uint256",
230
+ "name": "toIndex",
231
+ "type": "uint256"
232
+ }
233
+ ],
234
+ "name": "getAnnouncementsInRange",
235
+ "outputs": [
236
+ {
237
+ "components": [
238
+ {
239
+ "internalType": "string",
240
+ "name": "senderPublicKey",
241
+ "type": "string"
242
+ },
243
+ {
244
+ "internalType": "string",
245
+ "name": "spendingPublicKey",
246
+ "type": "string"
247
+ },
248
+ {
249
+ "internalType": "address",
250
+ "name": "stealthAddress",
251
+ "type": "address"
252
+ },
253
+ {
254
+ "internalType": "uint256",
255
+ "name": "timestamp",
256
+ "type": "uint256"
257
+ }
258
+ ],
259
+ "internalType": "struct StealthAnnouncer.StealthAnnouncement[]",
260
+ "name": "",
261
+ "type": "tuple[]"
262
+ }
263
+ ],
264
+ "stateMutability": "view",
265
+ "type": "function"
266
+ },
267
+ {
268
+ "inputs": [
269
+ {
270
+ "internalType": "uint256",
271
+ "name": "_newFee",
272
+ "type": "uint256"
273
+ }
274
+ ],
275
+ "name": "updateDevFee",
276
+ "outputs": [],
277
+ "stateMutability": "nonpayable",
278
+ "type": "function"
279
+ },
280
+ {
281
+ "inputs": [
282
+ {
283
+ "internalType": "address",
284
+ "name": "_newAddress",
285
+ "type": "address"
286
+ }
287
+ ],
288
+ "name": "updateDevAddress",
289
+ "outputs": [],
290
+ "stateMutability": "nonpayable",
291
+ "type": "function"
292
+ },
293
+ {
294
+ "inputs": [],
295
+ "name": "withdrawStuckETH",
296
+ "outputs": [],
297
+ "stateMutability": "nonpayable",
298
+ "type": "function"
299
+ }
300
+ ];
301
+
302
+ const PROOF_OF_INTEGRITY_ABI = [
303
+ {
304
+ "inputs": [
305
+ {
306
+ "internalType": "bytes[]",
307
+ "name": "nodeIds",
308
+ "type": "bytes[]"
309
+ },
310
+ {
311
+ "internalType": "bytes32[]",
312
+ "name": "contentHashes",
313
+ "type": "bytes32[]"
314
+ }
315
+ ],
316
+ "name": "batchUpdateData",
317
+ "outputs": [],
318
+ "stateMutability": "nonpayable",
319
+ "type": "function"
320
+ },
321
+ {
322
+ "anonymous": false,
323
+ "inputs": [
324
+ {
325
+ "indexed": true,
326
+ "internalType": "bytes",
327
+ "name": "nodeId",
328
+ "type": "bytes"
329
+ },
330
+ {
331
+ "indexed": false,
332
+ "internalType": "bytes32",
333
+ "name": "contentHash",
334
+ "type": "bytes32"
335
+ },
336
+ {
337
+ "indexed": false,
338
+ "internalType": "address",
339
+ "name": "updater",
340
+ "type": "address"
341
+ }
342
+ ],
343
+ "name": "DataUpdated",
344
+ "type": "event"
345
+ },
346
+ {
347
+ "inputs": [
348
+ {
349
+ "internalType": "bytes",
350
+ "name": "nodeId",
351
+ "type": "bytes"
352
+ }
353
+ ],
354
+ "name": "getLatestRecord",
355
+ "outputs": [
356
+ {
357
+ "internalType": "bytes32",
358
+ "name": "",
359
+ "type": "bytes32"
360
+ },
361
+ {
362
+ "internalType": "uint256",
363
+ "name": "",
364
+ "type": "uint256"
365
+ },
366
+ {
367
+ "internalType": "address",
368
+ "name": "",
369
+ "type": "address"
370
+ }
371
+ ],
372
+ "stateMutability": "view",
373
+ "type": "function"
374
+ },
375
+ {
376
+ "inputs": [
377
+ {
378
+ "internalType": "bytes",
379
+ "name": "nodeId",
380
+ "type": "bytes"
381
+ },
382
+ {
383
+ "internalType": "bytes32",
384
+ "name": "contentHash",
385
+ "type": "bytes32"
386
+ }
387
+ ],
388
+ "name": "updateData",
389
+ "outputs": [],
390
+ "stateMutability": "nonpayable",
391
+ "type": "function"
392
+ },
393
+ {
394
+ "inputs": [
395
+ {
396
+ "internalType": "bytes",
397
+ "name": "nodeId",
398
+ "type": "bytes"
399
+ },
400
+ {
401
+ "internalType": "bytes32",
402
+ "name": "contentHash",
403
+ "type": "bytes32"
404
+ }
405
+ ],
406
+ "name": "verifyData",
407
+ "outputs": [
408
+ {
409
+ "internalType": "bool",
410
+ "name": "",
411
+ "type": "bool"
412
+ },
413
+ {
414
+ "internalType": "uint256",
415
+ "name": "",
416
+ "type": "uint256"
417
+ },
418
+ {
419
+ "internalType": "address",
420
+ "name": "",
421
+ "type": "address"
422
+ }
423
+ ],
424
+ "stateMutability": "view",
425
+ "type": "function"
426
+ }
427
+ ];
428
+
429
+ // =============================================
430
+ // IMPORTS AND GLOBAL VARIABLES
431
+ // =============================================
432
+
433
+ let rpcUrl = "";
434
+ let privateKey = "";
435
+
436
+ const MESSAGE_TO_SIGN = "Access GunDB with Ethereum";
437
+
438
+ // =============================================
439
+ // UTILITY FUNCTIONS
440
+ // =============================================
441
+ /**
442
+ * Generates a random node ID for GunDB
443
+ * @returns {string} A random hexadecimal string
444
+ */
445
+ function generateRandomId() {
446
+ return ethers.hexlify(ethers.randomBytes(32)).slice(2);
447
+ }
448
+
449
+ /**
450
+ * Generates a password from a signature.
451
+ * @param {string} signature - The signature to derive the password from.
452
+ * @returns {string|null} The generated password or null if generation fails.
453
+ */
454
+ function generatePassword(signature) {
455
+ try {
456
+ const hexSignature = ethers.hexlify(signature);
457
+ const hash = ethers.keccak256(hexSignature);
458
+ console.log("Generated password:", hash);
459
+ return hash;
460
+ } catch (error) {
461
+ console.error("Error generating password:", error);
462
+ return null;
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Converts a Gun private key to an Ethereum account.
468
+ * @param {string} gunPrivateKey - The Gun private key in base64url format.
469
+ * @returns {Object} An object containing the Ethereum account and public key.
470
+ */
471
+ function gunToEthAccount(gunPrivateKey) {
472
+ // Function to convert base64url to hex
473
+ const base64UrlToHex = (base64url) => {
474
+ const padding = "=".repeat((4 - (base64url.length % 4)) % 4);
475
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
476
+ const binary = atob(base64);
477
+ return Array.from(binary, (char) =>
478
+ char.charCodeAt(0).toString(16).padStart(2, "0")
479
+ ).join("");
480
+ };
481
+
482
+ // Convert Gun private key to hex format
483
+ const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
484
+
485
+ // Create an Ethereum wallet from the private key
486
+ const wallet = new ethers.Wallet(hexPrivateKey);
487
+
488
+ // Get the public address (public key)
489
+ const publicKey = wallet.address;
490
+
491
+ return {
492
+ account: wallet,
493
+ publicKey: publicKey,
494
+ privateKey: hexPrivateKey,
495
+ };
496
+ }
497
+
498
+ /**
499
+ * Gets an Ethereum signer based on current configuration
500
+ * @returns {Promise<ethers.Signer>} The configured signer
501
+ * @throws {Error} If no valid provider is found
502
+ */
503
+ const getSigner = async () => {
504
+ if (rpcUrl && privateKey) {
505
+ // Standalone mode with local provider
506
+ const provider = new ethers.JsonRpcProvider(rpcUrl, {
507
+ chainId: LOCAL_CONFIG.CHAIN_ID,
508
+ name: "localhost"
509
+ });
510
+ return new ethers.Wallet(privateKey, provider);
511
+ } else if (
512
+ typeof window !== "undefined" &&
513
+ typeof window.ethereum !== "undefined"
514
+ ) {
515
+ // Browser mode
516
+ await window.ethereum.request({ method: "eth_requestAccounts" });
517
+ const provider = new ethers.BrowserProvider(window.ethereum);
518
+ return provider.getSigner();
519
+ } else {
520
+ throw new Error("No valid Ethereum provider found");
521
+ }
522
+ };
523
+
524
+ /**
525
+ * Utility function to generate stealth address
526
+ * @param {string} sharedSecret - The shared secret
527
+ * @param {string} spendingPublicKey - The spending public key
528
+ * @returns {Object} The stealth address and private key
529
+ */
530
+ function deriveStealthAddress(sharedSecret, spendingPublicKey) {
531
+ try {
532
+ // Convert shared secret to bytes
533
+ const sharedSecretBytes = Buffer.from(sharedSecret, 'base64');
534
+
535
+ // Generate stealth private key using shared secret and spending public key
536
+ const stealthPrivateKey = ethers.keccak256(
537
+ ethers.concat([
538
+ sharedSecretBytes,
539
+ ethers.getBytes(spendingPublicKey)
540
+ ])
541
+ );
542
+
543
+ // Create stealth wallet
544
+ const stealthWallet = new ethers.Wallet(stealthPrivateKey);
545
+
546
+ console.log("Debug deriveStealthAddress:", {
547
+ sharedSecretHex: ethers.hexlify(sharedSecretBytes),
548
+ spendingPublicKey,
549
+ stealthPrivateKey,
550
+ stealthAddress: stealthWallet.address
551
+ });
552
+
553
+ return {
554
+ stealthPrivateKey,
555
+ stealthAddress: stealthWallet.address,
556
+ wallet: stealthWallet
557
+ };
558
+ } catch (error) {
559
+ console.error("Error in deriveStealthAddress:", error);
560
+ throw error;
561
+ }
562
+ }
563
+
564
+ // =============================================
565
+ // BASIC GUN-ETH CHAIN METHODS
566
+ // =============================================
567
+
568
+ // Set the message to sign
569
+ Gun.chain.MESSAGE_TO_SIGN = MESSAGE_TO_SIGN;
570
+
571
+ /**
572
+ * Sets standalone configuration for Gun.
573
+ * @param {string} newRpcUrl - The new RPC URL.
574
+ * @param {string} newPrivateKey - The new private key.
575
+ * @returns {Gun} The Gun instance for chaining.
576
+ */
577
+ Gun.chain.setSigner = function (newRpcUrl, newPrivateKey) {
578
+ rpcUrl = newRpcUrl;
579
+ privateKey = newPrivateKey;
580
+ console.log("Standalone configuration set");
581
+ return this;
582
+ };
583
+
584
+ Gun.chain.getSigner = getSigner();
585
+
586
+ /**
587
+ * Verifies an Ethereum signature.
588
+ * @param {string} message - The original message that was signed.
589
+ * @param {string} signature - The signature to verify.
590
+ * @returns {Promise<string|null>} The recovered address or null if verification fails.
591
+ */
592
+ Gun.chain.verifySignature = async function (message, signature) {
593
+ try {
594
+ const recoveredAddress = ethers.verifyMessage(message, signature);
595
+ return recoveredAddress;
596
+ } catch (error) {
597
+ console.error("Error verifying signature:", error);
598
+ return null;
599
+ }
600
+ };
601
+
602
+ /**
603
+ * Generates a password from a signature.
604
+ * @param {string} signature - The signature to derive the password from.
605
+ * @returns {string|null} The generated password or null if generation fails.
606
+ */
607
+ Gun.chain.generatePassword = function (signature) {
608
+ return generatePassword(signature);
609
+ };
610
+
611
+ /**
612
+ * Creates an Ethereum signature for a given message.
613
+ * @param {string} message - The message to sign.
614
+ * @returns {Promise<string|null>} The signature or null if signing fails.
615
+ */
616
+ Gun.chain.createSignature = async function (message) {
617
+ try {
618
+ // Check if message matches MESSAGE_TO_SIGN
619
+ if (message !== MESSAGE_TO_SIGN) {
620
+ throw new Error("Invalid message, valid message is: " + MESSAGE_TO_SIGN);
621
+ }
622
+ const signer = await getSigner();
623
+ const signature = await signer.signMessage(message);
624
+ console.log("Signature created:", signature);
625
+ return signature;
626
+ } catch (error) {
627
+ console.error("Error creating signature:", error);
628
+ return null;
629
+ }
630
+ };
631
+
632
+ // =============================================
633
+ // KEY PAIR MANAGEMENT
634
+ // =============================================
635
+ /**
636
+ * Creates and stores an encrypted key pair for a given address.
637
+ * @param {string} address - The Ethereum address to associate with the key pair.
638
+ * @param {string} signature - The signature to use for encryption.
639
+ * @returns {Promise<void>}
640
+ */
641
+ Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
642
+ try {
643
+ const gun = this;
644
+ const pair = await SEA.pair();
645
+ const v_pair = await SEA.pair();
646
+ const s_pair = await SEA.pair();
647
+ const password = generatePassword(signature);
648
+
649
+ // Save original SEA pairs
650
+ const encryptedPair = await SEA.encrypt(JSON.stringify(pair), password);
651
+ const encryptedV_pair = await SEA.encrypt(JSON.stringify(v_pair), password);
652
+ const encryptedS_pair = await SEA.encrypt(JSON.stringify(s_pair), password);
653
+
654
+ // Convert only to get Ethereum addresses
655
+ const viewingAccount = gunToEthAccount(v_pair.priv);
656
+ const spendingAccount = gunToEthAccount(s_pair.priv);
657
+
658
+ gun.get("gun-eth").get("users").get(address).put({
659
+ pair: encryptedPair,
660
+ v_pair: encryptedV_pair,
661
+ s_pair: encryptedS_pair,
662
+ publicKeys: {
663
+ viewingPublicKey: v_pair.epub, // Use SEA encryption public key
664
+ viewingPublicKey: v_pair.epub, // Use SEA encryption public key
665
+ spendingPublicKey: spendingAccount.publicKey, // Use Ethereum address
666
+ ethViewingAddress: viewingAccount.publicKey // Also save Ethereum address
667
+ }
668
+ });
669
+
670
+ console.log("Encrypted pairs and public keys stored for:", address);
671
+ } catch (error) {
672
+ console.error("Error creating and storing encrypted pair:", error);
673
+ throw error;
674
+ }
675
+ };
676
+
677
+ /**
678
+ * Retrieves and decrypts a stored key pair for a given address.
679
+ * @param {string} address - The Ethereum address associated with the key pair.
680
+ * @param {string} signature - The signature to use for decryption.
681
+ * @returns {Promise<Object|null>} The decrypted key pair or null if retrieval fails.
682
+ */
683
+ Gun.chain.getAndDecryptPair = async function (address, signature) {
684
+ try {
685
+ const gun = this;
686
+ const encryptedData = await gun
687
+ .get("gun-eth")
688
+ .get("users")
689
+ .get(address)
690
+ .get("pair")
691
+ .then();
692
+ if (!encryptedData) {
693
+ throw new Error("No encrypted data found for this address");
694
+ }
695
+ const password = generatePassword(signature);
696
+ const decryptedPair = await SEA.decrypt(encryptedData, password);
697
+ console.log(decryptedPair);
698
+ return decryptedPair;
699
+ } catch (error) {
700
+ console.error("Error retrieving and decrypting pair:", error);
701
+ return null;
702
+ }
703
+ };
704
+
705
+ // =============================================
706
+ // PROOF OF INTEGRITY
707
+ // =============================================
708
+ /**
709
+ * Proof of Integrity
710
+ * @param {string} chain - The blockchain to use (e.g., "optimismSepolia").
711
+ * @param {string} nodeId - The ID of the node to verify or write.
712
+ * @param {Object} data - The data to write (if writing).
713
+ * @param {Function} callback - Callback function to handle the result.
714
+ * @returns {Gun} The Gun instance for chaining.
715
+ */
716
+ Gun.chain.proof = function (chain, nodeId, data, callback) {
717
+ console.log("Proof plugin called with:", { chain, nodeId, data });
718
+
719
+ if (typeof callback !== "function") {
720
+ console.error("Callback must be a function");
721
+ return this;
722
+ }
723
+
724
+ try {
725
+ // Usa getAddressesForChain per ottenere la configurazione corretta
726
+ const chainConfig = getAddressesForChain(chain);
727
+ console.log(`Using ${chain} configuration:`, chainConfig);
728
+
729
+ // Usa gli indirizzi dalla configurazione
730
+ const contract = new ethers.Contract(
731
+ chainConfig.PROOF_OF_INTEGRITY_ADDRESS,
732
+ PROOF_OF_INTEGRITY_ABI,
733
+ signer
734
+ );
735
+
736
+ // Funzione per verificare on-chain
737
+ const verifyOnChain = async (nodeId, contentHash) => {
738
+ console.log("Verifying on chain:", { nodeId, contentHash });
739
+ const signer = await getSigner();
740
+ const contract = new ethers.Contract(
741
+ PROOF_CONTRACT_ADDRESS,
742
+ PROOF_OF_INTEGRITY_ABI,
743
+ signer
744
+ );
745
+ const [isValid, timestamp, updater] = await contract.verifyData(
746
+ ethers.toUtf8Bytes(nodeId),
747
+ contentHash
748
+ );
749
+ console.log("Verification result:", { isValid, timestamp, updater });
750
+ return { isValid, timestamp, updater };
751
+ };
752
+
753
+ // Funzione per scrivere on-chain
754
+ const writeOnChain = async (nodeId, contentHash) => {
755
+ console.log("Writing on chain:", { nodeId, contentHash });
756
+ const signer = await getSigner();
757
+ const contract = new ethers.Contract(
758
+ PROOF_CONTRACT_ADDRESS,
759
+ PROOF_OF_INTEGRITY_ABI,
760
+ signer
761
+ );
762
+ const tx = await contract.updateData(
763
+ ethers.toUtf8Bytes(nodeId),
764
+ contentHash
765
+ );
766
+ console.log("Transaction sent:", tx.hash);
767
+ const receipt = await tx.wait();
768
+ console.log("Transaction confirmed:", receipt);
769
+ return tx;
770
+ };
771
+
772
+ // Funzione per ottenere l'ultimo record
773
+ const getLatestRecord = async (nodeId) => {
774
+ const signer = await getSigner();
775
+ const contract = new ethers.Contract(
776
+ PROOF_CONTRACT_ADDRESS,
777
+ PROOF_OF_INTEGRITY_ABI,
778
+ signer
779
+ );
780
+ const [contentHash, timestamp, updater] = await contract.getLatestRecord(
781
+ ethers.toUtf8Bytes(nodeId)
782
+ );
783
+ console.log("Latest record from blockchain:", {
784
+ nodeId,
785
+ contentHash,
786
+ timestamp,
787
+ updater,
788
+ });
789
+ return { contentHash, timestamp, updater };
790
+ };
791
+
792
+
793
+ if (nodeId && !data) {
794
+ // Case 1: User passes only node
795
+ gun.get(nodeId).once(async (existingData) => {
796
+ if (!existingData) {
797
+ if (callback) callback({ err: "Node not found in GunDB" });
798
+ return;
799
+ }
800
+
801
+ console.log("existingData", existingData);
802
+
803
+ // Use stored contentHash instead of recalculating
804
+ const contentHash = existingData._contentHash;
805
+ console.log("contentHash", contentHash);
806
+
807
+ if (!contentHash) {
808
+ if (callback) callback({ err: "No content hash found for this node" });
809
+ return;
810
+ }
811
+
812
+ try {
813
+ const { isValid, timestamp, updater } = await verifyOnChain(
814
+ nodeId,
815
+ contentHash
816
+ );
817
+ const latestRecord = await getLatestRecord(nodeId);
818
+
819
+ if (isValid) {
820
+ if (callback)
821
+ callback({
822
+ ok: true,
823
+ message: "Data verified on blockchain",
824
+ timestamp,
825
+ updater,
826
+ latestRecord,
827
+ });
828
+ } else {
829
+ if (callback)
830
+ callback({
831
+ ok: false,
832
+ message: "Data not verified on blockchain",
833
+ latestRecord,
834
+ });
835
+ }
836
+ } catch (error) {
837
+ if (callback) callback({ err: error.message });
838
+ }
839
+ });
840
+ } else if (data && !nodeId) {
841
+ // Case 2: User passes only text (data)
842
+ const newNodeId = generateRandomId();
843
+ const dataString = JSON.stringify(data);
844
+ const contentHash = ethers.keccak256(ethers.toUtf8Bytes(dataString));
845
+
846
+ gun
847
+ .get(newNodeId)
848
+ .put({ ...data, _contentHash: contentHash }, async (ack) => {
849
+ console.log("ack", ack);
850
+ if (ack.err) {
851
+ if (callback) callback({ err: "Error saving data to GunDB" });
852
+ return;
853
+ }
854
+
855
+ try {
856
+ const tx = await writeOnChain(newNodeId, contentHash);
857
+ if (callback)
858
+ callback({
859
+ ok: true,
860
+ message: "Data written to GunDB and blockchain",
861
+ nodeId: newNodeId,
862
+ txHash: tx.hash,
863
+ });
864
+ } catch (error) {
865
+ if (callback) callback({ err: error.message });
866
+ }
867
+ });
868
+ } else {
869
+ if (callback)
870
+ callback({
871
+ err: "Invalid input. Provide either nodeId or data, not both.",
872
+ });
873
+ }
874
+
875
+ return gun;
876
+ } catch (error) {
877
+ callback({ err: error.message });
878
+ return this;
879
+ }
880
+ };
881
+
882
+ // =============================================
883
+ // STEALTH ADDRESS CORE FUNCTIONS
884
+ // =============================================
885
+ /**
886
+ * Converts a Gun private key to an Ethereum account.
887
+ * @param {string} gunPrivateKey - The Gun private key in base64url format.
888
+ * @returns {Object} An object containing the Ethereum account and public key.
889
+ */
890
+ Gun.chain.gunToEthAccount = function (gunPrivateKey) {
891
+ return gunToEthAccount(gunPrivateKey);
892
+ };
893
+
894
+ /**
895
+ * Generate a stealth key and related key pairs
896
+ * @param {string} recipientAddress - The recipient's Ethereum address
897
+ * @param {string} signature - The sender's signature to access their keys
898
+ * @returns {Promise<Object>} Object containing stealth addresses and keys
899
+ */
900
+ Gun.chain.generateStealthAddress = async function (recipientAddress, signature) {
901
+ try {
902
+ const gun = this;
903
+
904
+ // Get recipient's public keys
905
+ const recipientData = await gun
906
+ .get("gun-eth")
907
+ .get("users")
908
+ .get(recipientAddress)
909
+ .get("publicKeys")
910
+ .then();
911
+
912
+ if (!recipientData || !recipientData.viewingPublicKey || !recipientData.spendingPublicKey) {
913
+ throw new Error("Recipient's public keys not found");
914
+ }
915
+
916
+ // Get sender's keys
917
+ const senderAddress = await this.verifySignature(MESSAGE_TO_SIGN, signature);
918
+ const password = generatePassword(signature);
919
+
920
+ const senderData = await gun
921
+ .get("gun-eth")
922
+ .get("users")
923
+ .get(senderAddress)
924
+ .then();
925
+
926
+ if (!senderData || !senderData.s_pair) {
927
+ throw new Error("Sender's keys not found");
928
+ }
929
+
930
+ // Decrypt sender's spending pair
931
+ let spendingKeyPair;
932
+ try {
933
+ const decryptedData = await SEA.decrypt(senderData.s_pair, password);
934
+ spendingKeyPair = typeof decryptedData === 'string' ?
935
+ JSON.parse(decryptedData) :
936
+ decryptedData;
937
+ } catch (error) {
938
+ console.error("Error decrypting spending pair:", error);
939
+ throw new Error("Unable to decrypt spending pair");
940
+ }
941
+
942
+ // Generate shared secret using SEA ECDH with encryption public key
943
+ const sharedSecret = await SEA.secret(recipientData.viewingPublicKey, spendingKeyPair);
944
+
945
+ if (!sharedSecret) {
946
+ throw new Error("Unable to generate shared secret");
947
+ }
948
+
949
+ console.log("Generate shared secret:", sharedSecret);
950
+
951
+ const { stealthAddress } = deriveStealthAddress(
952
+ sharedSecret,
953
+ recipientData.spendingPublicKey
954
+ );
955
+
956
+ return {
957
+ stealthAddress,
958
+ senderPublicKey: spendingKeyPair.epub, // Use encryption public key
959
+ spendingPublicKey: recipientData.spendingPublicKey
960
+ };
961
+
962
+ } catch (error) {
963
+ console.error("Error generating stealth address:", error);
964
+ throw error;
965
+ }
966
+ };
967
+
968
+ /**
969
+ * Publish public keys needed to receive stealth payments
970
+ * @param {string} signature - The signature to authenticate the user
971
+ * @returns {Promise<void>}
972
+ */
973
+ Gun.chain.publishStealthKeys = async function (signature) {
974
+ try {
975
+ const gun = this;
976
+ const address = await this.verifySignature(MESSAGE_TO_SIGN, signature);
977
+ const password = generatePassword(signature);
978
+
979
+ // Get encrypted key pairs
980
+ const encryptedData = await gun
981
+ .get("gun-eth")
982
+ .get("users")
983
+ .get(address)
984
+ .then();
985
+
986
+ if (!encryptedData || !encryptedData.v_pair || !encryptedData.s_pair) {
987
+ throw new Error("Keys not found");
988
+ }
989
+
990
+ // Decrypt viewing and spending pairs
991
+ const viewingKeyPair = JSON.parse(
992
+ await SEA.decrypt(encryptedData.v_pair, password)
993
+ );
994
+ const spendingKeyPair = JSON.parse(
995
+ await SEA.decrypt(encryptedData.s_pair, password)
996
+ );
997
+
998
+ const viewingAccount = gunToEthAccount(viewingKeyPair.priv);
999
+ const spendingAccount = gunToEthAccount(spendingKeyPair.priv);
1000
+
1001
+ // Publish only public keys
1002
+ gun.get("gun-eth").get("users").get(address).get("publicKeys").put({
1003
+ viewingPublicKey: viewingAccount.publicKey,
1004
+ spendingPublicKey: spendingAccount.publicKey,
1005
+ });
1006
+
1007
+ console.log("Stealth public keys published successfully");
1008
+ } catch (error) {
1009
+ console.error("Error publishing stealth keys:", error);
1010
+ throw error;
1011
+ }
1012
+ };
1013
+
1014
+ // =============================================
1015
+ // STEALTH PAYMENT FUNCTIONS
1016
+ // =============================================
1017
+ /**
1018
+ * Recover funds from a stealth address
1019
+ * @param {string} stealthAddress - The stealth address to recover funds from
1020
+ * @param {string} senderPublicKey - The sender's public key used to generate the address
1021
+ * @param {string} signature - The signature to decrypt private keys
1022
+ * @returns {Promise<Object>} Object containing wallet to access funds
1023
+ */
1024
+ Gun.chain.recoverStealthFunds = async function (
1025
+ stealthAddress,
1026
+ senderPublicKey,
1027
+ signature,
1028
+ spendingPublicKey
1029
+ ) {
1030
+ try {
1031
+ const gun = this;
1032
+ const password = generatePassword(signature);
1033
+
1034
+ // Get own key pairs
1035
+ const myAddress = await this.verifySignature(MESSAGE_TO_SIGN, signature);
1036
+ const encryptedData = await gun
1037
+ .get("gun-eth")
1038
+ .get("users")
1039
+ .get(myAddress)
1040
+ .then();
1041
+
1042
+ if (!encryptedData || !encryptedData.v_pair || !encryptedData.s_pair) {
1043
+ throw new Error("Keys not found");
1044
+ }
1045
+
1046
+ // Decrypt viewing and spending pairs
1047
+ let viewingKeyPair;
1048
+ try {
1049
+ const decryptedViewingData = await SEA.decrypt(encryptedData.v_pair, password);
1050
+ viewingKeyPair = typeof decryptedViewingData === 'string' ?
1051
+ JSON.parse(decryptedViewingData) :
1052
+ decryptedViewingData;
1053
+ } catch (error) {
1054
+ console.error("Error decrypting keys:", error);
1055
+ throw new Error("Unable to decrypt keys");
1056
+ }
1057
+
1058
+ // Generate shared secret using SEA ECDH
1059
+ const sharedSecret = await SEA.secret(senderPublicKey, viewingKeyPair);
1060
+
1061
+ if (!sharedSecret) {
1062
+ throw new Error("Unable to generate shared secret");
1063
+ }
1064
+
1065
+ console.log("Recover shared secret:", sharedSecret);
1066
+
1067
+ const { wallet, stealthAddress: recoveredAddress } = deriveStealthAddress(
1068
+ sharedSecret,
1069
+ spendingPublicKey
1070
+ );
1071
+
1072
+ // Verify address matches
1073
+ if (recoveredAddress.toLowerCase() !== stealthAddress.toLowerCase()) {
1074
+ console.error("Mismatch:", {
1075
+ recovered: recoveredAddress,
1076
+ expected: stealthAddress,
1077
+ sharedSecret
1078
+ });
1079
+ throw new Error("Recovered stealth address does not match");
1080
+ }
1081
+
1082
+ return {
1083
+ wallet,
1084
+ address: recoveredAddress,
1085
+ };
1086
+ } catch (error) {
1087
+ console.error("Error recovering stealth funds:", error);
1088
+ throw error;
1089
+ }
1090
+ };
1091
+
1092
+ /**
1093
+ * Announce a stealth payment
1094
+ * @param {string} stealthAddress - The generated stealth address
1095
+ * @param {string} senderPublicKey - The sender's public key
1096
+ * @param {string} spendingPublicKey - The spending public key
1097
+ * @param {string} signature - The sender's signature
1098
+ * @returns {Promise<void>}
1099
+ */
1100
+ Gun.chain.announceStealthPayment = async function (
1101
+ stealthAddress,
1102
+ senderPublicKey,
1103
+ spendingPublicKey,
1104
+ signature,
1105
+ options = { onChain: false, chain: 'optimismSepolia' }
1106
+ ) {
1107
+ try {
1108
+ const gun = this;
1109
+ const senderAddress = await this.verifySignature(MESSAGE_TO_SIGN, signature);
1110
+
1111
+ if (options.onChain) {
1112
+ const signer = await getSigner();
1113
+ const chainConfig = getAddressesForChain(options.chain);
1114
+
1115
+ const contract = new ethers.Contract(
1116
+ chainConfig.STEALTH_ANNOUNCER_ADDRESS,
1117
+ STEALTH_ANNOUNCER_ABI,
1118
+ signer
1119
+ );
1120
+
1121
+ // Get dev fee from contract
1122
+ const devFee = await contract.devFee();
1123
+ console.log("Dev fee:", devFee.toString());
1124
+
1125
+ // Call contract
1126
+ const tx = await contract.announcePayment(
1127
+ senderPublicKey,
1128
+ spendingPublicKey,
1129
+ stealthAddress,
1130
+ { value: devFee }
1131
+ );
1132
+
1133
+ console.log("Transaction sent:", tx.hash);
1134
+ const receipt = await tx.wait();
1135
+ console.log("Transaction confirmed:", receipt.hash);
1136
+
1137
+ console.log("Stealth payment announced on-chain (dev fee paid)");
1138
+ } else {
1139
+ // Off-chain announcement (GunDB)
1140
+ gun
1141
+ .get("gun-eth")
1142
+ .get("stealth-payments")
1143
+ .set({
1144
+ stealthAddress,
1145
+ senderAddress,
1146
+ senderPublicKey,
1147
+ spendingPublicKey,
1148
+ timestamp: Date.now(),
1149
+ });
1150
+ console.log("Stealth payment announced off-chain");
1151
+ }
1152
+ } catch (error) {
1153
+ console.error("Error announcing stealth payment:", error);
1154
+ console.error("Error details:", error.stack);
1155
+ throw error;
1156
+ }
1157
+ };
1158
+
1159
+ /**
1160
+ * Get all stealth payments for an address
1161
+ * @param {string} signature - The signature to authenticate the user
1162
+ * @returns {Promise<Array>} List of stealth payments
1163
+ */
1164
+ Gun.chain.getStealthPayments = async function (signature, options = { source: 'both' }) {
1165
+ try {
1166
+ const payments = [];
1167
+
1168
+ if (options.source === 'onChain' || options.source === 'both') {
1169
+ // Get on-chain payments
1170
+ const signer = await getSigner();
1171
+ const contractAddress = process.env.NODE_ENV === 'development'
1172
+ ? LOCAL_CONFIG.STEALTH_ANNOUNCER_ADDRESS
1173
+ : STEALTH_ANNOUNCER_ADDRESS;
1174
+
1175
+ const contract = new ethers.Contract(
1176
+ contractAddress,
1177
+ STEALTH_ANNOUNCER_ABI,
1178
+ signer
1179
+ );
1180
+
1181
+ try {
1182
+ // Get total number of announcements
1183
+ const totalAnnouncements = await contract.getAnnouncementsCount();
1184
+ const totalCount = Number(totalAnnouncements.toString());
1185
+ console.log("Total on-chain announcements:", totalCount);
1186
+
1187
+ if (totalCount > 0) {
1188
+ // Get announcements in batches of 100
1189
+ const batchSize = 100;
1190
+ const lastIndex = totalCount - 1;
1191
+
1192
+ for(let i = 0; i <= lastIndex; i += batchSize) {
1193
+ const toIndex = Math.min(i + batchSize - 1, lastIndex);
1194
+ const batch = await contract.getAnnouncementsInRange(i, toIndex);
1195
+
1196
+ // For each announcement, try to decrypt
1197
+ for(const announcement of batch) {
1198
+ try {
1199
+ // Verify announcement is valid
1200
+ if (!announcement || !announcement.stealthAddress ||
1201
+ !announcement.senderPublicKey || !announcement.spendingPublicKey) {
1202
+ console.log("Invalid announcement:", announcement);
1203
+ continue;
1204
+ }
1205
+
1206
+ // Try to recover funds to verify if announcement is for us
1207
+ const recoveredWallet = await this.recoverStealthFunds(
1208
+ announcement.stealthAddress,
1209
+ announcement.senderPublicKey,
1210
+ signature,
1211
+ announcement.spendingPublicKey
1212
+ );
1213
+
1214
+ // If no errors thrown, announcement is for us
1215
+ payments.push({
1216
+ stealthAddress: announcement.stealthAddress,
1217
+ senderPublicKey: announcement.senderPublicKey,
1218
+ spendingPublicKey: announcement.spendingPublicKey,
1219
+ timestamp: Number(announcement.timestamp),
1220
+ source: 'onChain',
1221
+ wallet: recoveredWallet
1222
+ });
1223
+
1224
+ } catch (e) {
1225
+ // Not for us, continue
1226
+ console.log(`Announcement not for us: ${announcement.stealthAddress}`);
1227
+ continue;
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1232
+ } catch (error) {
1233
+ console.error("Error retrieving on-chain announcements:", error);
1234
+ }
1235
+ }
1236
+
1237
+ if (options.source === 'offChain' || options.source === 'both') {
1238
+ // Get off-chain payments
1239
+ const gun = this;
1240
+ const offChainPayments = await new Promise((resolve) => {
1241
+ const p = [];
1242
+ gun
1243
+ .get("gun-eth")
1244
+ .get("stealth-payments")
1245
+ .get(recipientAddress)
1246
+ .map()
1247
+ .once((payment, id) => {
1248
+ if (payment?.stealthAddress) {
1249
+ p.push({ ...payment, id, source: 'offChain' });
1250
+ }
1251
+ });
1252
+ setTimeout(() => resolve(p), 2000);
1253
+ });
1254
+
1255
+ payments.push(...offChainPayments);
1256
+ }
1257
+
1258
+ console.log(`Found ${payments.length} stealth payments`);
1259
+ return payments;
1260
+ } catch (error) {
1261
+ console.error("Error retrieving stealth payments:", error);
1262
+ throw error;
1263
+ }
1264
+ };
1265
+
1266
+ /**
1267
+ * Clean up old stealth payments
1268
+ * @param {string} recipientAddress - The recipient's address
1269
+ * @returns {Promise<void>}
1270
+ */
1271
+ Gun.chain.cleanStealthPayments = async function(recipientAddress) {
1272
+ try {
1273
+ const gun = this;
1274
+ const payments = await gun
1275
+ .get("gun-eth")
1276
+ .get("stealth-payments")
1277
+ .get(recipientAddress)
1278
+ .map()
1279
+ .once()
1280
+ .then();
1281
+
1282
+ // Remove empty or invalid nodes
1283
+ if (payments) {
1284
+ Object.keys(payments).forEach(async (key) => {
1285
+ const payment = payments[key];
1286
+ if (!payment || !payment.stealthAddress || !payment.senderPublicKey || !payment.spendingPublicKey) {
1287
+ await gun
1288
+ .get("gun-eth")
1289
+ .get("stealth-payments")
1290
+ .get(recipientAddress)
1291
+ .get(key)
1292
+ .put(null);
1293
+ }
1294
+ });
1295
+ }
1296
+ } catch (error) {
1297
+ console.error("Error cleaning stealth payments:", error);
1298
+ }
1299
+ };
1300
+
1301
+ // =============================================
1302
+ // EXPORTS
1303
+ // =============================================
1304
+
1305
+ // Crea una classe GunEth che contiene tutti i metodi e le utility
1306
+ class GunEth {
1307
+ // Static utility methods
1308
+ static generateRandomId = generateRandomId;
1309
+ static generatePassword = generatePassword;
1310
+ static gunToEthAccount = gunToEthAccount;
1311
+ static getSigner = getSigner;
1312
+ static deriveStealthAddress = deriveStealthAddress;
1313
+
1314
+ // Chain methods
1315
+ static chainMethods = {
1316
+ setSigner: Gun.chain.setSigner,
1317
+ getSigner: Gun.chain.getSigner,
1318
+ verifySignature: Gun.chain.verifySignature,
1319
+ generatePassword: Gun.chain.generatePassword,
1320
+ createSignature: Gun.chain.createSignature,
1321
+ createAndStoreEncryptedPair: Gun.chain.createAndStoreEncryptedPair,
1322
+ getAndDecryptPair: Gun.chain.getAndDecryptPair,
1323
+ proof: Gun.chain.proof,
1324
+ gunToEthAccount: Gun.chain.gunToEthAccount,
1325
+ generateStealthAddress: Gun.chain.generateStealthAddress,
1326
+ publishStealthKeys: Gun.chain.publishStealthKeys,
1327
+ recoverStealthFunds: Gun.chain.recoverStealthFunds,
1328
+ announceStealthPayment: Gun.chain.announceStealthPayment,
1329
+ getStealthPayments: Gun.chain.getStealthPayments,
1330
+ cleanStealthPayments: Gun.chain.cleanStealthPayments
1331
+ };
1332
+
1333
+ // Constants
1334
+ static MESSAGE_TO_SIGN = MESSAGE_TO_SIGN;
1335
+ static PROOF_CONTRACT_ADDRESS = PROOF_CONTRACT_ADDRESS;
1336
+ static LOCAL_CONFIG = LOCAL_CONFIG;
1337
+ }
1338
+
1339
+ export { GunEth, MESSAGE_TO_SIGN, deriveStealthAddress, generatePassword, generateRandomId, getSigner, gunToEthAccount };
1340
+ //# sourceMappingURL=gun-eth.node.mjs.map