vikramronavrsc commited on
Commit
635881c
·
verified ·
1 Parent(s): 5004d8f

Upload 2 files

Browse files
Files changed (2) hide show
  1. blockchain-utils-metamask.py +529 -0
  2. metamask-component.py +250 -0
blockchain-utils-metamask.py ADDED
@@ -0,0 +1,529 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # blockchain_utils_metamask.py
2
+ import hashlib
3
+ import json
4
+ import os
5
+ import time
6
+ import streamlit as st
7
+ from web3 import Web3
8
+
9
+ class BlockchainManagerMetaMask:
10
+ def __init__(self,
11
+ contract_address=None,
12
+ network_id=None):
13
+ """
14
+ Initialize blockchain connection for MetaMask integration.
15
+
16
+ Args:
17
+ contract_address: Address of the deployed RAG Document Verifier contract
18
+ network_id: Network ID from MetaMask (e.g., '1' for Ethereum Mainnet)
19
+ """
20
+ self.contract_address = contract_address
21
+ self.network_id = network_id
22
+
23
+ # MetaMask connection status
24
+ self.is_connected = False
25
+ self.user_address = None
26
+
27
+ # Contract ABI - Load from file or define here
28
+ self.abi = [
29
+ {
30
+ "inputs": [],
31
+ "stateMutability": "nonpayable",
32
+ "type": "constructor"
33
+ },
34
+ {
35
+ "anonymous": False,
36
+ "inputs": [
37
+ {
38
+ "indexed": True,
39
+ "internalType": "address",
40
+ "name": "user",
41
+ "type": "address"
42
+ },
43
+ {
44
+ "indexed": True,
45
+ "internalType": "string",
46
+ "name": "documentId",
47
+ "type": "string"
48
+ },
49
+ {
50
+ "indexed": False,
51
+ "internalType": "string",
52
+ "name": "documentHash",
53
+ "type": "string"
54
+ },
55
+ {
56
+ "indexed": False,
57
+ "internalType": "uint256",
58
+ "name": "timestamp",
59
+ "type": "uint256"
60
+ }
61
+ ],
62
+ "name": "DocumentVerified",
63
+ "type": "event"
64
+ },
65
+ {
66
+ "anonymous": False,
67
+ "inputs": [
68
+ {
69
+ "indexed": True,
70
+ "internalType": "address",
71
+ "name": "user",
72
+ "type": "address"
73
+ },
74
+ {
75
+ "indexed": True,
76
+ "internalType": "string",
77
+ "name": "queryId",
78
+ "type": "string"
79
+ },
80
+ {
81
+ "indexed": False,
82
+ "internalType": "string",
83
+ "name": "queryHash",
84
+ "type": "string"
85
+ },
86
+ {
87
+ "indexed": False,
88
+ "internalType": "uint256",
89
+ "name": "timestamp",
90
+ "type": "uint256"
91
+ }
92
+ ],
93
+ "name": "QueryLogged",
94
+ "type": "event"
95
+ },
96
+ {
97
+ "inputs": [
98
+ {
99
+ "internalType": "string",
100
+ "name": "",
101
+ "type": "string"
102
+ }
103
+ ],
104
+ "name": "documentHashes",
105
+ "outputs": [
106
+ {
107
+ "internalType": "string",
108
+ "name": "",
109
+ "type": "string"
110
+ }
111
+ ],
112
+ "stateMutability": "view",
113
+ "type": "function"
114
+ },
115
+ {
116
+ "inputs": [
117
+ {
118
+ "internalType": "string",
119
+ "name": "documentId",
120
+ "type": "string"
121
+ }
122
+ ],
123
+ "name": "getDocumentHash",
124
+ "outputs": [
125
+ {
126
+ "internalType": "string",
127
+ "name": "",
128
+ "type": "string"
129
+ }
130
+ ],
131
+ "stateMutability": "view",
132
+ "type": "function"
133
+ },
134
+ {
135
+ "inputs": [
136
+ {
137
+ "internalType": "string",
138
+ "name": "queryId",
139
+ "type": "string"
140
+ }
141
+ ],
142
+ "name": "getQueryInfo",
143
+ "outputs": [
144
+ {
145
+ "internalType": "string",
146
+ "name": "",
147
+ "type": "string"
148
+ }
149
+ ],
150
+ "stateMutability": "view",
151
+ "type": "function"
152
+ },
153
+ {
154
+ "inputs": [
155
+ {
156
+ "internalType": "string",
157
+ "name": "queryId",
158
+ "type": "string"
159
+ },
160
+ {
161
+ "internalType": "string",
162
+ "name": "queryHash",
163
+ "type": "string"
164
+ }
165
+ ],
166
+ "name": "logQuery",
167
+ "outputs": [],
168
+ "stateMutability": "nonpayable",
169
+ "type": "function"
170
+ },
171
+ {
172
+ "inputs": [
173
+ {
174
+ "internalType": "string",
175
+ "name": "",
176
+ "type": "string"
177
+ }
178
+ ],
179
+ "name": "queryHashes",
180
+ "outputs": [
181
+ {
182
+ "internalType": "string",
183
+ "name": "",
184
+ "type": "string"
185
+ }
186
+ ],
187
+ "stateMutability": "view",
188
+ "type": "function"
189
+ },
190
+ {
191
+ "inputs": [
192
+ {
193
+ "internalType": "string",
194
+ "name": "documentId",
195
+ "type": "string"
196
+ },
197
+ {
198
+ "internalType": "string",
199
+ "name": "documentHash",
200
+ "type": "string"
201
+ }
202
+ ],
203
+ "name": "verifyDocument",
204
+ "outputs": [],
205
+ "stateMutability": "nonpayable",
206
+ "type": "function"
207
+ }
208
+ ]
209
+
210
+ # Flag to track if we're in a browser environment
211
+ self.is_browser_env = self._check_browser_environment()
212
+
213
+ # Generate JavaScript for web3 interaction with MetaMask
214
+ self.web3_js = self._generate_web3_js()
215
+
216
+ def _check_browser_environment(self):
217
+ """Check if we're running in a browser environment."""
218
+ # This is a simplified check - in a real app, you'd detect this differently
219
+ return True
220
+
221
+ def _generate_web3_js(self):
222
+ """Generate JavaScript code for MetaMask interaction."""
223
+ js_code = """
224
+ <script>
225
+ async function verifyDocumentWithMetaMask(documentId, documentHash) {
226
+ if (typeof window.ethereum === 'undefined') {
227
+ return {
228
+ status: false,
229
+ error: "MetaMask is not installed. Please install MetaMask and try again."
230
+ };
231
+ }
232
+
233
+ try {
234
+ // Request account access if needed
235
+ const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
236
+ const account = accounts[0];
237
+
238
+ // Create web3 instance
239
+ const web3 = new Web3(window.ethereum);
240
+
241
+ // Create contract instance
242
+ const contractABI = CONTRACT_ABI_PLACEHOLDER;
243
+ const contractAddress = 'CONTRACT_ADDRESS_PLACEHOLDER';
244
+ const contract = new web3.eth.Contract(contractABI, contractAddress);
245
+
246
+ // Prepare and send transaction
247
+ const tx = await contract.methods.verifyDocument(documentId, documentHash).send({
248
+ from: account
249
+ });
250
+
251
+ return {
252
+ status: true,
253
+ tx_hash: tx.transactionHash,
254
+ document_id: documentId,
255
+ document_hash: documentHash,
256
+ block_number: tx.blockNumber,
257
+ status_code: tx.status ? 1 : 0
258
+ };
259
+ } catch (error) {
260
+ console.error("Error verifying document:", error);
261
+ return {
262
+ status: false,
263
+ error: error.message
264
+ };
265
+ }
266
+ }
267
+
268
+ async function logQueryWithMetaMask(queryId, queryHash) {
269
+ if (typeof window.ethereum === 'undefined') {
270
+ return {
271
+ status: false,
272
+ error: "MetaMask is not installed. Please install MetaMask and try again."
273
+ };
274
+ }
275
+
276
+ try {
277
+ // Request account access if needed
278
+ const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
279
+ const account = accounts[0];
280
+
281
+ // Create web3 instance
282
+ const web3 = new Web3(window.ethereum);
283
+
284
+ // Create contract instance
285
+ const contractABI = CONTRACT_ABI_PLACEHOLDER;
286
+ const contractAddress = 'CONTRACT_ADDRESS_PLACEHOLDER';
287
+ const contract = new web3.eth.Contract(contractABI, contractAddress);
288
+
289
+ // Prepare and send transaction
290
+ const tx = await contract.methods.logQuery(queryId, queryHash).send({
291
+ from: account
292
+ });
293
+
294
+ return {
295
+ status: true,
296
+ tx_hash: tx.transactionHash,
297
+ query_id: queryId,
298
+ query_hash: queryHash,
299
+ block_number: tx.blockNumber,
300
+ status_code: tx.status ? 1 : 0
301
+ };
302
+ } catch (error) {
303
+ console.error("Error logging query:", error);
304
+ return {
305
+ status: false,
306
+ error: error.message
307
+ };
308
+ }
309
+ }
310
+
311
+ async function checkDocumentVerifiedWithMetaMask(documentId) {
312
+ if (typeof window.ethereum === 'undefined') {
313
+ return {
314
+ status: false,
315
+ error: "MetaMask is not installed. Please install MetaMask and try again."
316
+ };
317
+ }
318
+
319
+ try {
320
+ // Create web3 instance
321
+ const web3 = new Web3(window.ethereum);
322
+
323
+ // Create contract instance
324
+ const contractABI = CONTRACT_ABI_PLACEHOLDER;
325
+ const contractAddress = 'CONTRACT_ADDRESS_PLACEHOLDER';
326
+ const contract = new web3.eth.Contract(contractABI, contractAddress);
327
+
328
+ // Call the contract
329
+ const result = await contract.methods.getDocumentHash(documentId).call();
330
+
331
+ return {
332
+ status: true,
333
+ document_id: documentId,
334
+ document_hash: result,
335
+ is_verified: result !== ""
336
+ };
337
+ } catch (error) {
338
+ console.error("Error checking document:", error);
339
+ return {
340
+ status: false,
341
+ error: error.message
342
+ };
343
+ }
344
+ }
345
+ </script>
346
+ """
347
+
348
+ # Replace placeholders with actual values
349
+ js_code = js_code.replace('CONTRACT_ABI_PLACEHOLDER', json.dumps(self.abi))
350
+ if self.contract_address:
351
+ js_code = js_code.replace('CONTRACT_ADDRESS_PLACEHOLDER', self.contract_address)
352
+
353
+ return js_code
354
+
355
+ def update_connection(self, is_connected, user_address=None, network_id=None):
356
+ """Update the connection status with MetaMask info."""
357
+ self.is_connected = is_connected
358
+ self.user_address = user_address
359
+ if network_id:
360
+ self.network_id = network_id
361
+
362
+ def compute_file_hash(self, file_path):
363
+ """
364
+ Compute the SHA-256 hash of a file.
365
+
366
+ Args:
367
+ file_path: Path to the file
368
+
369
+ Returns:
370
+ str: Hexadecimal hash of the file
371
+ """
372
+ sha256_hash = hashlib.sha256()
373
+
374
+ with open(file_path, "rb") as f:
375
+ # Read and update hash in chunks for memory efficiency
376
+ for byte_block in iter(lambda: f.read(4096), b""):
377
+ sha256_hash.update(byte_block)
378
+
379
+ return sha256_hash.hexdigest()
380
+
381
+ def verify_document(self, document_id, file_path):
382
+ """
383
+ Verify a document by storing its hash on the blockchain using MetaMask.
384
+
385
+ In a real Streamlit app, this would generate HTML/JS to call MetaMask.
386
+ For this example, we'll use placeholders.
387
+
388
+ Args:
389
+ document_id: Unique identifier for the document
390
+ file_path: Path to the document file
391
+
392
+ Returns:
393
+ dict: Transaction status info
394
+ """
395
+ if not self.is_connected:
396
+ return {
397
+ "status": False,
398
+ "error": "MetaMask is not connected. Please connect your wallet first."
399
+ }
400
+
401
+ if not self.contract_address:
402
+ return {
403
+ "status": False,
404
+ "error": "Contract address is not set."
405
+ }
406
+
407
+ try:
408
+ # Compute document hash
409
+ document_hash = self.compute_file_hash(file_path)
410
+
411
+ # In a real app, we would use JavaScript to call MetaMask
412
+ # For this example, we'll return a simulated successful response
413
+
414
+ # Display MetaMask interaction info
415
+ st.info("Please confirm the transaction in your MetaMask wallet...")
416
+
417
+ # Simulate transaction process time
418
+ with st.spinner("Waiting for transaction confirmation..."):
419
+ time.sleep(3) # Simulate transaction time
420
+
421
+ # Simulate successful transaction
422
+ tx_hash = "0x" + "".join([format(i, "02x") for i in os.urandom(32)])
423
+ block_number = 12345678
424
+
425
+ return {
426
+ "status": True,
427
+ "tx_hash": tx_hash,
428
+ "document_id": document_id,
429
+ "document_hash": document_hash,
430
+ "block_number": block_number,
431
+ "status": 1 # Success
432
+ }
433
+
434
+ except Exception as e:
435
+ st.error(f"Error during verification: {str(e)}")
436
+ return {
437
+ "status": False,
438
+ "error": str(e)
439
+ }
440
+
441
+ def check_document_verified(self, document_id):
442
+ """
443
+ Check if a document has already been verified on the blockchain.
444
+
445
+ Args:
446
+ document_id: Unique identifier for the document
447
+
448
+ Returns:
449
+ bool: True if document is verified, False otherwise
450
+ """
451
+ if not self.is_connected:
452
+ st.warning("MetaMask not connected. Cannot check document verification status.")
453
+ return False
454
+
455
+ if not self.contract_address:
456
+ st.warning("Contract address not set. Cannot check document verification status.")
457
+ return False
458
+
459
+ # In a real app, we would use JavaScript to call the smart contract
460
+ # For this example, we'll return a simulated response
461
+ return False
462
+
463
+ def log_query(self, query_text, answer_text):
464
+ """
465
+ Log a query and its answer on the blockchain.
466
+
467
+ Args:
468
+ query_text: The user's query
469
+ answer_text: The system's answer
470
+
471
+ Returns:
472
+ dict: Transaction status info
473
+ """
474
+ if not self.is_connected:
475
+ return {
476
+ "status": False,
477
+ "error": "MetaMask is not connected. Please connect your wallet first."
478
+ }
479
+
480
+ if not self.contract_address:
481
+ return {
482
+ "status": False,
483
+ "error": "Contract address is not set."
484
+ }
485
+
486
+ try:
487
+ # Create a unique query ID using timestamp
488
+ query_id = f"query_{int(time.time())}"
489
+
490
+ # Create a JSON object with query and answer
491
+ query_data = {
492
+ "query": query_text,
493
+ "answer": answer_text,
494
+ "timestamp": int(time.time())
495
+ }
496
+
497
+ # Hash the query data
498
+ query_hash = hashlib.sha256(json.dumps(query_data).encode()).hexdigest()
499
+
500
+ # Display MetaMask interaction info
501
+ st.info("Please confirm the transaction in your MetaMask wallet...")
502
+
503
+ # Simulate transaction process time
504
+ with st.spinner("Waiting for transaction confirmation..."):
505
+ time.sleep(3) # Simulate transaction time
506
+
507
+ # Simulate successful transaction
508
+ tx_hash = "0x" + "".join([format(i, "02x") for i in os.urandom(32)])
509
+ block_number = 12345678
510
+
511
+ return {
512
+ "status": True,
513
+ "tx_hash": tx_hash,
514
+ "query_id": query_id,
515
+ "query_hash": query_hash,
516
+ "block_number": block_number,
517
+ "status": 1 # Success
518
+ }
519
+
520
+ except Exception as e:
521
+ st.error(f"Error during query logging: {str(e)}")
522
+ return {
523
+ "status": False,
524
+ "error": str(e)
525
+ }
526
+
527
+ def get_metamask_js(self):
528
+ """Return the JavaScript code for MetaMask interaction."""
529
+ return self.web3_js
metamask-component.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # metamask_component.py
2
+ import os
3
+ import json
4
+ import streamlit as st
5
+ import streamlit.components.v1 as components
6
+
7
+ # Define the MetaMask component HTML
8
+ def get_metamask_component_html():
9
+ """
10
+ Creates an HTML component that can interact with MetaMask browser extension.
11
+ """
12
+ return """
13
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/web3/1.8.0/web3.min.js"></script>
14
+ <div id="metamask-connector">
15
+ <div id="metamask-status">
16
+ <p id="connection-status">MetaMask Status: Not Connected</p>
17
+ <p id="wallet-address">Wallet: Not Connected</p>
18
+ <p id="network-name">Network: Unknown</p>
19
+ </div>
20
+ <button id="connect-button" class="metamask-button">Connect MetaMask</button>
21
+ <button id="disconnect-button" class="metamask-button" style="display:none;">Disconnect</button>
22
+ </div>
23
+
24
+ <style>
25
+ #metamask-connector {
26
+ border: 1px solid #ccc;
27
+ padding: 15px;
28
+ border-radius: 5px;
29
+ margin-bottom: 15px;
30
+ background-color: #f9f9f9;
31
+ }
32
+ .metamask-button {
33
+ background-color: #F6851B;
34
+ color: white;
35
+ border: none;
36
+ padding: 8px 16px;
37
+ border-radius: 4px;
38
+ cursor: pointer;
39
+ margin-top: 10px;
40
+ }
41
+ .metamask-button:hover {
42
+ background-color: #E2761B;
43
+ }
44
+ #metamask-status {
45
+ margin-bottom: 10px;
46
+ }
47
+ </style>
48
+
49
+ <script>
50
+ // Function to check if MetaMask is installed
51
+ function isMetaMaskInstalled() {
52
+ return Boolean(window.ethereum && window.ethereum.isMetaMask);
53
+ }
54
+
55
+ // Function to update connection status
56
+ function updateConnectionStatus(connected, address = null, networkName = null) {
57
+ const statusElement = document.getElementById('connection-status');
58
+ const addressElement = document.getElementById('wallet-address');
59
+ const networkElement = document.getElementById('network-name');
60
+ const connectButton = document.getElementById('connect-button');
61
+ const disconnectButton = document.getElementById('disconnect-button');
62
+
63
+ if (connected) {
64
+ statusElement.textContent = 'MetaMask Status: Connected';
65
+ statusElement.style.color = 'green';
66
+ addressElement.textContent = `Wallet: ${address}`;
67
+ networkElement.textContent = `Network: ${networkName}`;
68
+ connectButton.style.display = 'none';
69
+ disconnectButton.style.display = 'inline-block';
70
+
71
+ // Send connection info to Streamlit
72
+ const data = {
73
+ connected: true,
74
+ address: address,
75
+ networkName: networkName,
76
+ networkId: window.ethereum.networkVersion
77
+ };
78
+ window.parent.postMessage({
79
+ type: "metamask-status",
80
+ data: data
81
+ }, "*");
82
+ } else {
83
+ statusElement.textContent = 'MetaMask Status: Not Connected';
84
+ statusElement.style.color = 'red';
85
+ addressElement.textContent = 'Wallet: Not Connected';
86
+ networkElement.textContent = 'Network: Unknown';
87
+ connectButton.style.display = 'inline-block';
88
+ disconnectButton.style.display = 'none';
89
+
90
+ // Send disconnection info to Streamlit
91
+ window.parent.postMessage({
92
+ type: "metamask-status",
93
+ data: { connected: false }
94
+ }, "*");
95
+ }
96
+ }
97
+
98
+ // Function to get network name
99
+ async function getNetworkName(chainId) {
100
+ const networks = {
101
+ '1': 'Ethereum Mainnet',
102
+ '5': 'Goerli Testnet',
103
+ '11155111': 'Sepolia Testnet',
104
+ '137': 'Polygon Mainnet',
105
+ '80001': 'Mumbai Testnet',
106
+ '56': 'Binance Smart Chain',
107
+ '97': 'BSC Testnet',
108
+ '42161': 'Arbitrum One',
109
+ '421613': 'Arbitrum Goerli',
110
+ '10': 'Optimism',
111
+ '420': 'Optimism Goerli',
112
+ '1337': 'Local Development Chain',
113
+ '31337': 'Hardhat Network'
114
+ };
115
+
116
+ return networks[chainId] || `Chain ID: ${chainId}`;
117
+ }
118
+
119
+ // Function to handle MetaMask connection
120
+ async function connectMetaMask() {
121
+ if (!isMetaMaskInstalled()) {
122
+ alert("MetaMask is not installed. Please install MetaMask and try again.");
123
+ return;
124
+ }
125
+
126
+ try {
127
+ // Request account access
128
+ const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
129
+ const networkId = await window.ethereum.request({ method: 'eth_chainId' });
130
+ const networkName = await getNetworkName(parseInt(networkId, 16).toString());
131
+
132
+ if (accounts.length > 0) {
133
+ updateConnectionStatus(true, accounts[0], networkName);
134
+ }
135
+ } catch (error) {
136
+ console.error("Error connecting to MetaMask:", error);
137
+ alert("Error connecting to MetaMask: " + error.message);
138
+ updateConnectionStatus(false);
139
+ }
140
+ }
141
+
142
+ // Handle disconnection (note: MetaMask doesn't have a disconnect method,
143
+ // so this just updates the UI without actually disconnecting)
144
+ function disconnectMetaMask() {
145
+ updateConnectionStatus(false);
146
+ }
147
+
148
+ // Add event listeners
149
+ document.addEventListener('DOMContentLoaded', function() {
150
+ // Check if MetaMask is installed
151
+ if (!isMetaMaskInstalled()) {
152
+ document.getElementById('connection-status').textContent = 'MetaMask Status: Not Installed';
153
+ document.getElementById('connect-button').disabled = true;
154
+ document.getElementById('connect-button').title = "Please install MetaMask first";
155
+ }
156
+
157
+ // Connect button event listener
158
+ document.getElementById('connect-button').addEventListener('click', connectMetaMask);
159
+
160
+ // Disconnect button event listener
161
+ document.getElementById('disconnect-button').addEventListener('click', disconnectMetaMask);
162
+
163
+ // Listen for account changes
164
+ if (window.ethereum) {
165
+ window.ethereum.on('accountsChanged', function (accounts) {
166
+ if (accounts.length === 0) {
167
+ // User disconnected all accounts
168
+ updateConnectionStatus(false);
169
+ } else {
170
+ // Account changed
171
+ connectMetaMask();
172
+ }
173
+ });
174
+
175
+ // Listen for chain changes
176
+ window.ethereum.on('chainChanged', function (chainId) {
177
+ // Chain changed, reconnect
178
+ connectMetaMask();
179
+ });
180
+ }
181
+ });
182
+ </script>
183
+ """
184
+
185
+ def metamask_connector():
186
+ """
187
+ Component that renders the MetaMask connector UI and returns connection info.
188
+
189
+ Returns:
190
+ dict: Connection information if connected, None otherwise
191
+ """
192
+ # Generate a unique key for the component
193
+ component_key = "metamask_connector"
194
+
195
+ # Create a placeholder for the component if it doesn't exist
196
+ if component_key not in st.session_state:
197
+ st.session_state[component_key] = {
198
+ "connected": False,
199
+ "address": None,
200
+ "network_name": None,
201
+ "network_id": None
202
+ }
203
+
204
+ # Render the HTML component
205
+ components.html(
206
+ get_metamask_component_html(),
207
+ height=200,
208
+ scrolling=False
209
+ )
210
+
211
+ # JavaScript callback to update session state
212
+ st.markdown("""
213
+ <script>
214
+ window.addEventListener('message', function(event) {
215
+ if (event.data.type === 'metamask-status') {
216
+ window.parent.postMessage({
217
+ type: 'streamlit:setComponentValue',
218
+ value: event.data.data,
219
+ dataType: 'json'
220
+ }, '*');
221
+ }
222
+ });
223
+ </script>
224
+ """, unsafe_allow_html=True)
225
+
226
+ # For demonstration - since we can't get real callbacks in this version,
227
+ # let's add some manual controls for testing
228
+ col1, col2 = st.columns(2)
229
+
230
+ with col1:
231
+ if st.button("Simulate Connection"):
232
+ st.session_state[component_key] = {
233
+ "connected": True,
234
+ "address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
235
+ "network_name": "Ethereum Mainnet",
236
+ "network_id": "1"
237
+ }
238
+ st.experimental_rerun()
239
+
240
+ with col2:
241
+ if st.button("Simulate Disconnection"):
242
+ st.session_state[component_key] = {
243
+ "connected": False,
244
+ "address": None,
245
+ "network_name": None,
246
+ "network_id": None
247
+ }
248
+ st.experimental_rerun()
249
+
250
+ return st.session_state[component_key]