jzou19950715 commited on
Commit
7e3a3e5
Β·
verified Β·
1 Parent(s): e850079

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +128 -495
app.py CHANGED
@@ -1,529 +1,162 @@
1
- import os
2
- from dataclasses import dataclass # βœ… Fixes the error
3
- import re
4
  import json
5
- import logging
6
- import asyncio
7
  import time
8
- import base64
9
- from pathlib import Path # βœ… Fixes the error
10
- from typing import Dict, List, Tuple, Any, Optional, TypeVar
11
- from datetime import datetime
12
  from decimal import Decimal
13
-
14
- import aiohttp
15
- import openai
16
  import gradio as gr
17
- from tenacity import retry, stop_after_attempt, wait_exponential
18
- from pathlib import Path # βœ… Fix 'Path' not defined error
19
- from dataclasses import dataclass # βœ… Fix 'dataclass' not defined error
20
- import os
21
- import json
22
- import logging
23
- import asyncio
24
- from typing import List, Dict, Tuple, Any, Optional
25
-
26
- # βœ… Fix 'Config' self-referencing error by using a string in type hint
27
- @dataclass
28
- class Config:
29
- """Application configuration settings."""
30
-
31
- SYSTEM_PROMPT: str = "You are an AI-powered blockchain assistant."
32
- OPENAI_MODEL: str = "gpt-4o-mini"
33
- MAX_TOKENS: int = 4000
34
- TEMPERATURE: float = 0.7
35
-
36
- @classmethod
37
- def load(cls, config_path: str | Path) -> "Config": # βœ… Fix self-referencing error
38
- """Load configuration from a JSON file."""
39
- try:
40
- with open(config_path) as f:
41
- config_data = json.load(f)
42
- return cls(**config_data)
43
- except Exception as e:
44
- logging.error(f"Error loading config: {e}")
45
- return cls()
46
-
47
- # Type variables
48
- T = TypeVar('T')
49
- ChatHistory = List[Tuple[str, str]]
50
 
51
- # Configure logging
52
- logging.basicConfig(
53
- level=logging.INFO,
54
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
55
- handlers=[
56
- logging.FileHandler("blockchain_analyzer.log"),
57
- logging.StreamHandler()
58
- ]
59
- )
60
- logger = logging.getLogger(__name__)
61
 
62
- # Custom Exceptions
63
- class APIError(Exception):
64
- """Raised when an API request fails."""
65
- pass
66
 
67
- class ValidationError(Exception):
68
- """Raised when input validation fails."""
69
- pass
70
 
71
- @dataclass
72
- class Config:
73
- """Application configuration settings."""
74
- SYSTEM_PROMPT: str = """
75
- You are LOSS DOG πŸ• (Learning & Observing Smart Systems Digital Output Generator),
76
- an adorable blockchain-sniffing puppy!
77
- Your personality:
78
- - Friendly and enthusiastic
79
- - Explain findings in fun, simple ways
80
- Instructions:
81
- - Use Alchemy API to fetch wallet data (NFTs and token holdings)
82
- - Reference exact numbers and collections when discussing assets
83
- - Highlight interesting findings
84
  """
85
- ALCHEMY_BASE_URL: str = "https://eth-mainnet.g.alchemy.com/v2"
86
- ETHEREUM_ADDRESS_REGEX: str = r"0x[a-fA-F0-9]{40}"
87
- RATE_LIMIT_DELAY: float = 0.2
88
- MAX_RETRIES: int = 3
89
- OPENAI_MODEL: str = "gpt-4o-mini"
90
- MAX_TOKENS: int = 4000
91
- TEMPERATURE: float = 0.7
92
- HISTORY_LIMIT: int = 5
93
 
94
- @classmethod
95
- def load(cls, config_path: str | Path) -> Config:
96
- """Load configuration from a JSON file."""
97
- try:
98
- with open(config_path) as f:
99
- config_data = json.load(f)
100
- return cls(**config_data)
101
- except Exception as e:
102
- logger.error(f"Error loading config: {e}")
103
- return cls()
104
 
