gun-eth 1.2.14 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -5,18 +5,19 @@
5
5
 
6
6
  ## Table of Contents
7
7
 
8
- 1. [Description](#description)
9
- 2. [Smart Contract](#smart-contract)
10
- 3. [Key Features](#key-features)
11
- 4. [How to Install](#how-to-install)
12
- 5. [How to Use](#how-to-use)
13
- 6. [Core Functions](#core-functions)
14
- 7. [SHINE](#shine)
15
- 8. [Standalone Mode](#standalone-mode)
16
- 9. [Security Considerations](#security-considerations)
17
- 10. [Contributing](#contributing)
18
- 11. [License](#license)
19
- 12. [Contact](#contact)
8
+ 1. [DESCRIPTION](#description)
9
+ 2. [SMART CONTRACT](#smart-contract)
10
+ 3. [KEY FEATURES](#key-features)
11
+ 4. [HOW TO INSTALL](#how-to-install)
12
+ 5. [HOW TO USE](#how-to-use)
13
+ 6. [HOW IT WORKS](#how-it-works)
14
+ 7. [CORE FUNCTIONS](#core-functions)
15
+ 8. [SHINE](#shine)
16
+ 9. [STANDALONE MODE](#standalone-mode)
17
+ 10. [SECURITY CONSIDERATIONS](#security-considerations)
18
+ 11. [CONTRIBUTING](#contributing)
19
+ 12. [LICENSE](#license)
20
+ 13. [CONTACT](#contact)
20
21
 
21
22
  ## DESCRIPTION
22
23
 
@@ -60,6 +61,19 @@ Learn more about Gun.js [here](https://gun.eco/docs/Getting-Started).
60
61
 
61
62
  Learn more about plugin implementation [here](https://github.com/amark/gun/wiki/Adding-Methods-to-the-Gun-Chain#abstraction-layers).
62
63
 
64
+
65
+ ## HOW IT WORKS
66
+
67
+ ### Create KeyPair
68
+
69
+
70
+ [![](https://mermaid.ink/img/pako:eNpdUUtuwjAQvcrIGzZwgSwqJSRQhEorwqZNWLjxkFgkduSPEAJu1Fv0Yp0khaj1wh6P3s_jCyu0QBawQ61PRcWNg12cKxhWmK2T97dwtYVlskm24W71utnDbPYE0SWVpUIDJdLOHQIHSx3uvEE4SVdBJS2ceF2juz0Eo458Tb-_rjDPlnfqGs8tl2agpUm4f-DnvVmcJaow59aBq_6hR09vpSqh9OqvQtwrJGQ2sBU9F7TqgHEEEzpm6KoJEGjiLRrbl1wIg9aOMkkvs8hSp8kLhzgo4PgbRnaKn3E0MhY945miiz2bsgZNw6WgSV86SM4oTIM5C6gU3Bxz1rfVjbDcO52eVcECZzxOmdG-rFhw4LWlm28FDS2WvDS8eXRRSIr2Mnxm_6dT1nL1ofUdc_sBNpWchQ?type=png)](https://mermaid.live/edit#pako:eNpdUUtuwjAQvcrIGzZwgSwqJSRQhEorwqZNWLjxkFgkduSPEAJu1Fv0Yp0khaj1wh6P3s_jCyu0QBawQ61PRcWNg12cKxhWmK2T97dwtYVlskm24W71utnDbPYE0SWVpUIDJdLOHQIHSx3uvEE4SVdBJS2ceF2juz0Eo458Tb-_rjDPlnfqGs8tl2agpUm4f-DnvVmcJaow59aBq_6hR09vpSqh9OqvQtwrJGQ2sBU9F7TqgHEEEzpm6KoJEGjiLRrbl1wIg9aOMkkvs8hSp8kLhzgo4PgbRnaKn3E0MhY945miiz2bsgZNw6WgSV86SM4oTIM5C6gU3Bxz1rfVjbDcO52eVcECZzxOmdG-rFhw4LWlm28FDS2WvDS8eXRRSIr2Mnxm_6dT1nL1ofUdc_sBNpWchQ)
71
+
72
+ ### Retrive KeyPair
73
+ ----
74
+
75
+ [![](https://mermaid.ink/img/pako:eNplUsluwjAQ_ZWRz_ADObQCEiggOLAc2iQHN56ABbGjsU2FAv_erBBBLs7Yb5lnT8ESLZB5LD3rv-TIycLOjxSU3yjc7kabnQcbtCTxgrDEa84lxTAcfsC42BskyElfpEADXAhCU65KgJEHxa0j_Lw3WuOKcvtGc4NJ-NBDldA1tyjg1ChDSjqDmVP-OG6Ik9rLL4J3qHZKdPr-Uz8IfayxD6QzUh26RnvNtRZBbTEtWprUCoxLkjJL6s6dwfRpMKsCOFIg8KWnuI9d6xt8dVAk0uTBXvHfM4LVHTfut18x5i-M9spBadskjvsXWjEWL4yVNHXe7j00vSWe1YmXYbD2nw7Uvkrn8NWAmmLeLxZNwQYsQ8q4FOX4FNVRxOwRM4yYV_4KTqeI1dvqXmK5s3p7VQnzLDkcMNLucGReys-mrFwuuEVf8gPx7LGLQlpNq2ZC60EdsJyrH607zP0f6c7pXw?type=png)](https://mermaid.live/edit#pako:eNplUsluwjAQ_ZWRz_ADObQCEiggOLAc2iQHN56ABbGjsU2FAv_erBBBLs7Yb5lnT8ESLZB5LD3rv-TIycLOjxSU3yjc7kabnQcbtCTxgrDEa84lxTAcfsC42BskyElfpEADXAhCU65KgJEHxa0j_Lw3WuOKcvtGc4NJ-NBDldA1tyjg1ChDSjqDmVP-OG6Ik9rLL4J3qHZKdPr-Uz8IfayxD6QzUh26RnvNtRZBbTEtWprUCoxLkjJL6s6dwfRpMKsCOFIg8KWnuI9d6xt8dVAk0uTBXvHfM4LVHTfut18x5i-M9spBadskjvsXWjEWL4yVNHXe7j00vSWe1YmXYbD2nw7Uvkrn8NWAmmLeLxZNwQYsQ8q4FOX4FNVRxOwRM4yYV_4KTqeI1dvqXmK5s3p7VQnzLDkcMNLucGReys-mrFwuuEVf8gPx7LGLQlpNq2ZC60EdsJyrH607zP0f6c7pXw)
76
+
63
77
  ## CORE FUNCTIONS
64
78
 
65
79
  - `verifySignature(message, signature)`: Verifies an Ethereum signature for a given message.
@@ -114,6 +128,11 @@ Learn more about plugin implementation [here](https://github.com/amark/gun/wiki/
114
128
 
115
129
  SHINE (Secure Hash Integrity Network Ethereum) provides a mechanism for verifying data integrity using Ethereum and Gun.js.
116
130
 
131
+
132
+ [![](https://mermaid.ink/img/pako:eNplk1GTmjAQx79KJs_oICAiD9epQoX25Dq1006LPqRkPTMHiRODdx763S8Ez7N3PCXZ__73txvS4EJQwCFel-Kx2BCp0M9oyZH-PucL1e4XSZrF6BdItmYFUUzwFer1btCkSfm2ViHi2iGln05d2qQNHv_A7oim-Q9QksEeECWKoLUUFZrVPJqsOu3UGEVNZKKi5heX6M0lzuMnJUmhUCG4Aq4SstusrmWZOKIvecxpiIwTF6pzO6tiU2aWmx4OHYvgPd0u42fJzEiSjmRPSkYvilek5A0pzWegUEkU7BSSUAhJu-b-laJ4uPZNLoBf3wHuzUDhlTE1AN-uRO8E19Nt_W41AwepGRCHx_MlnLW3xmyeTyW08Y9zmxtBpm9YSECM_3crmQne5b8l08mCf-zqzii-d7CZrm5GumvN6ApbuAJZEUb1b9W0CUusNlDBEod6SYl8WGJzzE9aS2olFgde4FDJGiwsRX2_weGalDu9q7faGiJG7iWpLqdbwnHY4Ccc9pxxf-wEA9exPc92A384tPABh8NB33WDwdCxfcfxAy84WfhZCG0x6HuuZ49HI98PRjpojywMlGn2efcSzIMwNf6ahBbr9AJRuPoz?type=png)](https://mermaid.live/edit#pako:eNplk1GTmjAQx79KJs_oICAiD9epQoX25Dq1006LPqRkPTMHiRODdx763S8Ez7N3PCXZ__73txvS4EJQwCFel-Kx2BCp0M9oyZH-PucL1e4XSZrF6BdItmYFUUzwFer1btCkSfm2ViHi2iGln05d2qQNHv_A7oim-Q9QksEeECWKoLUUFZrVPJqsOu3UGEVNZKKi5heX6M0lzuMnJUmhUCG4Aq4SstusrmWZOKIvecxpiIwTF6pzO6tiU2aWmx4OHYvgPd0u42fJzEiSjmRPSkYvilek5A0pzWegUEkU7BSSUAhJu-b-laJ4uPZNLoBf3wHuzUDhlTE1AN-uRO8E19Nt_W41AwepGRCHx_MlnLW3xmyeTyW08Y9zmxtBpm9YSECM_3crmQne5b8l08mCf-zqzii-d7CZrm5GumvN6ApbuAJZEUb1b9W0CUusNlDBEod6SYl8WGJzzE9aS2olFgde4FDJGiwsRX2_weGalDu9q7faGiJG7iWpLqdbwnHY4Ccc9pxxf-wEA9exPc92A384tPABh8NB33WDwdCxfcfxAy84WfhZCG0x6HuuZ49HI98PRjpojywMlGn2efcSzIMwNf6ahBbr9AJRuPoz)
133
+
134
+
135
+
117
136
  #### SHINE Contract Configuration
118
137
 
119
138
  Currently, SHINE supports only the Optimism Sepolia network. The contract address is managed internally:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gun-eth",
3
- "version": "1.2.14",
3
+ "version": "1.3.0",
4
4
  "description": "A GunDB plugin for Ethereum, and Web3",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
@@ -0,0 +1,163 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>💎 Ethereum to 🔫 Gun Key Pair Demo</title>
6
+ <!-- Include Gun -->
7
+ <script src="https://cdn.jsdelivr.net/npm/gun/gun.js"></script>
8
+ <!-- Include SEA (part of Gun) -->
9
+ <script src="https://cdn.jsdelivr.net/npm/gun/sea.js"></script>
10
+ <!-- Include Ethers.js -->
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.umd.min.js"></script>
12
+ <!-- Include the Gun-Eth plugin -->
13
+ <script src="../gun-eth.js"></script>
14
+ <style>
15
+ body {
16
+ font-family: Arial, sans-serif;
17
+ margin: 0;
18
+ padding: 20px;
19
+ background-color: #f0f0f0;
20
+ }
21
+ .container {
22
+ max-width: 400px;
23
+ background-color: white;
24
+ border: 1px solid #000;
25
+ padding: 10px;
26
+ }
27
+ h1,
28
+ h2 {
29
+ margin: 0 0 10px 0;
30
+ font-size: 16px;
31
+ }
32
+ form, div {
33
+ margin-bottom: 15px;
34
+ }
35
+ input,
36
+ button {
37
+ display: inline-block;
38
+ margin: 2px 0;
39
+ padding: 2px;
40
+ font-size: 12px;
41
+ }
42
+ input {
43
+ width: 150px;
44
+ }
45
+ button {
46
+ width: 100px;
47
+ }
48
+ label {
49
+ display: inline-block;
50
+ width: 70px;
51
+ font-size: 12px;
52
+ }
53
+ #configStatus,
54
+ .result {
55
+ font-weight: bold;
56
+ margin-bottom: 10px;
57
+ }
58
+ .result {
59
+ color: blue;
60
+ }
61
+ </style>
62
+ </head>
63
+ <body>
64
+ <div class="container">
65
+ <h1>💎 Ethereum to 🔫 Gun Key Pair Demo</h1>
66
+
67
+ <form id="configForm">
68
+ <h2>🔧 Configuration</h2>
69
+ <label for="rpcUrl">🌐 RPC URL:</label>
70
+ <br>
71
+ <input type="text" id="rpcUrl" name="rpcUrl" required>
72
+ <br>
73
+ <label for="privateKey">🔑 Private Key:</label>
74
+ <br>
75
+ <input type="password" id="privateKey" name="privateKey" required>
76
+ <br>
77
+ <button type="submit">⚙️ Configure</button>
78
+ </form>
79
+
80
+ <div id="createPairSection">
81
+ <h2>🔒 Create and Store Encrypted Pair</h2>
82
+ <button id="createPairBtn">🔐 Create Pair</button>
83
+ </div>
84
+
85
+ <div id="retrievePairSection">
86
+ <h2>🔓 Retrieve and Decrypt Pair</h2>
87
+ <button id="retrievePairBtn">🔎 Retrieve Pair</button>
88
+ </div>
89
+
90
+ <div id="resultSection">
91
+ <h2>📊 Result</h2>
92
+ <p id="resultText"></p>
93
+ </div>
94
+ </div>
95
+
96
+ <script>
97
+ let gun;
98
+ const MESSAGE_TO_SIGN = "GunDB access with Ethereum";
99
+
100
+ document.getElementById('configForm').addEventListener('submit', function(event) {
101
+ event.preventDefault();
102
+ const rpcUrl = document.getElementById('rpcUrl').value;
103
+ const privateKey = document.getElementById('privateKey').value;
104
+
105
+ gun = Gun();
106
+ gun.setStandaloneConfig(rpcUrl, privateKey);
107
+
108
+ console.log('Standalone configuration set');
109
+ document.getElementById('resultText').innerText = '✅ Configuration set successfully';
110
+ });
111
+
112
+ document.getElementById('createPairBtn').addEventListener('click', async function() {
113
+ if (!gun) {
114
+ alert('⚠️ Please configure Gun first');
115
+ return;
116
+ }
117
+
118
+ try {
119
+ const signature = await gun.createSignature(MESSAGE_TO_SIGN);
120
+ if (!signature) {
121
+ throw new Error('Failed to create signature');
122
+ }
123
+
124
+ const signer = new ethers.Wallet(document.getElementById('privateKey').value);
125
+ const address = await signer.getAddress();
126
+
127
+ await gun.createAndStoreEncryptedPair(address, signature);
128
+
129
+ document.getElementById('resultText').innerText = '✅ Encrypted pair created and stored successfully';
130
+ } catch (error) {
131
+ document.getElementById('resultText').innerText = '❌ Error: ' + error.message;
132
+ }
133
+ });
134
+
135
+ document.getElementById('retrievePairBtn').addEventListener('click', async function() {
136
+ if (!gun) {
137
+ alert('⚠️ Please configure Gun first');
138
+ return;
139
+ }
140
+
141
+ try {
142
+ const signature = await gun.createSignature(MESSAGE_TO_SIGN);
143
+ if (!signature) {
144
+ throw new Error('Failed to create signature');
145
+ }
146
+
147
+ const signer = new ethers.Wallet(document.getElementById('privateKey').value);
148
+ const address = await signer.getAddress();
149
+
150
+ const pair = await gun.getAndDecryptPair(address, signature);
151
+
152
+ document.getElementById('resultText').innerHTML = `
153
+ <strong>🔑 Retrieved pair:</strong>
154
+ <br>
155
+ ${JSON.stringify(pair, null, 2)}
156
+ `;
157
+ } catch (error) {
158
+ document.getElementById('resultText').innerText = '❌ Error: ' + error.message;
159
+ }
160
+ });
161
+ </script>
162
+ </body>
163
+ </html>
@@ -0,0 +1,164 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Gun to Ethereum Address Demo</title>
6
+ <!-- Include Gun -->
7
+ <script src="https://cdn.jsdelivr.net/npm/gun/gun.js"></script>
8
+ <!-- Include SEA (part of Gun) -->
9
+ <script src="https://cdn.jsdelivr.net/npm/gun/sea.js"></script>
10
+ <!-- Include Ethers.js -->
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.umd.min.js"></script>
12
+ <!-- Include the Gun-Eth plugin -->
13
+ <script src="../gun-eth.js"></script>
14
+ <style>
15
+ body {
16
+ font-family: Arial, sans-serif;
17
+ margin: 0;
18
+ padding: 20px;
19
+ background-color: #f0f0f0;
20
+ }
21
+ .container {
22
+ max-width: 400px;
23
+ background-color: white;
24
+ border: 1px solid #000;
25
+ padding: 10px;
26
+ }
27
+ h1,
28
+ h2 {
29
+ margin: 0 0 10px 0;
30
+ font-size: 16px;
31
+ }
32
+ form {
33
+ margin-bottom: 15px;
34
+ }
35
+ input,
36
+ button {
37
+ display: inline-block;
38
+ margin: 2px 0;
39
+ padding: 2px;
40
+ font-size: 12px;
41
+ }
42
+ input {
43
+ width: 150px;
44
+ }
45
+ button {
46
+ width: 100px;
47
+ }
48
+ label {
49
+ display: inline-block;
50
+ width: 70px;
51
+ font-size: 12px;
52
+ }
53
+ #configStatus,
54
+ .result {
55
+ font-weight: bold;
56
+ margin-bottom: 10px;
57
+ }
58
+ .result {
59
+ color: blue;
60
+ }
61
+ .disclaimer {
62
+ background-color: #ffffd0;
63
+ border: 1px solid #e6e600;
64
+ padding: 10px;
65
+ margin-bottom: 15px;
66
+ font-size: 14px;
67
+ }
68
+ </style>
69
+ </head>
70
+ <body>
71
+ <div class="container">
72
+ <h1>🔫 Gun to 💎 Ethereum Address Demo</h1>
73
+
74
+ <form id="gunUserForm">
75
+ <h2>👤 User Login/Registration</h2>
76
+ <label for="gunAlias">👤 Alias:</label>
77
+ <br>
78
+ <input type="text" id="gunAlias" name="gunAlias" required>
79
+ <br>
80
+ <label for="gunPassword">🔑 Password:</label>
81
+ <br>
82
+ <input type="password" id="gunPassword" name="gunPassword" required>
83
+ <br>
84
+ <button type="submit">🚀 Create/Login</button>
85
+ </form>
86
+
87
+ <div id="conversionSection" style="display:none;">
88
+ <h2>🔄 Convert Gun User to Ethereum Address</h2>
89
+ <button id="convertBtn">🔄 Convert
90
+ </div>
91
+
92
+ <div id="resultSection">
93
+ <h2>Result</h2>
94
+ <p id="resultText"></p>
95
+ </div>
96
+ </div>
97
+
98
+ <script>
99
+ let gun = Gun();
100
+ let currentUser = null;
101
+
102
+ document.getElementById('gunUserForm').addEventListener('submit', function(event) {
103
+ event.preventDefault();
104
+ const alias = document.getElementById('gunAlias').value;
105
+ const password = document.getElementById('gunPassword').value;
106
+
107
+ console.log('Attempting to create or login user:', alias);
108
+
109
+ gun.user().create(alias, password, function(ack) {
110
+ if (ack.err) {
111
+ console.log('User creation failed, trying to login:', ack.err);
112
+ gun.user().auth(alias, password, function(authAck) {
113
+ if (authAck.err) {
114
+ document.getElementById('resultText').innerText = 'Error logging in: ' + authAck.err;
115
+ } else {
116
+ currentUser = gun.user();
117
+ document.getElementById('resultText').innerText = 'User logged in successfully.';
118
+ document.getElementById('conversionSection').style.display = 'block';
119
+ }
120
+ });
121
+ } else {
122
+ console.log('User created successfully:', ack);
123
+ currentUser = gun.user();
124
+ document.getElementById('resultText').innerText = 'User created and logged in successfully.';
125
+ document.getElementById('conversionSection').style.display = 'block';
126
+ }
127
+ });
128
+ });
129
+
130
+ document.getElementById('convertBtn').addEventListener('click', function() {
131
+ if (!currentUser) {
132
+ alert('Please login first');
133
+ return;
134
+ }
135
+
136
+ console.log('Current user:', currentUser);
137
+ console.log('User pair:', currentUser._.sea);
138
+
139
+ try {
140
+ const pair = currentUser._.sea;
141
+ if (!pair || !pair.priv) {
142
+ throw new Error('Unable to retrieve Gun user private key');
143
+ }
144
+
145
+ const ethAccount = gun.gunToEthAccount(pair.priv);
146
+
147
+ document.getElementById('resultText').innerHTML = `
148
+ <strong>Gun User</strong>
149
+ <br>
150
+ ${currentUser.is.alias}
151
+ <br>
152
+ <br>
153
+ <strong>Ethereum Address:</strong>
154
+ <br>
155
+ ${ethAccount.publicKey}
156
+ `;
157
+ } catch (error) {
158
+ document.getElementById('resultText').innerText = 'Error: ' + error.message;
159
+ console.error('Conversion error:', error);
160
+ }
161
+ });
162
+ </script>
163
+ </body>
164
+ </html>
@@ -0,0 +1,256 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Demo Gun-Eth Plugin</title>
6
+ <!-- Include Gun -->
7
+ <script src="https://cdn.jsdelivr.net/npm/gun/gun.js"></script>
8
+ <!-- Include SEA (part of Gun) -->
9
+ <script src="https://cdn.jsdelivr.net/npm/gun/sea.js"></script>
10
+ <!-- Include Ethers.js -->
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.umd.min.js"></script>
12
+ <!-- Include the Gun-Eth plugin -->
13
+ <script src="https://cdn.jsdelivr.net/npm/gun-eth/src/gun-eth.js""></script>
14
+ <style>
15
+ body {
16
+ font-family: Arial, sans-serif;
17
+ margin: 0;
18
+ padding: 20px;
19
+ background-color: #f0f0f0;
20
+ }
21
+ .container {
22
+ max-width: 400px;
23
+ background-color: white;
24
+ border: 1px solid #000;
25
+ padding: 10px;
26
+ }
27
+ h1,
28
+ h2 {
29
+ margin: 0 0 10px 0;
30
+ font-size: 16px;
31
+ }
32
+ form {
33
+ margin-bottom: 15px;
34
+ }
35
+ input,
36
+ button {
37
+ display: inline-block;
38
+ margin: 2px 0;
39
+ padding: 2px;
40
+ font-size: 12px;
41
+ }
42
+ input {
43
+ width: 150px;
44
+ }
45
+ button {
46
+ width: 70px;
47
+ }
48
+ label {
49
+ display: inline-block;
50
+ width: 70px;
51
+ font-size: 12px;
52
+ }
53
+ #configStatus,
54
+ .result {
55
+ font-weight: bold;
56
+ margin-bottom: 10px;
57
+ }
58
+ .result {
59
+ color: blue;
60
+ }
61
+ .disclaimer {
62
+ background-color: #ffffd0;
63
+ border: 1px solid #e6e600;
64
+ padding: 10px;
65
+ margin-bottom: 15px;
66
+ font-size: 14px;
67
+ }
68
+ </style>
69
+ </head>
70
+ <body>
71
+ <div class="container">
72
+ <div class="disclaimer">
73
+ <strong>⚠️</strong> This example is available only on the
74
+ <strong>Optimism Sepolia</strong> chain.
75
+ </div>
76
+ <h1>💎 SHINE (Secure Hash Integriry Network Ethereum)</h1>
77
+ <hr />
78
+ <h2>🔧 Configuration</h2>
79
+ <form id="configForm">
80
+ <label for="rpcUrl">RPC URL:</label>
81
+ <input type="text" id="rpcUrl" name="rpcUrl" required />
82
+ <br />
83
+ <label for="privateKey">Private Key:</label>
84
+ <input type="password" id="privateKey" name="privateKey" required />
85
+ <br />
86
+ <button type="submit">Configure</button>
87
+ </form>
88
+ <div id="configStatus">Status: Not Configured</div>
89
+ <hr />
90
+
91
+ <h2>🔴 Write Data to Blockchain</h2>
92
+ <form id="writeForm">
93
+ <label for="data">Data</label>
94
+ <br />
95
+ <input type="text" id="data" name="data" required />
96
+ <br />
97
+ <button type="submit">Write</button>
98
+ </form>
99
+ <div id="writeResult" class="result"></div>
100
+ <hr />
101
+
102
+ <h2>🔍 Verify Data on Blockchain</h2>
103
+ <form id="verifyForm">
104
+ <label for="nodeId">Node ID</label>
105
+ <br />
106
+ <input type="text" id="nodeId" name="nodeId" required />
107
+ <br />
108
+ <button type="submit">Verify</button>
109
+ </form>
110
+ <div id="verifyResult" class="result"></div>
111
+ <hr />
112
+
113
+ <h2>🔄 Update Node Data in Gun</h2>
114
+ <form id="updateForm">
115
+ <label for="updateNodeId">Node ID</label>
116
+ <br />
117
+ <input type="text" id="updateNodeId" name="updateNodeId" required />
118
+ <br />
119
+ <label for="editMessage">New Data</label>
120
+ <br />
121
+ <input type="text" id="editMessage" name="editMessage" required />
122
+ <br />
123
+ <button type="submit">Update</button>
124
+ </form>
125
+ <div id="updateResult" class="result"></div>
126
+ </div>
127
+
128
+ <script>
129
+ let gun;
130
+ let isConfigured = false;
131
+
132
+ function updateConfigStatus() {
133
+ const statusElement = document.getElementById("configStatus");
134
+ statusElement.textContent = isConfigured
135
+ ? "Status: Configured"
136
+ : "Status: Not Configured";
137
+ statusElement.style.color = isConfigured ? "green" : "red";
138
+ }
139
+
140
+ document
141
+ .getElementById("configForm")
142
+ .addEventListener("submit", function (event) {
143
+ event.preventDefault();
144
+ const rpcUrl = document.getElementById("rpcUrl").value;
145
+ const privateKey = document.getElementById("privateKey").value;
146
+
147
+ // Configure Gun with the provided details
148
+ gun = Gun();
149
+ gun.setStandaloneConfig(rpcUrl, privateKey);
150
+
151
+ console.log("Standalone configuration set");
152
+ isConfigured = true;
153
+ updateConfigStatus();
154
+ });
155
+
156
+ document
157
+ .getElementById("writeForm")
158
+ .addEventListener("submit", function (event) {
159
+ event.preventDefault();
160
+ if (!isConfigured) {
161
+ document.getElementById("writeResult").textContent =
162
+ "Please configure Gun first.";
163
+ return;
164
+ }
165
+ const data = document.getElementById("data").value;
166
+ const resultElement = document.getElementById("writeResult");
167
+ resultElement.textContent = "Writing data...";
168
+
169
+ // Assicurati che `data` sia un oggetto JSON valido
170
+
171
+ gun.shine("optimismSepolia", null, data, function (result) {
172
+ console.log("Shine result:", result);
173
+ if (result.ok) {
174
+ resultElement.textContent =
175
+ "Data successfully written: " + result.nodeId;
176
+ } else {
177
+ resultElement.textContent = "Error writing data: " + result.err;
178
+ }
179
+ });
180
+ });
181
+
182
+ document
183
+ .getElementById("verifyForm")
184
+ .addEventListener("submit", function (event) {
185
+ event.preventDefault();
186
+ if (!isConfigured) {
187
+ document.getElementById("verifyResult").textContent =
188
+ "Please configure Gun first.";
189
+ return;
190
+ }
191
+ const nodeId = document.getElementById("nodeId").value;
192
+ const resultElement = document.getElementById("verifyResult");
193
+ resultElement.textContent = "Verifying data...";
194
+
195
+ gun.shine("optimismSepolia", nodeId, null, function (result) {
196
+ console.log("Verification result:", result);
197
+ if (result.ok) {
198
+ resultElement.textContent =
199
+ "Data successfully verified: " + result.message;
200
+ } else {
201
+ resultElement.textContent =
202
+ "Error verifying data: " + result.message;
203
+ }
204
+ });
205
+ });
206
+
207
+ document
208
+ .getElementById("updateForm")
209
+ .addEventListener("submit", function (event) {
210
+ event.preventDefault();
211
+ if (!isConfigured) {
212
+ document.getElementById("updateResult").textContent =
213
+ "Please configure Gun first.";
214
+ return;
215
+ }
216
+ const nodeId = document.getElementById("updateNodeId").value;
217
+ const editMessage = document.getElementById("editMessage").value;
218
+ const resultElement = document.getElementById("updateResult");
219
+ resultElement.textContent = "Updating data...";
220
+
221
+ gun.get(nodeId).once(function (existingData) {
222
+ // Prepara i nuovi dati
223
+ const newData = {
224
+ message: editMessage,
225
+ _contentHash: existingData._contentHash, // Manteniamo il contentHash precedente per il calcolo
226
+ };
227
+
228
+ // Calcola il nuovo contentHash
229
+ const dataString = JSON.stringify(editMessage);
230
+ const newContentHash = ethers.keccak256(
231
+ ethers.toUtf8Bytes(dataString)
232
+ );
233
+
234
+ // Aggiorna i dati con il nuovo contentHash
235
+ newData._contentHash = newContentHash;
236
+
237
+ // Update data in Gun
238
+ gun.get(nodeId).put(newData, function (ack) {
239
+ if (ack.err) {
240
+ console.error("Error updating data in Gun:", ack.err);
241
+ resultElement.textContent =
242
+ "Error updating data in Gun: " + ack.err;
243
+ } else {
244
+ console.log("Data successfully updated in Gun:", nodeId);
245
+ resultElement.textContent =
246
+ "Data successfully updated in Gun: " + nodeId;
247
+ }
248
+ });
249
+ });
250
+ });
251
+
252
+ // Inizializza lo stato di configurazione
253
+ updateConfigStatus();
254
+ </script>
255
+ </body>
256
+ </html>
package/src/gun-eth.js ADDED
@@ -0,0 +1,566 @@
1
+ (function (root, factory) {
2
+ if (typeof define === "function" && define.amd) {
3
+ define(["gun", "gun/sea", "ethers"], factory);
4
+ } else if (typeof module === "object" && module.exports) {
5
+ module.exports = factory(require("gun/gun"), require("gun/sea"), require("ethers"));
6
+ } else {
7
+ factory(root.Gun, root.SEA, root.ethers);
8
+ }
9
+ })(typeof self !== "undefined" ? self : this, function (Gun, SEA, ethers) {
10
+ console.log('Factory del plugin Gun-Eth chiamata');
11
+
12
+
13
+
14
+ // Funzione per verificare se ethers è disponibile
15
+ function checkEthers() {
16
+ if (typeof ethers === 'undefined') {
17
+ console.error('Ethers.js non è disponibile. Assicurati che sia caricato prima di questo script.');
18
+ return false;
19
+ }
20
+ console.log('Ethers version:', ethers.version);
21
+ return true;
22
+ }
23
+
24
+ // Variabili globali
25
+ let SHINE_ABI = [
26
+ {
27
+ "anonymous": false,
28
+ "inputs": [
29
+ {
30
+ "indexed": true,
31
+ "internalType": "bytes",
32
+ "name": "nodeId",
33
+ "type": "bytes"
34
+ },
35
+ {
36
+ "indexed": false,
37
+ "internalType": "bytes32",
38
+ "name": "contentHash",
39
+ "type": "bytes32"
40
+ },
41
+ {
42
+ "indexed": false,
43
+ "internalType": "address",
44
+ "name": "updater",
45
+ "type": "address"
46
+ }
47
+ ],
48
+ "name": "DataUpdated",
49
+ "type": "event"
50
+ },
51
+ {
52
+ "inputs": [
53
+ {
54
+ "internalType": "bytes[]",
55
+ "name": "nodeIds",
56
+ "type": "bytes[]"
57
+ },
58
+ {
59
+ "internalType": "bytes32[]",
60
+ "name": "contentHashes",
61
+ "type": "bytes32[]"
62
+ }
63
+ ],
64
+ "name": "batchUpdateData",
65
+ "outputs": [],
66
+ "stateMutability": "nonpayable",
67
+ "type": "function"
68
+ },
69
+ {
70
+ "inputs": [
71
+ {
72
+ "internalType": "bytes",
73
+ "name": "nodeId",
74
+ "type": "bytes"
75
+ }
76
+ ],
77
+ "name": "getLatestRecord",
78
+ "outputs": [
79
+ {
80
+ "internalType": "bytes32",
81
+ "name": "",
82
+ "type": "bytes32"
83
+ },
84
+ {
85
+ "internalType": "uint256",
86
+ "name": "",
87
+ "type": "uint256"
88
+ },
89
+ {
90
+ "internalType": "address",
91
+ "name": "",
92
+ "type": "address"
93
+ }
94
+ ],
95
+ "stateMutability": "view",
96
+ "type": "function"
97
+ },
98
+ {
99
+ "inputs": [
100
+ {
101
+ "internalType": "bytes",
102
+ "name": "",
103
+ "type": "bytes"
104
+ }
105
+ ],
106
+ "name": "nodeData",
107
+ "outputs": [
108
+ {
109
+ "internalType": "bytes32",
110
+ "name": "contentHash",
111
+ "type": "bytes32"
112
+ },
113
+ {
114
+ "internalType": "uint256",
115
+ "name": "timestamp",
116
+ "type": "uint256"
117
+ },
118
+ {
119
+ "internalType": "address",
120
+ "name": "updater",
121
+ "type": "address"
122
+ }
123
+ ],
124
+ "stateMutability": "view",
125
+ "type": "function"
126
+ },
127
+ {
128
+ "inputs": [
129
+ {
130
+ "internalType": "bytes",
131
+ "name": "nodeId",
132
+ "type": "bytes"
133
+ },
134
+ {
135
+ "internalType": "bytes32",
136
+ "name": "contentHash",
137
+ "type": "bytes32"
138
+ }
139
+ ],
140
+ "name": "updateData",
141
+ "outputs": [],
142
+ "stateMutability": "nonpayable",
143
+ "type": "function"
144
+ },
145
+ {
146
+ "inputs": [
147
+ {
148
+ "internalType": "bytes",
149
+ "name": "nodeId",
150
+ "type": "bytes"
151
+ },
152
+ {
153
+ "internalType": "bytes32",
154
+ "name": "contentHash",
155
+ "type": "bytes32"
156
+ }
157
+ ],
158
+ "name": "verifyData",
159
+ "outputs": [
160
+ {
161
+ "internalType": "bool",
162
+ "name": "",
163
+ "type": "bool"
164
+ },
165
+ {
166
+ "internalType": "uint256",
167
+ "name": "",
168
+ "type": "uint256"
169
+ },
170
+ {
171
+ "internalType": "address",
172
+ "name": "",
173
+ "type": "address"
174
+ }
175
+ ],
176
+ "stateMutability": "view",
177
+ "type": "function"
178
+ }
179
+ ]
180
+
181
+ let SHINE_OPTIMISM_SEPOLIA = "0x43D838b683F772F08f321E5FA265ad3e333BE9C2";
182
+ let SHINE_CONTRACT_ADDRESS;
183
+ let customToken = "";
184
+ let rpcUrl = "";
185
+ let privateKey = "";
186
+
187
+ /**
188
+ * Funzione per ottenere il signer
189
+ * @returns {Promise<ethers.Signer>} Il signer.
190
+ */
191
+ const getSigner = async () => {
192
+ if (rpcUrl && privateKey) {
193
+ // Modalità standalone
194
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
195
+ return new ethers.Wallet(privateKey, provider);
196
+ } else if (
197
+ typeof window !== "undefined" &&
198
+ typeof window.ethereum !== "undefined"
199
+ ) {
200
+ // Modalità browser
201
+ await window.ethereum.request({ method: "eth_requestAccounts" });
202
+ const provider = new ethers.BrowserProvider(window.ethereum);
203
+ return provider.getSigner();
204
+ } else {
205
+ throw new Error("No valid Ethereum provider found");
206
+ }
207
+ };
208
+
209
+ /**
210
+ * Sets standalone configuration for Gun.
211
+ * @param {string} newRpcUrl - The new RPC URL.
212
+ * @param {string} newPrivateKey - The new private key.
213
+ * @returns {Gun} The Gun instance for chaining.
214
+ */
215
+ Gun.chain.setStandaloneConfig = function (newRpcUrl, newPrivateKey) {
216
+ rpcUrl = newRpcUrl;
217
+ privateKey = newPrivateKey;
218
+ console.log("Standalone configuration set");
219
+ return this;
220
+ };
221
+
222
+ /**
223
+ * Sets a custom token for Gun operations.
224
+ * @param {string} token - The token to be set.
225
+ * @returns {Gun} The Gun instance for chaining.
226
+ */
227
+ Gun.chain.setToken = function (token) {
228
+ if (typeof token === "string" && token.length > 0) {
229
+ customToken = token;
230
+ console.log("Token set successfully:", token);
231
+ } else {
232
+ console.error("Invalid token. Must be a non-empty string.");
233
+ }
234
+ return this;
235
+ };
236
+
237
+ /**
238
+ * Retrieves the current custom token.
239
+ * @returns {string} The current custom token.
240
+ */
241
+ Gun.chain.getToken = function () {
242
+ return customToken;
243
+ };
244
+
245
+ // Add custom token to all 'put' operations
246
+ Gun.on("put", function (msg) {
247
+ const to = this.to;
248
+ msg.headers = {
249
+ token: customToken,
250
+ };
251
+ to.next(msg);
252
+ });
253
+
254
+ /**
255
+ * Verifies an Ethereum signature.
256
+ * @param {string} message - The original message that was signed.
257
+ * @param {string} signature - The signature to verify.
258
+ * @returns {Promise<string|null>} The recovered address or null if verification fails.
259
+ */
260
+ Gun.chain.verifySignature = async function (message, signature) {
261
+ try {
262
+ const recoveredAddress = ethers.verifyMessage(message, signature);
263
+ return recoveredAddress;
264
+ } catch (error) {
265
+ console.error("Error verifying signature:", error);
266
+ return null;
267
+ }
268
+ };
269
+
270
+ /**
271
+ * Generates a password from a signature.
272
+ * @param {string} signature - The signature to derive the password from.
273
+ * @returns {string|null} The generated password or null if generation fails.
274
+ */
275
+ Gun.chain.generatePassword = function (signature) {
276
+ try {
277
+ const hexSignature = ethers.hexlify(signature);
278
+ const hash = ethers.keccak256(hexSignature);
279
+ console.log("Generated password:", hash);
280
+ return hash;
281
+ } catch (error) {
282
+ console.error("Error generating password:", error);
283
+ return null;
284
+ }
285
+ };
286
+
287
+ /**
288
+ * Creates an Ethereum signature for a given message.
289
+ * @param {string} message - The message to sign.
290
+ * @returns {Promise<string|null>} The signature or null if signing fails.
291
+ */
292
+ Gun.chain.createSignature = async function (message) {
293
+ try {
294
+ const signer = await getSigner();
295
+ const signature = await signer.signMessage(message);
296
+ console.log("Signature created:", signature);
297
+ return signature;
298
+ } catch (error) {
299
+ console.error("Error creating signature:", error);
300
+ return null;
301
+ }
302
+ };
303
+
304
+ /**
305
+ * Creates and stores an encrypted key pair for a given address.
306
+ * @param {string} address - The Ethereum address to associate with the key pair.
307
+ * @param {string} signature - The signature to use for encryption.
308
+ * @returns {Promise<void>}
309
+ */
310
+ Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
311
+ try {
312
+ const gun = this;
313
+ const pair = await SEA.pair();
314
+ const encryptedPair = await SEA.encrypt(JSON.stringify(pair), signature);
315
+ await gun.get("gun-eth").get("users").get(address).put({ encryptedPair });
316
+ console.log("Encrypted pair stored for:", address);
317
+ } catch (error) {
318
+ console.error("Error creating and storing encrypted pair:", error);
319
+ }
320
+ };
321
+
322
+ /**
323
+ * Retrieves and decrypts a stored key pair for a given address.
324
+ * @param {string} address - The Ethereum address associated with the key pair.
325
+ * @param {string} signature - The signature to use for decryption.
326
+ * @returns {Promise<Object|null>} The decrypted key pair or null if retrieval fails.
327
+ */
328
+ Gun.chain.getAndDecryptPair = async function (address, signature) {
329
+ try {
330
+ const gun = this;
331
+ const encryptedData = await gun
332
+ .get("gun-eth")
333
+ .get("users")
334
+ .get(address)
335
+ .get("encryptedPair")
336
+ .then();
337
+ if (!encryptedData) {
338
+ throw new Error("No encrypted data found for this address");
339
+ }
340
+ const decryptedPair = await SEA.decrypt(encryptedData, signature);
341
+ console.log(decryptedPair);
342
+ return decryptedPair;
343
+ } catch (error) {
344
+ console.error("Error retrieving and decrypting pair:", error);
345
+ return null;
346
+ }
347
+ };
348
+
349
+ /**
350
+ * SHINE (Secure Hybrid Information and Network Environment) functionality.
351
+ * @param {string} chain - The blockchain to use (e.g., "optimismSepolia").
352
+ * @param {string} nodeId - The ID of the node to verify or write.
353
+ * @param {Object} data - The data to write (if writing).
354
+ * @param {Function} callback - Callback function to handle the result.
355
+ * @returns {Gun} The Gun instance for chaining.
356
+ */
357
+ Gun.chain.shine = function (chain, nodeId, data, callback) {
358
+ console.log("SHINE plugin called with:", { chain, nodeId, data });
359
+
360
+ if (!checkEthers()) {
361
+ if (callback) callback({ err: "Ethers.js non è disponibile" });
362
+ return this;
363
+ }
364
+
365
+ if (typeof callback !== "function") {
366
+ console.error("Callback must be a function");
367
+ return this;
368
+ }
369
+
370
+ const gun = this;
371
+
372
+ // Seleziona l'indirizzo basato sulla catena
373
+ if (chain === "optimismSepolia") {
374
+ SHINE_CONTRACT_ADDRESS = SHINE_OPTIMISM_SEPOLIA;
375
+ } else {
376
+ throw new Error("Chain not supported");
377
+ }
378
+
379
+ // Funzione per verificare on-chain
380
+ const verifyOnChain = async (nodeId, contentHash) => {
381
+ console.log("Verifying on chain:", { nodeId, contentHash });
382
+ const signer = await getSigner();
383
+ const contract = new ethers.Contract(
384
+ SHINE_CONTRACT_ADDRESS,
385
+ SHINE_ABI,
386
+ signer
387
+ );
388
+ const [isValid, timestamp, updater] = await contract.verifyData(
389
+ ethers.toUtf8Bytes(nodeId),
390
+ contentHash
391
+ );
392
+ console.log("Verification result:", { isValid, timestamp, updater });
393
+ return { isValid, timestamp, updater };
394
+ };
395
+
396
+ // Funzione per scrivere on-chain
397
+ const writeOnChain = async (nodeId, contentHash) => {
398
+ console.log("Writing on chain:", { nodeId, contentHash });
399
+ const signer = await getSigner();
400
+ const contract = new ethers.Contract(
401
+ SHINE_CONTRACT_ADDRESS,
402
+ SHINE_ABI,
403
+ signer
404
+ );
405
+ const tx = await contract.updateData(
406
+ ethers.toUtf8Bytes(nodeId),
407
+ contentHash
408
+ );
409
+ console.log("Transaction sent:", tx.hash);
410
+ const receipt = await tx.wait();
411
+ console.log("Transaction confirmed:", receipt);
412
+ return tx;
413
+ };
414
+
415
+ // Nuova funzione per ottenere l'ultimo record dalla blockchain
416
+ const getLatestRecord = async (nodeId) => {
417
+ const signer = await getSigner();
418
+ const contract = new ethers.Contract(
419
+ SHINE_CONTRACT_ADDRESS,
420
+ SHINE_ABI,
421
+ signer
422
+ );
423
+ const [contentHash, timestamp, updater] = await contract.getLatestRecord(
424
+ ethers.toUtf8Bytes(nodeId)
425
+ );
426
+ console.log("Latest record from blockchain:", {
427
+ nodeId,
428
+ contentHash,
429
+ timestamp,
430
+ updater,
431
+ });
432
+ return { contentHash, timestamp, updater };
433
+ };
434
+
435
+ // Processo SHINE
436
+ if (nodeId && !data) {
437
+ // Caso 1: Utente passa solo il nodo
438
+ gun.get(nodeId).once(async (existingData) => {
439
+ if (!existingData) {
440
+ if (callback) callback({ err: "Node not found in GunDB" });
441
+ return;
442
+ }
443
+
444
+ console.log("existingData", existingData);
445
+
446
+ // Usa il contentHash memorizzato invece di ricalcolarlo
447
+ const contentHash = existingData._contentHash;
448
+ console.log("contentHash", contentHash);
449
+
450
+ if (!contentHash) {
451
+ if (callback) callback({ err: "No content hash found for this node" });
452
+ return;
453
+ }
454
+
455
+ try {
456
+ const { isValid, timestamp, updater } = await verifyOnChain(
457
+ nodeId,
458
+ contentHash
459
+ );
460
+ const latestRecord = await getLatestRecord(nodeId);
461
+
462
+ if (isValid) {
463
+ if (callback)
464
+ callback({
465
+ ok: true,
466
+ message: "Data verified on blockchain",
467
+ timestamp,
468
+ updater,
469
+ latestRecord,
470
+ });
471
+ } else {
472
+ if (callback)
473
+ callback({
474
+ ok: false,
475
+ message: "Data not verified on blockchain",
476
+ latestRecord,
477
+ });
478
+ }
479
+ } catch (error) {
480
+ if (callback) callback({ err: error.message });
481
+ }
482
+ });
483
+ } else if (data && !nodeId) {
484
+ // Caso 2: Utente passa solo il testo (data)
485
+ const newNodeId = Gun.text.random();
486
+ const dataString = JSON.stringify(data);
487
+ const contentHash = ethers.keccak256(ethers.toUtf8Bytes(dataString));
488
+
489
+ gun
490
+ .get(newNodeId)
491
+ .put({ ...data, _contentHash: contentHash }, async (ack) => {
492
+ console.log("ack", ack);
493
+ if (ack.err) {
494
+ if (callback) callback({ err: "Error saving data to GunDB" });
495
+ return;
496
+ }
497
+
498
+ try {
499
+ const tx = await writeOnChain(newNodeId, contentHash);
500
+ if (callback)
501
+ callback({
502
+ ok: true,
503
+ message: "Data written to GunDB and blockchain",
504
+ nodeId: newNodeId,
505
+ txHash: tx.hash,
506
+ });
507
+ } catch (error) {
508
+ if (callback) callback({ err: error.message });
509
+ }
510
+ });
511
+ } else {
512
+ if (callback)
513
+ callback({
514
+ err: "Invalid input. Provide either nodeId or data, not both.",
515
+ });
516
+ }
517
+
518
+ return gun;
519
+ };
520
+
521
+ /**
522
+ * Converts a Gun private key to an Ethereum account.
523
+ * @param {string} gunPrivateKey - The Gun private key in base64url format.
524
+ * @returns {Object} An object containing the Ethereum account and public key.
525
+ */
526
+ Gun.chain.gunToEthAccount = function (gunPrivateKey) {
527
+ // Function to convert base64url to hex
528
+ const base64UrlToHex = (base64url) => {
529
+ const padding = "=".repeat((4 - (base64url.length % 4)) % 4);
530
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
531
+ const binary = atob(base64);
532
+ return Array.from(binary, (char) =>
533
+ char.charCodeAt(0).toString(16).padStart(2, "0")
534
+ ).join("");
535
+ };
536
+
537
+ // Convert Gun private key to hex format
538
+ const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
539
+
540
+ // Create an Ethereum wallet from the private key
541
+ const wallet = new ethers.Wallet(hexPrivateKey);
542
+
543
+ // Get the public address (public key)
544
+ const publicKey = wallet.address;
545
+
546
+ return {
547
+ account: wallet,
548
+ publicKey: publicKey,
549
+ };
550
+ };
551
+
552
+ /**
553
+ * Sets SHINE configuration.
554
+ * @param {Object} abi - The ABI of the SHINE contract.
555
+ * @param {string} optimismSepoliaAddress - The contract address on Optimism Sepolia.
556
+ * @returns {Gun} The Gun instance for chaining.
557
+ */
558
+ Gun.chain.setShineConfig = function (abi, optimismSepoliaAddress) {
559
+ SHINE_ABI = abi;
560
+ SHINE_OPTIMISM_SEPOLIA = optimismSepoliaAddress;
561
+ console.log("SHINE configuration set");
562
+ return this;
563
+ };
564
+
565
+ console.log('Plugin Gun-Eth caricato con successo');
566
+ });
package/src/index.js CHANGED
@@ -340,4 +340,34 @@ Gun.chain.shine = function (chain, nodeId, data, callback) {
340
340
  return gun;
341
341
  };
342
342
 
343
+
344
+ /**
345
+ * Converts a Gun private key to an Ethereum account.
346
+ * @param {string} gunPrivateKey - The Gun private key in base64url format.
347
+ * @returns {Object} An object containing the Ethereum account and public key.
348
+ */
349
+ Gun.chain.gunToEthAccount = function(gunPrivateKey) {
350
+ // Function to convert base64url to hex
351
+ const base64UrlToHex = (base64url) => {
352
+ const padding = "=".repeat((4 - (base64url.length % 4)) % 4);
353
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
354
+ const binary = atob(base64);
355
+ return Array.from(binary, (char) => char.charCodeAt(0).toString(16).padStart(2, "0")).join("");
356
+ };
357
+
358
+ // Convert Gun private key to hex format
359
+ const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
360
+
361
+ // Create an Ethereum wallet from the private key
362
+ const wallet = new ethers.Wallet(hexPrivateKey);
363
+
364
+ // Get the public address (public key)
365
+ const publicKey = wallet.address;
366
+
367
+ return {
368
+ account: wallet,
369
+ publicKey: publicKey
370
+ };
371
+ };
372
+
343
373
  module.exports = Gun;