gun-eth 1.3.2 → 1.3.4

Sign up to get free protection for your applications and to get access to all the features.
package/src/index.js CHANGED
@@ -1,346 +1,5 @@
1
- const Gun = require("gun/gun");
2
- const SEA = require("gun/sea");
3
- const ethers = require("ethers");
4
- const SHINE = require("../abis/SHINE.json");
5
-
6
- const SHINE_ABI = SHINE.abi;
7
- const SHINE_OPTIMISM_SEPOLIA = SHINE.address;
8
-
9
- let SHINE_CONTRACT_ADDRESS;
10
-
11
- let rpcUrl = "";
12
- let privateKey = "";
13
-
14
- const MESSAGE_TO_SIGN = "Accesso a GunDB con Ethereum";
15
-
16
- /**
17
- * Funzione per ottenere il signer
18
- * @returns {Promise<ethers.Signer>} Il signer.
19
- */
20
- const getSigner = async () => {
21
- if (rpcUrl && privateKey) {
22
- // Modalità standalone
23
- const provider = new ethers.JsonRpcProvider(rpcUrl);
24
- return new ethers.Wallet(privateKey, provider);
25
- } else if (
26
- typeof window !== "undefined" &&
27
- typeof window.ethereum !== "undefined"
28
- ) {
29
- // Modalità browser
30
- await window.ethereum.request({ method: "eth_requestAccounts" });
31
- const provider = new ethers.BrowserProvider(window.ethereum);
32
- return provider.getSigner();
33
- } else {
34
- throw new Error("No valid Ethereum provider found");
35
- }
36
- };
37
-
38
- /**
39
- * Sets standalone configuration for Gun.
40
- * @param {string} newRpcUrl - The new RPC URL.
41
- * @param {string} newPrivateKey - The new private key.
42
- * @returns {Gun} The Gun instance for chaining.
43
- */
44
- Gun.chain.setStandaloneConfig = function (newRpcUrl, newPrivateKey) {
45
- rpcUrl = newRpcUrl;
46
- privateKey = newPrivateKey;
47
- console.log("Standalone configuration set");
48
- return this;
49
- };
50
-
51
- /**
52
- * Verifies an Ethereum signature.
53
- * @param {string} message - The original message that was signed.
54
- * @param {string} signature - The signature to verify.
55
- * @returns {Promise<string|null>} The recovered address or null if verification fails.
56
- */
57
- Gun.chain.verifySignature = async function (message, signature) {
58
- try {
59
- const recoveredAddress = ethers.verifyMessage(message, signature);
60
- return recoveredAddress;
61
- } catch (error) {
62
- console.error("Error verifying signature:", error);
63
- return null;
64
- }
65
- };
66
-
67
- /**
68
- * Generates a password from a signature.
69
- * @param {string} signature - The signature to derive the password from.
70
- * @returns {string|null} The generated password or null if generation fails.
71
- */
72
- Gun.chain.generatePassword = function (signature) {
73
- try {
74
- const hexSignature = ethers.hexlify(signature);
75
- const hash = ethers.keccak256(hexSignature);
76
- console.log("Generated password:", hash);
77
- return hash;
78
- } catch (error) {
79
- console.error("Error generating password:", error);
80
- return null;
81
- }
82
- };
83
-
84
- /**
85
- * Creates an Ethereum signature for a given message.
86
- * @param {string} message - The message to sign.
87
- * @returns {Promise<string|null>} The signature or null if signing fails.
88
- */
89
- Gun.chain.createSignature = async function (message) {
90
- try {
91
- // Verifica se il messaggio è uguale a MESSAGE_TO_SIGN
92
- if (message !== MESSAGE_TO_SIGN) {
93
- throw new Error("Invalid message, valid message is: " + MESSAGE_TO_SIGN);
94
- }
95
- const signer = await getSigner();
96
- const signature = await signer.signMessage(message);
97
- console.log("Signature created:", signature);
98
- return signature;
99
- } catch (error) {
100
- console.error("Error creating signature:", error);
101
- return null;
102
- }
103
- };
104
-
105
- /**
106
- * Creates and stores an encrypted key pair for a given address.
107
- * @param {string} address - The Ethereum address to associate with the key pair.
108
- * @param {string} signature - The signature to use for encryption.
109
- * @returns {Promise<void>}
110
- */
111
- Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
112
- try {
113
- const gun = this;
114
- const pair = await SEA.pair();
115
- const encryptedPair = await SEA.encrypt(JSON.stringify(pair), signature);
116
- await gun.get("gun-eth").get("users").get(address).put({ encryptedPair });
117
- console.log("Encrypted pair stored for:", address);
118
- } catch (error) {
119
- console.error("Error creating and storing encrypted pair:", error);
120
- }
121
- };
122
-
123
- /**
124
- * Retrieves and decrypts a stored key pair for a given address.
125
- * @param {string} address - The Ethereum address associated with the key pair.
126
- * @param {string} signature - The signature to use for decryption.
127
- * @returns {Promise<Object|null>} The decrypted key pair or null if retrieval fails.
128
- */
129
- Gun.chain.getAndDecryptPair = async function (address, signature) {
130
- try {
131
- const gun = this;
132
- const encryptedData = await gun
133
- .get("gun-eth")
134
- .get("users")
135
- .get(address)
136
- .get("encryptedPair")
137
- .then();
138
- if (!encryptedData) {
139
- throw new Error("No encrypted data found for this address");
140
- }
141
- const decryptedPair = await SEA.decrypt(encryptedData, signature);
142
- console.log(decryptedPair);
143
- return decryptedPair;
144
- } catch (error) {
145
- console.error("Error retrieving and decrypting pair:", error);
146
- return null;
147
- }
148
- };
149
-
150
- /**
151
- * SHINE (Secure Hybrid Information and Network Environment) functionality.
152
- * @param {string} chain - The blockchain to use (e.g., "optimismSepolia").
153
- * @param {string} nodeId - The ID of the node to verify or write.
154
- * @param {Object} data - The data to write (if writing).
155
- * @param {Function} callback - Callback function to handle the result.
156
- * @returns {Gun} The Gun instance for chaining.
157
- */
158
- Gun.chain.shine = function (chain, nodeId, data, callback) {
159
- console.log("SHINE plugin called with:", { chain, nodeId, data });
160
-
161
- if (typeof callback !== "function") {
162
- console.error("Callback must be a function");
163
- return this;
164
- }
165
-
166
- const gun = this;
167
-
168
- // Seleziona l'indirizzo basato sulla catena
169
- if (chain === "optimismSepolia") {
170
- SHINE_CONTRACT_ADDRESS = SHINE_OPTIMISM_SEPOLIA;
171
- } else {
172
- throw new Error("Chain not supported");
173
- }
174
- // Funzione per verificare on-chain
175
- const verifyOnChain = async (nodeId, contentHash) => {
176
- console.log("Verifying on chain:", { nodeId, contentHash });
177
- const signer = await getSigner();
178
- const contract = new ethers.Contract(
179
- SHINE_CONTRACT_ADDRESS,
180
- SHINE_ABI,
181
- signer
182
- );
183
- const [isValid, timestamp, updater] = await contract.verifyData(
184
- ethers.toUtf8Bytes(nodeId),
185
- contentHash
186
- );
187
- console.log("Verification result:", { isValid, timestamp, updater });
188
- return { isValid, timestamp, updater };
189
- };
190
-
191
- // Funzione per scrivere on-chain
192
- const writeOnChain = async (nodeId, contentHash) => {
193
- console.log("Writing on chain:", { nodeId, contentHash });
194
- const signer = await getSigner();
195
- const contract = new ethers.Contract(
196
- SHINE_CONTRACT_ADDRESS,
197
- SHINE_ABI,
198
- signer
199
- );
200
- const tx = await contract.updateData(
201
- ethers.toUtf8Bytes(nodeId),
202
- contentHash
203
- );
204
- console.log("Transaction sent:", tx.hash);
205
- const receipt = await tx.wait();
206
- console.log("Transaction confirmed:", receipt);
207
- return tx;
208
- };
209
-
210
- // Nuova funzione per ottenere l'ultimo record dalla blockchain
211
- const getLatestRecord = async (nodeId) => {
212
- const signer = await getSigner();
213
- const contract = new ethers.Contract(
214
- SHINE_CONTRACT_ADDRESS,
215
- SHINE_ABI,
216
- signer
217
- );
218
- const [contentHash, timestamp, updater] = await contract.getLatestRecord(
219
- ethers.toUtf8Bytes(nodeId)
220
- );
221
- console.log("Latest record from blockchain:", {
222
- nodeId,
223
- contentHash,
224
- timestamp,
225
- updater,
226
- });
227
- return { contentHash, timestamp, updater };
228
- };
229
-
230
- // Processo SHINE
231
- if (nodeId && !data) {
232
- // Caso 1: Utente passa solo il nodo
233
- gun.get(nodeId).once(async (existingData) => {
234
- if (!existingData) {
235
- if (callback) callback({ err: "Node not found in GunDB" });
236
- return;
237
- }
238
-
239
- console.log("existingData", existingData);
240
-
241
- // Usa il contentHash memorizzato invece di ricalcolarlo
242
- const contentHash = existingData._contentHash;
243
- console.log("contentHash", contentHash);
244
-
245
- if (!contentHash) {
246
- if (callback) callback({ err: "No content hash found for this node" });
247
- return;
248
- }
249
-
250
- try {
251
- const { isValid, timestamp, updater } = await verifyOnChain(
252
- nodeId,
253
- contentHash
254
- );
255
- const latestRecord = await getLatestRecord(nodeId);
256
-
257
- if (isValid) {
258
- if (callback)
259
- callback({
260
- ok: true,
261
- message: "Data verified on blockchain",
262
- timestamp,
263
- updater,
264
- latestRecord,
265
- });
266
- } else {
267
- if (callback)
268
- callback({
269
- ok: false,
270
- message: "Data not verified on blockchain",
271
- latestRecord,
272
- });
273
- }
274
- } catch (error) {
275
- if (callback) callback({ err: error.message });
276
- }
277
- });
278
- } else if (data && !nodeId) {
279
- // Caso 2: Utente passa solo il testo (data)
280
- const newNodeId = Gun.text.random();
281
- const dataString = JSON.stringify(data);
282
- const contentHash = ethers.keccak256(ethers.toUtf8Bytes(dataString));
283
-
284
- gun
285
- .get(newNodeId)
286
- .put({ ...data, _contentHash: contentHash }, async (ack) => {
287
- console.log("ack", ack);
288
- if (ack.err) {
289
- if (callback) callback({ err: "Error saving data to GunDB" });
290
- return;
291
- }
292
-
293
- try {
294
- const tx = await writeOnChain(newNodeId, contentHash);
295
- if (callback)
296
- callback({
297
- ok: true,
298
- message: "Data written to GunDB and blockchain",
299
- nodeId: newNodeId,
300
- txHash: tx.hash,
301
- });
302
- } catch (error) {
303
- if (callback) callback({ err: error.message });
304
- }
305
- });
306
- } else {
307
- if (callback)
308
- callback({
309
- err: "Invalid input. Provide either nodeId or data, not both.",
310
- });
311
- }
312
-
313
- return gun;
314
- };
315
-
316
-
317
- /**
318
- * Converts a Gun private key to an Ethereum account.
319
- * @param {string} gunPrivateKey - The Gun private key in base64url format.
320
- * @returns {Object} An object containing the Ethereum account and public key.
321
- */
322
- Gun.chain.gunToEthAccount = function(gunPrivateKey) {
323
- // Function to convert base64url to hex
324
- const base64UrlToHex = (base64url) => {
325
- const padding = "=".repeat((4 - (base64url.length % 4)) % 4);
326
- const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
327
- const binary = atob(base64);
328
- return Array.from(binary, (char) => char.charCodeAt(0).toString(16).padStart(2, "0")).join("");
329
- };
330
-
331
- // Convert Gun private key to hex format
332
- const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
333
-
334
- // Create an Ethereum wallet from the private key
335
- const wallet = new ethers.Wallet(hexPrivateKey);
336
-
337
- // Get the public address (public key)
338
- const publicKey = wallet.address;
339
-
340
- return {
341
- account: wallet,
342
- publicKey: publicKey
343
- };
344
- };
345
-
346
- module.exports = Gun;
1
+ if (typeof window === 'undefined') {
2
+ module.exports = require('./node/gun-eth-node.js');
3
+ } else {
4
+ module.exports = require('./browser/gun-eth-browser.js');
5
+ }
@@ -0,0 +1,347 @@
1
+ const Gun = require("gun/gun");
2
+ const SEA = require("gun/sea");
3
+ const ethers = require("ethers");
4
+ const SHINE = require("../abis/SHINE.json");
5
+
6
+ const SHINE_ABI = SHINE.abi;
7
+ const SHINE_OPTIMISM_SEPOLIA = SHINE.address;
8
+
9
+ let SHINE_CONTRACT_ADDRESS;
10
+
11
+ let rpcUrl = "";
12
+ let privateKey = "";
13
+
14
+ const MESSAGE_TO_SIGN = "Accesso a GunDB con Ethereum";
15
+
16
+ /**
17
+ * Funzione per ottenere il signer
18
+ * @returns {Promise<ethers.Signer>} Il signer.
19
+ */
20
+ const getSigner = async () => {
21
+ if (rpcUrl && privateKey) {
22
+ // Modalità standalone
23
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
24
+ return new ethers.Wallet(privateKey, provider);
25
+ } else if (
26
+ typeof window !== "undefined" &&
27
+ typeof window.ethereum !== "undefined"
28
+ ) {
29
+ // Modalità browser
30
+ await window.ethereum.request({ method: "eth_requestAccounts" });
31
+ const provider = new ethers.BrowserProvider(window.ethereum);
32
+ return provider.getSigner();
33
+ } else {
34
+ throw new Error("No valid Ethereum provider found");
35
+ }
36
+ };
37
+
38
+ /**
39
+ * Sets standalone configuration for Gun.
40
+ * @param {string} newRpcUrl - The new RPC URL.
41
+ * @param {string} newPrivateKey - The new private key.
42
+ * @returns {Gun} The Gun instance for chaining.
43
+ */
44
+ Gun.chain.setStandaloneConfig = function (newRpcUrl, newPrivateKey) {
45
+ rpcUrl = newRpcUrl;
46
+ privateKey = newPrivateKey;
47
+ console.log("Standalone configuration set");
48
+ return this;
49
+ };
50
+
51
+ /**
52
+ * Verifies an Ethereum signature.
53
+ * @param {string} message - The original message that was signed.
54
+ * @param {string} signature - The signature to verify.
55
+ * @returns {Promise<string|null>} The recovered address or null if verification fails.
56
+ */
57
+ Gun.chain.verifySignature = async function (message, signature) {
58
+ try {
59
+ const recoveredAddress = ethers.verifyMessage(message, signature);
60
+ return recoveredAddress;
61
+ } catch (error) {
62
+ console.error("Error verifying signature:", error);
63
+ return null;
64
+ }
65
+ };
66
+
67
+ /**
68
+ * Generates a password from a signature.
69
+ * @param {string} signature - The signature to derive the password from.
70
+ * @returns {string|null} The generated password or null if generation fails.
71
+ */
72
+ Gun.chain.generatePassword = function (signature) {
73
+ try {
74
+ const hexSignature = ethers.hexlify(signature);
75
+ const hash = ethers.keccak256(hexSignature);
76
+ console.log("Generated password:", hash);
77
+ return hash;
78
+ } catch (error) {
79
+ console.error("Error generating password:", error);
80
+ return null;
81
+ }
82
+ };
83
+
84
+ /**
85
+ * Creates an Ethereum signature for a given message.
86
+ * @param {string} message - The message to sign.
87
+ * @returns {Promise<string|null>} The signature or null if signing fails.
88
+ */
89
+ Gun.chain.createSignature = async function (message) {
90
+ try {
91
+ // Verifica se il messaggio è uguale a MESSAGE_TO_SIGN
92
+ if (message !== MESSAGE_TO_SIGN) {
93
+ throw new Error("Invalid message, valid message is: " + MESSAGE_TO_SIGN);
94
+ }
95
+ const signer = await getSigner();
96
+ const signature = await signer.signMessage(message);
97
+ console.log("Signature created:", signature);
98
+ return signature;
99
+ } catch (error) {
100
+ console.error("Error creating signature:", error);
101
+ return null;
102
+ }
103
+ };
104
+
105
+ /**
106
+ * Creates and stores an encrypted key pair for a given address.
107
+ * @param {string} address - The Ethereum address to associate with the key pair.
108
+ * @param {string} signature - The signature to use for encryption.
109
+ * @returns {Promise<void>}
110
+ */
111
+ Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
112
+ try {
113
+ const gun = this;
114
+ const pair = await SEA.pair();
115
+ const encryptedPair = await SEA.encrypt(JSON.stringify(pair), signature);
116
+ await gun.get("gun-eth").get("users").get(address).put({ encryptedPair });
117
+ console.log("Encrypted pair stored for:", address);
118
+ } catch (error) {
119
+ console.error("Error creating and storing encrypted pair:", error);
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Retrieves and decrypts a stored key pair for a given address.
125
+ * @param {string} address - The Ethereum address associated with the key pair.
126
+ * @param {string} signature - The signature to use for decryption.
127
+ * @returns {Promise<Object|null>} The decrypted key pair or null if retrieval fails.
128
+ */
129
+ Gun.chain.getAndDecryptPair = async function (address, signature) {
130
+ try {
131
+ const gun = this;
132
+ const encryptedData = await gun
133
+ .get("gun-eth")
134
+ .get("users")
135
+ .get(address)
136
+ .get("encryptedPair")
137
+ .then();
138
+ if (!encryptedData) {
139
+ throw new Error("No encrypted data found for this address");
140
+ }
141
+ const decryptedPair = await SEA.decrypt(encryptedData, signature);
142
+ console.log(decryptedPair);
143
+ return decryptedPair;
144
+ } catch (error) {
145
+ console.error("Error retrieving and decrypting pair:", error);
146
+ return null;
147
+ }
148
+ };
149
+
150
+ /**
151
+ * SHINE (Secure Hybrid Information and Network Environment) functionality.
152
+ * @param {string} chain - The blockchain to use (e.g., "optimismSepolia").
153
+ * @param {string} nodeId - The ID of the node to verify or write.
154
+ * @param {Object} data - The data to write (if writing).
155
+ * @param {Function} callback - Callback function to handle the result.
156
+ * @returns {Gun} The Gun instance for chaining.
157
+ */
158
+ Gun.chain.shine = function (chain, nodeId, data, callback) {
159
+ console.log("SHINE plugin called with:", { chain, nodeId, data });
160
+
161
+ if (typeof callback !== "function") {
162
+ console.error("Callback must be a function");
163
+ return this;
164
+ }
165
+
166
+ const gun = this;
167
+
168
+ // Seleziona l'indirizzo basato sulla catena
169
+ if (chain === "optimismSepolia") {
170
+ SHINE_CONTRACT_ADDRESS = SHINE_OPTIMISM_SEPOLIA;
171
+ } else {
172
+ throw new Error("Chain not supported");
173
+ }
174
+ // Funzione per verificare on-chain
175
+ const verifyOnChain = async (nodeId, contentHash) => {
176
+ console.log("Verifying on chain:", { nodeId, contentHash });
177
+ const signer = await getSigner();
178
+ const contract = new ethers.Contract(
179
+ SHINE_CONTRACT_ADDRESS,
180
+ SHINE_ABI,
181
+ signer
182
+ );
183
+ const [isValid, timestamp, updater] = await contract.verifyData(
184
+ ethers.toUtf8Bytes(nodeId),
185
+ contentHash
186
+ );
187
+ console.log("Verification result:", { isValid, timestamp, updater });
188
+ return { isValid, timestamp, updater };
189
+ };
190
+
191
+ // Funzione per scrivere on-chain
192
+ const writeOnChain = async (nodeId, contentHash) => {
193
+ console.log("Writing on chain:", { nodeId, contentHash });
194
+ const signer = await getSigner();
195
+ const contract = new ethers.Contract(
196
+ SHINE_CONTRACT_ADDRESS,
197
+ SHINE_ABI,
198
+ signer
199
+ );
200
+ const tx = await contract.updateData(
201
+ ethers.toUtf8Bytes(nodeId),
202
+ contentHash
203
+ );
204
+ console.log("Transaction sent:", tx.hash);
205
+ const receipt = await tx.wait();
206
+ console.log("Transaction confirmed:", receipt);
207
+ return tx;
208
+ };
209
+
210
+ // Nuova funzione per ottenere l'ultimo record dalla blockchain
211
+ const getLatestRecord = async (nodeId) => {
212
+ const signer = await getSigner();
213
+ const contract = new ethers.Contract(
214
+ SHINE_CONTRACT_ADDRESS,
215
+ SHINE_ABI,
216
+ signer
217
+ );
218
+ const [contentHash, timestamp, updater] = await contract.getLatestRecord(
219
+ ethers.toUtf8Bytes(nodeId)
220
+ );
221
+ console.log("Latest record from blockchain:", {
222
+ nodeId,
223
+ contentHash,
224
+ timestamp,
225
+ updater,
226
+ });
227
+ return { contentHash, timestamp, updater };
228
+ };
229
+
230
+ // Processo SHINE
231
+ if (nodeId && !data) {
232
+ // Caso 1: Utente passa solo il nodo
233
+ gun.get(nodeId).once(async (existingData) => {
234
+ if (!existingData) {
235
+ if (callback) callback({ err: "Node not found in GunDB" });
236
+ return;
237
+ }
238
+
239
+ console.log("existingData", existingData);
240
+
241
+ // Usa il contentHash memorizzato invece di ricalcolarlo
242
+ const contentHash = existingData._contentHash;
243
+ console.log("contentHash", contentHash);
244
+
245
+ if (!contentHash) {
246
+ if (callback) callback({ err: "No content hash found for this node" });
247
+ return;
248
+ }
249
+
250
+ try {
251
+ const { isValid, timestamp, updater } = await verifyOnChain(
252
+ nodeId,
253
+ contentHash
254
+ );
255
+ const latestRecord = await getLatestRecord(nodeId);
256
+
257
+ if (isValid) {
258
+ if (callback)
259
+ callback({
260
+ ok: true,
261
+ message: "Data verified on blockchain",
262
+ timestamp,
263
+ updater,
264
+ latestRecord,
265
+ });
266
+ } else {
267
+ if (callback)
268
+ callback({
269
+ ok: false,
270
+ message: "Data not verified on blockchain",
271
+ latestRecord,
272
+ });
273
+ }
274
+ } catch (error) {
275
+ if (callback) callback({ err: error.message });
276
+ }
277
+ });
278
+ } else if (data && !nodeId) {
279
+ // Caso 2: Utente passa solo il testo (data)
280
+ const newNodeId = Gun.text.random();
281
+ const dataString = JSON.stringify(data);
282
+ const contentHash = ethers.keccak256(ethers.toUtf8Bytes(dataString));
283
+
284
+ gun
285
+ .get(newNodeId)
286
+ .put({ ...data, _contentHash: contentHash }, async (ack) => {
287
+ console.log("ack", ack);
288
+ if (ack.err) {
289
+ if (callback) callback({ err: "Error saving data to GunDB" });
290
+ return;
291
+ }
292
+
293
+ try {
294
+ const tx = await writeOnChain(newNodeId, contentHash);
295
+ if (callback)
296
+ callback({
297
+ ok: true,
298
+ message: "Data written to GunDB and blockchain",
299
+ nodeId: newNodeId,
300
+ txHash: tx.hash,
301
+ });
302
+ } catch (error) {
303
+ if (callback) callback({ err: error.message });
304
+ }
305
+ });
306
+ } else {
307
+ if (callback)
308
+ callback({
309
+ err: "Invalid input. Provide either nodeId or data, not both.",
310
+ });
311
+ }
312
+
313
+ return gun;
314
+ };
315
+
316
+ /**
317
+ * Converts a Gun private key to an Ethereum account.
318
+ * @param {string} gunPrivateKey - The Gun private key in base64url format.
319
+ * @returns {Object} An object containing the Ethereum account and public key.
320
+ */
321
+ Gun.chain.gunToEthAccount = function (gunPrivateKey) {
322
+ // Function to convert base64url to hex
323
+ const base64UrlToHex = (base64url) => {
324
+ const padding = "=".repeat((4 - (base64url.length % 4)) % 4);
325
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
326
+ const binary = atob(base64);
327
+ return Array.from(binary, (char) =>
328
+ char.charCodeAt(0).toString(16).padStart(2, "0")
329
+ ).join("");
330
+ };
331
+
332
+ // Convert Gun private key to hex format
333
+ const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
334
+
335
+ // Create an Ethereum wallet from the private key
336
+ const wallet = new ethers.Wallet(hexPrivateKey);
337
+
338
+ // Get the public address (public key)
339
+ const publicKey = wallet.address;
340
+
341
+ return {
342
+ account: wallet,
343
+ publicKey: publicKey,
344
+ };
345
+ };
346
+
347
+ module.exports = Gun;