105
- class AlchemyAPI:
106
- """Handles interactions with the Alchemy API."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
- def __init__(self, api_key: str):
109
- """Initialize Alchemy API client."""
110
- self.api_key = api_key
111
- self.base_url = f"{Config.ALCHEMY_BASE_URL}/{api_key}"
112
- self.session: Optional[aiohttp.ClientSession] = None
113
 
114
- async def __aenter__(self) -> "AlchemyAPI":
115
- """Create an aiohttp session on context manager enter."""
116
- self.session = aiohttp.ClientSession()
117
- return self
118
 
119
- async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
120
- """Close aiohttp session on context manager exit."""
121
- if self.session:
122
- await self.session.close()
123
- self.session = None
124
 
125
- async def _make_request(self, endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]:
126
- """Make an API request to Alchemy."""
127
- if not self.session:
128
- raise APIError("No active session. Use context manager.")
129
-
130
- try:
131
- url = f"{self.base_url}/{endpoint}"
132
- async with self.session.get(url, params=params) as response:
133
- if response.status != 200:
134
- raise APIError(f"API request failed with status {response.status}")
135
- return await response.json()
136
- except aiohttp.ClientError as e:
137
- raise APIError(f"Network error: {str(e)}")
138
 
139
- async def get_wallet_nfts(self, address: str) -> Dict[str, Any]:
140
- """Retrieve NFT holdings for a given wallet."""
141
- try:
142
- params = {"owner": address, "withMetadata": "true"}
143
- data = await self._make_request("getNFTs", params)
144
- return self._process_nfts(data)
145
- except Exception as e:
146
- raise APIError(f"Error getting wallet NFTs: {str(e)}")
147
 
148
- def _process_nfts(self, data: Dict[str, Any]) -> Dict[str, Any]:
149
- """Process NFT data from Alchemy."""
150
- nfts = data.get("ownedNfts", [])
151
- collections = {}
 
152
 
153
- for nft in nfts:
154
- contract = nft["contract"]["address"]
155
- token_id = nft["tokenId"]
156
- name = nft.get("title", "Unknown NFT")
157
- image_url = nft.get("media", [{}])[0].get("gateway", "")
158
 
159
- if contract not in collections:
160
- collections[contract] = {
161
- "count": 0,
162
- "nfts": []
163
- }
164
 
165
- collections[contract]["count"] += 1
166
- collections[contract]["nfts"].append({
167
- "token_id": token_id,
168
- "name": name,
169
- "image_url": image_url
170
- })
 
 
 
 
171
 
172
  return {
173
- "collections": collections,
174
- "total_nfts": sum(col["count"] for col in collections.values())
175
  }
 
 
 
176
 
177
- async def get_wallet_tokens(self, address: str) -> Dict[str, Any]:
178
- """Retrieve token holdings for a given wallet."""
179
- try:
180
- params = {"owner": address}
181
- data = await self._make_request("getTokenBalances", params)
182
- return self._process_tokens(data)
183
- except Exception as e:
184
- raise APIError(f"Error getting wallet tokens: {str(e)}")
185
-
186
- def _process_tokens(self, data: Dict[str, Any]) -> Dict[str, Any]:
187
- """Process token data from Alchemy."""
188
- tokens = data.get("tokenBalances", [])
189
- processed_tokens = []
190
-
191
- for token in tokens:
192
- contract = token["contractAddress"]
193
- balance = Decimal(token["tokenBalance"]) / Decimal(10**18)
194
-
195
- processed_tokens.append({
196
- "contract": contract,
197
- "balance": float(balance)
198
- })
199
 
200
- return {"tokens": processed_tokens, "total_tokens": len(processed_tokens)}
 
 
 
 
201
 
202
- async def get_wallet_summary(self, address: str) -> Dict[str, Any]:
203
- """Retrieve a complete wallet summary including NFTs and tokens."""
204
- async with asyncio.TaskGroup() as group:
205
- nft_task = group.create_task(self.get_wallet_nfts(address))
206
- token_task = group.create_task(self.get_wallet_tokens(address))
207
 
208
- return {
209
- "nfts": nft_task.result(),
210
- "tokens": token_task.result()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  }
212
- class ChatInterface:
213
- """Handles chat interaction using OpenAI API with blockchain data integration."""
214
-
215
- def __init__(self, openai_key: str, alchemy_key: str):
216
- """
217
- Initialize chat interface with API keys.
218
-
219
- Args:
220
- openai_key: OpenAI API key
221
- alchemy_key: Alchemy API key
222
- """
223
- self.openai_key = openai_key
224
- self.alchemy_key = alchemy_key
225
- self.context: Dict[str, Any] = {}
226
- openai.api_key = openai_key
227
-
228
- @staticmethod
229
- async def validate_api_keys(openai_key: str, alchemy_key: str) -> Tuple[bool, str]:
230
- """
231
- Validate OpenAI and Alchemy API keys.
232
-
233
- Args:
234
- openai_key: OpenAI API key
235
- alchemy_key: Alchemy API key
236
-
237
- Returns:
238
- Tuple[bool, str]: (is_valid, message)
239
- """
240
- try:
241
- # Validate OpenAI API Key (Sync Call)
242
- client = openai.OpenAI(api_key=openai_key)
243
- client.chat.completions.create(
244
- model=Config.OPENAI_MODEL,
245
- messages=[{"role": "user", "content": "test"}],
246
- max_tokens=1
247
- )
248
-
249
- # Validate Alchemy API Key (Async Call)
250
- async with AlchemyAPI(alchemy_key) as client:
251
- await client.get_wallet_nfts("0x0000000000000000000000000000000000000000")
252
-
253
- return True, "βœ… API keys validated successfully!"
254
-
255
- except Exception as e:
256
- logger.error(f"Validation error: {str(e)}")
257
- return False, f"❌ API key validation failed: {str(e)}"
258
-
259
- def _format_context_message(self) -> str:
260
- """
261
- Format wallet data as context message for the AI.
262
-
263
- Returns:
264
- str: Formatted context message
265
- """
266
- if not self.context:
267
- return "No wallet data available."
268
-
269
- context_msg = ["Current Wallet Data:\n"]
270
-
271
- for addr, data in self.context.items():
272
- context_msg.append(f"\nπŸ”Ή Wallet {addr[:8]}...{addr[-6:]}:")
273
-
274
- if data["tokens"]["total_tokens"]:
275
- context_msg.append(f"πŸͺ™ Tokens: {data['tokens']['total_tokens']}")
276
- for token in data["tokens"]["tokens"][:5]:
277
- context_msg.append(f" - {token['contract']} : {token['balance']}")
278
 
279
- if data["nfts"]["total_nfts"]:
280
- context_msg.append(f"🎨 NFTs: {data['nfts']['total_nfts']}")
281
- for collection, info in list(data["nfts"]["collections"].items())[:3]:
282
- context_msg.append(f" - {collection}: {info['count']} NFTs")
283
-
284
- return "\n".join(context_msg)
285
-
286
- async def fetch_wallet_data(self, address: str) -> Dict[str, Any]:
287
- """
288
- Fetch wallet data from Alchemy API.
289
-
290
- Args:
291
- address: Ethereum wallet address
292
-
293
- Returns:
294
- Dict[str, Any]: Wallet data summary
295
- """
296
- async with AlchemyAPI(self.alchemy_key) as client:
297
- return await client.get_wallet_summary(address)
298
-
299
- async def process_message(
300
- self,
301
- message: str,
302
- history: Optional[ChatHistory] = None
303
- ) -> Tuple[ChatHistory, Dict[str, Any], str]:
304
- """
305
- Process user message and generate response.
306
-
307
- Args:
308
- message: User message
309
- history: Chat history
310
-
311
- Returns:
312
- Tuple containing:
313
- - Updated chat history
314
- - Updated wallet context
315
- - Error message (if any)
316
- """
317
- if not message.strip():
318
- return history or [], self.context, ""
319
-
320
- history = history or []
321
- match = re.search(Config.ETHEREUM_ADDRESS_REGEX, message)
322
-
323
- if match:
324
- try:
325
- address = match.group(0)
326
- wallet_data = await self.fetch_wallet_data(address)
327
- self.context[address] = wallet_data
328
-
329
- bot_message = f"βœ… Wallet data for {address[:8]}...{address[-6:]} fetched successfully!"
330
- history.append((message, bot_message))
331
- return history, self.context, ""
332
-
333
- except Exception as e:
334
- logger.error(f"Error analyzing wallet: {e}")
335
- error_message = f"Error analyzing wallet: {str(e)}"
336
- history.append((message, error_message))
337
- return history, self.context, ""
338
-
339
- # Generate AI response
340
  try:
341
- context_msg = self._format_context_message()
342
- chat_history = []
343
-
344
- for user_msg, assistant_msg in history[-Config.HISTORY_LIMIT:]:
345
- chat_history.extend([
346
- {"role": "user", "content": user_msg},
347
- {"role": "assistant", "content": assistant_msg}
348
- ])
349
-
350
- client = openai.OpenAI(api_key=self.openai_key)
351
- response = client.chat.completions.create(
352
- model=Config.OPENAI_MODEL,
353
- messages=[
354
- {"role": "system", "content": Config.SYSTEM_PROMPT},
355
- {"role": "system", "content": context_msg},
356
- *chat_history,
357
- {"role": "user", "content": message}
358
- ],
359
- temperature=Config.TEMPERATURE,
360
- max_tokens=Config.MAX_TOKENS
361
- )
362
-
363
- bot_message = response.choices[0].message.content
364
- history.append((message, bot_message))
365
- return history, self.context, ""
366
-
367
- except Exception as e:
368
- logger.error(f"Error generating response: {e}")
369
- error_message = f"Failed to generate response: {str(e)}"
370
- history.append((message, error_message))
371
- return history, self.context, ""
372
-
373
- def clear_context(self) -> Tuple[Dict[str, Any], List[Tuple[str, str]]]:
374
- """
375
- Clear the wallet context and chat history.
376
 
377
- Returns:
378
- Tuple containing empty context and history
379
- """
380
- self.context = {}
381
- return {}, []
382
- import gradio as gr
383
-
384
- class GradioInterface:
385
- """Handles Gradio web interface setup and interactions."""
386
-
387
- def __init__(self):
388
- """Initialize Gradio interface."""
389
- self.chat_interface: Optional[ChatInterface] = None
390
- self.demo = self._create_interface()
391
-
392
- def _create_interface(self) -> gr.Blocks:
393
- """Create and configure Gradio interface."""
394
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
395
- gr.Markdown("""
396
- # πŸ›  Alchemy Blockchain Chatbot
397
- - Enter your API keys to start
398
- - Input an Ethereum wallet address
399
- - Get NFT and token data from Alchemy
400
- - Chat with AI about your holdings!
401
- """)
402
-
403
- # API Keys Section
404
- with gr.Row():
405
- openai_key = gr.Textbox(
406
- label="OpenAI API Key",
407
- type="password",
408
- placeholder="Enter your OpenAI API key..."
409
- )
410
- alchemy_key = gr.Textbox(
411
- label="Alchemy API Key",
412
- type="password",
413
- placeholder="Enter your Alchemy API key..."
414
- )
415
-
416
- validation_status = gr.Textbox(
417
- label="Validation Status",
418
- interactive=False
419
- )
420
- validate_btn = gr.Button("Validate API Keys", variant="primary")
421
-
422
- # Main Interface
423
- with gr.Row():
424
- # Chat Area
425
- chatbot = gr.Chatbot(label="Chat History", height=500, value=[])
426
- with gr.Row():
427
- msg_input = gr.Textbox(
428
- label="Message",
429
- placeholder="Enter wallet address or ask about holdings..."
430
- )
431
- send_btn = gr.Button("Send", variant="primary")
432
-
433
- # Right Sidebar (Wallet Data)
434
- with gr.Column(scale=1):
435
- gr.Markdown("### Wallet Data")
436
- wallet_context = gr.JSON(label="Wallet Data", show_label=True, value={})
437
- clear_btn = gr.Button("Clear All Data", variant="secondary")
438
-
439
- # Initial States
440
- msg_input.interactive = False
441
- send_btn.interactive = False
442
-
443
- async def validate_keys(openai_k: str, alchemy_k: str) -> Tuple[str, gr.update, gr.update]:
444
- """Validate API keys and initialize chat interface."""
445
- try:
446
- is_valid, message = await ChatInterface.validate_api_keys(openai_k, alchemy_k)
447
- if is_valid:
448
- self.chat_interface = ChatInterface(openai_k, alchemy_k)
449
- return (
450
- "βœ… API keys validated!",
451
- gr.update(interactive=True),
452
- gr.update(interactive=True)
453
- )
454
- return (f"❌ Validation failed: {message}", gr.update(interactive=False), gr.update(interactive=False))
455
- except Exception as e:
456
- return (f"❌ Error: {str(e)}", gr.update(interactive=False), gr.update(interactive=False))
457
-
458
- async def handle_message(
459
- message: str,
460
- chat_history: List[Tuple[str, str]],
461
- wallet_context_data: Dict[str, Any]
462
- ) -> Tuple[str, List[Tuple[str, str]], Dict[str, Any]]:
463
- """Handle user messages."""
464
- if not self.chat_interface:
465
- return "", [], {}
466
-
467
- try:
468
- history, new_context, _ = await self.chat_interface.process_message(
469
- message, chat_history
470
- )
471
- return "", history, new_context
472
- except Exception as e:
473
- chat_history.append((message, f"Error: {str(e)}"))
474
- return "", chat_history, wallet_context_data
475
-
476
- def clear_all_data() -> Tuple[Dict[str, Any], List[Tuple[str, str]]]:
477
- """Clear chat history and wallet data."""
478
- if self.chat_interface:
479
- return self.chat_interface.clear_context()
480
- return {}, []
481
-
482
- # Connect Event Handlers
483
- validate_btn.click(
484
- fn=validate_keys,
485
- inputs=[openai_key, alchemy_key],
486
- outputs=[validation_status, msg_input, send_btn]
487
- )
488
-
489
- clear_btn.click(
490
- fn=clear_all_data,
491
- inputs=[],
492
- outputs=[wallet_context, chatbot]
493
- )
494
-
495
- msg_input.submit(
496
- fn=handle_message,
497
- inputs=[msg_input, chatbot, wallet_context],
498
- outputs=[msg_input, chatbot, wallet_context]
499
- )
500
-
501
- send_btn.click(
502
- fn=handle_message,
503
- inputs=[msg_input, chatbot, wallet_context],
504
- outputs=[msg_input, chatbot, wallet_context]
505
- )
506
-
507
- return demo
508
-
509
- def launch(self, **kwargs):
510
- """Launch the Gradio interface."""
511
- self.demo.queue()
512
- self.demo.launch(**kwargs)
513
-
514
-
515
- def main():
516
- """Main entry point for the application."""
517
- try:
518
- interface = GradioInterface()
519
- interface.launch(
520
- server_name="0.0.0.0",
521
- server_port=7860
522
- )
523
- except Exception as e:
524
- logger.error(f"Application startup failed: {e}")
525
- raise
526
 
 
527
 
528
  if __name__ == "__main__":
529
- main()
 
1
+ import requests
 
 
2
  import json
3
+ import io
 
4
  import time
 
 
 
 
5
  from decimal import Decimal
6
+ from typing import Dict, Any, List, Tuple, Optional
 
 
7
  import gradio as gr
8
+ from PIL import Image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ # API Keys (Replace with your own)
11
+ ETHERSCAN_API_KEY = "YOUR_ETHERSCAN_API_KEY"
12
+ OPENSEA_API_KEY = "YOUR_OPENSEA_API_KEY"
13
+ OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
 
 
 
 
 
 
14
 
15
+ # API URLs
16
+ ETHERSCAN_BASE_URL = "https://api.etherscan.io/api"
17
+ OPENSEA_BASE_URL = "https://api.opensea.io/api/v2/chain/ethereum/contract"
18
+ OPENSEA_COLLECTION_STATS = "https://api.opensea.io/api/v1/collection"
19
 
20
+ HEADERS = {"X-API-KEY": OPENSEA_API_KEY} if OPENSEA_API_KEY else {}
 
 
21
 
22
+ ### 🏦 WALLET ANALYZER ###
23
+ def fetch_wallet_data(address: str) -> Dict[str, Any]:
 
 
 
 
 
 
 
 
 
 
 
24
  """
25
+ Fetch wallet data including ETH balance, tokens, and NFTs.
 
 
 
 
 
 
 
26
 
27
+ Args:
28
+ address (str): Ethereum wallet address.
 
 
 
 
 
 
 
 
29
 
30
+ Returns:
31
+ Dict[str, Any]: Wallet details including ETH balance, tokens, and NFTs.
32
+ """
33
+ def get_eth_balance():
34
+ params = {"module": "account", "action": "balance", "address": address, "tag": "latest", "apikey": ETHERSCAN_API_KEY}
35
+ response = requests.get(ETHERSCAN_BASE_URL, params=params)
36
+ data = response.json()
37
+ return Decimal(data["result"]) / Decimal("1000000000000000000") if "result" in data else 0
38
+
39
+ def get_tokens():
40
+ params = {"module": "account", "action": "tokentx", "address": address, "sort": "desc", "apikey": ETHERSCAN_API_KEY}
41
+ response = requests.get(ETHERSCAN_BASE_URL, params=params)
42
+ data = response.json()
43
+
44
+ tokens = {}
45
+ for tx in data.get("result", []):
46
+ contract = tx["contractAddress"]
47
+ if contract not in tokens:
48
+ tokens[contract] = {"name": tx["tokenName"], "symbol": tx["tokenSymbol"], "balance": Decimal(0)}
49
+
50
+ amount = Decimal(tx["value"]) / Decimal(10 ** int(tx["tokenDecimal"]))
51
+ tokens[contract]["balance"] += amount if tx["to"].lower() == address.lower() else -amount
52
 
53
+ return [{"name": v["name"], "symbol": v["symbol"], "balance": float(v["balance"])} for v in tokens.values() if v["balance"] > 0]
 
 
 
 
54
 
55
+ def get_nfts():
56
+ params = {"module": "account", "action": "tokennfttx", "address": address, "sort": "desc", "apikey": ETHERSCAN_API_KEY}
57
+ response = requests.get(ETHERSCAN_BASE_URL, params=params)
58
+ data = response.json()
59
 
60
+ nfts = {}
61
+ for tx in data.get("result", []):
62
+ contract = tx["contractAddress"]
63
+ token_id = tx["tokenID"]
64
+ collection = tx.get("tokenName", "Unknown Collection")
65
 
66
+ if contract not in nfts:
67
+ nfts[contract] = {"collection": collection, "token_ids": set()}
68
+
69
+ nfts[contract]["token_ids"].add(token_id)
 
 
 
 
 
 
 
 
 
70
 
71
+ return [{"contract": k, "collection": v["collection"], "token_ids": list(v["token_ids"])} for k, v in nfts.items()]
 
 
 
 
 
 
 
72
 
73
+ return {
74
+ "eth_balance": float(get_eth_balance()),
75
+ "tokens": get_tokens(),
76
+ "nfts": get_nfts()
77
+ }
78
 
79
+ ### 🎨 NFT FETCHING ###
80
+ def fetch_nft_metadata(contract: str, token_id: str) -> Optional[Dict[str, Any]]:
81
+ """
82
+ Fetch NFT metadata from OpenSea API.
 
83
 
84
+ Args:
85
+ contract (str): NFT contract address.
86
+ token_id (str): NFT Token ID.
 
 
87
 
88
+ Returns:
89
+ Dict[str, Any]: Metadata with name and image URL.
90
+ """
91
+ url = f"{OPENSEA_BASE_URL}/{contract}/nfts/{token_id}"
92
+
93
+ try:
94
+ response = requests.get(url, headers=HEADERS)
95
+ response.raise_for_status()
96
+ data = response.json()
97
+ nft_metadata = data.get("nft", {})
98
 
99
  return {
100
+ "name": nft_metadata.get("name", f"NFT #{token_id}"),
101
+ "image_url": nft_metadata.get("image_url", ""),
102
  }
103
+
104
+ except requests.exceptions.RequestException as e:
105
+ return {"error": f"Error fetching NFT: {str(e)}"}
106
 
107
+ ### 🌐 GRADIO UI ###
108
+ with gr.Blocks() as app:
109
+ gr.Markdown("# 🏦 Blockchain Wallet & NFT Analyzer")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
+ # Wallet Analysis Section
112
+ with gr.Row():
113
+ wallet_input = gr.Textbox(label="Enter Ethereum Address", placeholder="0x...")
114
+ analyze_btn = gr.Button("Analyze Wallet")
115
+ wallet_output = gr.JSON(label="Wallet Data")
116
 
117
+ analyze_btn.click(fn=fetch_wallet_data, inputs=[wallet_input], outputs=[wallet_output])
 
 
 
 
118
 
119
+ # NFT Viewer Section
120
+ gr.Markdown("## 🎨 Ethereum NFT Viewer")
121
+
122
+ with gr.Row():
123
+ contract_address = gr.Textbox(label="NFT Contract Address")
124
+ token_id = gr.Textbox(label="Token ID")
125
+ fetch_btn = gr.Button("Fetch NFT")
126
+
127
+ nft_name = gr.Textbox(label="NFT Name", interactive=False)
128
+ nft_image = gr.Image(label="NFT Image")
129
+
130
+ def display_nft(contract: str, token: str):
131
+ metadata = fetch_nft_metadata(contract, token)
132
+ if "error" in metadata:
133
+ return metadata["error"], None
134
+ return metadata["name"], metadata["image_url"]
135
+
136
+ fetch_btn.click(fn=display_nft, inputs=[contract_address, token_id], outputs=[nft_name, nft_image])
137
+
138
+ # Chatbot
139
+ gr.Markdown("## πŸ’¬ Ask About Blockchain Data")
140
+ chatbot = gr.Chatbot(label="Chat with Blockchain AI")
141
+ chat_input = gr.Textbox(label="Ask a question...")
142
+ chat_btn = gr.Button("Ask")
143
+
144
+ def chat_response(message: str):
145
+ headers = {"Authorization": f"Bearer {OPENAI_API_KEY}"}
146
+ data = {
147
+ "model": "gpt-4o-mini",
148
+ "messages": [{"role": "user", "content": message}],
149
+ "temperature": 0.7
150
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  try:
153
+ response = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=data)
154
+ return response.json()["choices"][0]["message"]["content"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
+ except requests.exceptions.RequestException as e:
157
+ return f"Error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
+ chat_btn.click(fn=chat_response, inputs=[chat_input], outputs=[chatbot])
160
 
161
  if __name__ == "__main__":
162
+ app.launch()