Spaces:
Sleeping
Sleeping
| import requests | |
| import pandas as pd | |
| import gradio as gr | |
| import plotly.express as px | |
| from datetime import datetime, timedelta | |
| import plotly.graph_objects as go | |
| import numpy as np | |
| import json | |
| from web3 import Web3 | |
| import time | |
| import os | |
| from itertools import product | |
| # RPC URLs | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| OPTIMISM_RPC_URL = os.getenv('OPTIMISM_RPC_URL') | |
| BASE_RPC_URL = os.getenv('BASE_RPC_URL') | |
| ETH_RPC_URL = os.getenv('ETH_RPC_URL') | |
| MODE_RPC_URL = os.getenv('MODE_RPC_URL') | |
| print(f"Optimism RPC URL: {OPTIMISM_RPC_URL}") | |
| # Initialize Web3 instances | |
| print("Initializing Web3 instances...") | |
| web3_optimism = Web3(Web3.HTTPProvider(OPTIMISM_RPC_URL)) | |
| web3_base = Web3(Web3.HTTPProvider(BASE_RPC_URL)) | |
| web3_eth = Web3(Web3.HTTPProvider(ETH_RPC_URL)) | |
| web3_mode = Web3(Web3.HTTPProvider(MODE_RPC_URL)) # New chain | |
| # Contract addresses for service registries | |
| contract_address_optimism = '0x3d77596beb0f130a4415df3D2D8232B3d3D31e44' | |
| contract_address_base = '0x3C1fF68f5aa342D296d4DEe4Bb1cACCA912D95fE' | |
| contract_address_eth = '0x48b6af7B12C71f09e2fC8aF4855De4Ff54e775cA' | |
| contract_address_mode = '0x3C1fF68f5aa342D296d4DEe4Bb1cACCA912D95fE' # New chain | |
| # Load the ABI from a local JSON file | |
| with open('./contracts/service_registry_abi.json', 'r') as abi_file: | |
| contract_abi = json.load(abi_file) | |
| # Create the contract instances | |
| service_registry_optimism = web3_optimism.eth.contract(address=contract_address_optimism, abi=contract_abi) | |
| service_registry_base = web3_base.eth.contract(address=contract_address_base, abi=contract_abi) | |
| service_registry_eth = web3_eth.eth.contract(address=contract_address_eth, abi=contract_abi) | |
| service_registry_mode = web3_mode.eth.contract(address=contract_address_mode, abi=contract_abi) # New chain | |
| print("Service registry contracts loaded.") | |
| # Check if connection is successful | |
| if not web3_optimism.is_connected(): | |
| raise Exception("Failed to connect to the Optimism network.") | |
| if not web3_base.is_connected(): | |
| raise Exception("Failed to connect to the Base network.") | |
| if not web3_eth.is_connected(): | |
| raise Exception("Failed to connect to the ETH network.") | |
| if not web3_mode.is_connected(): | |
| raise Exception("Failed to connect to the Mode network.") # New chain | |
| print("Successfully connected to Ethereum, Optimism, Base, and Mode networks.") | |
| def fetch_service_safes(web3, registry_contract): | |
| print("\nFetching service safes...") | |
| total_services = registry_contract.functions.totalSupply().call() | |
| print(f"Total services: {total_services}") | |
| service_safes_dict = {} | |
| zero_address = '0x0000000000000000000000000000000000000000' | |
| for service_id in range(1, total_services + 1): | |
| print(f"Processing service ID: {service_id}") | |
| service = registry_contract.functions.getService(service_id).call() | |
| agent_ids = service[-1] # Assuming the last element is the list of agent IDs | |
| print(f"Agent IDs: {agent_ids}") | |
| if 40 in agent_ids or 25 in agent_ids: | |
| agent_instance_data = registry_contract.functions.getAgentInstances(service_id).call() | |
| service_safe = service[1] | |
| agent_addresses = agent_instance_data[1] | |
| if agent_addresses: | |
| agent_address = agent_addresses[0] | |
| if service_safe != zero_address: | |
| print(f"Found agent_address: {agent_address}") | |
| print(f"Found service safe: {service_safe}") | |
| service_safes_dict[agent_address] = service_safe | |
| else: | |
| print(f"Found zero address for service safe, skipping for agent: {agent_address}") | |
| else: | |
| print(f"No agent address found for service ID: {service_id}") | |
| print(f"Total unique agent addresses found (excluding zero safe addresses): {len(service_safes_dict)}") | |
| return service_safes_dict | |
| # Fetch service safes for each network | |
| service_safes_optimism = fetch_service_safes(web3_optimism, service_registry_optimism) | |
| service_safes_base = fetch_service_safes(web3_base, service_registry_base) | |
| service_safes_eth = fetch_service_safes(web3_eth, service_registry_eth) | |
| service_safes_mode = fetch_service_safes(web3_mode, service_registry_mode) | |
| # service_safes_eth = {safe for safe in service_safes_eth if safe.lower() != '0x0000000000000000000000000000000000000000'} | |
| print(f"Service safes for Mode: {service_safes_mode}") | |
| def get_block_range_for_date(chain_id, date_str, api_key, base_url): | |
| """Get the block range for a specific date.""" | |
| target_date = datetime.strptime(date_str, "%Y-%m-%d").date() | |
| start_of_day = datetime.combine(target_date, datetime.min.time()) | |
| if target_date == datetime.now().date(): | |
| end_of_day = datetime.now() # Use the current time if the target date is today | |
| else: | |
| end_of_day = datetime.combine(target_date, datetime.max.time()) | |
| start_timestamp = int(start_of_day.timestamp()) | |
| end_timestamp = int(end_of_day.timestamp()) | |
| # Get start block | |
| start_response = requests.get( | |
| f"{base_url}?module=block&action=getblocknobytime×tamp={start_timestamp}&closest=before&apikey={api_key}" | |
| ) | |
| if start_response.status_code == 200: | |
| start_data = start_response.json() | |
| start_block = start_data.get('result') | |
| else: | |
| print(f"Error fetching start block for {date_str} on chain {chain_id}") | |
| return None, None | |
| if start_block is None: | |
| print(f"No start block found for chain {chain_id} on {date_str}") | |
| return None, None | |
| print(f"Start block for chain {chain_id} on {date_str}: {start_block}") | |
| # Get end block | |
| time.sleep(1) | |
| end_response = requests.get( | |
| f"{base_url}?module=block&action=getblocknobytime×tamp={end_timestamp}&closest=before&apikey={api_key}" | |
| ) | |
| if end_response.status_code == 200: | |
| end_data = end_response.json() | |
| end_block = end_data.get('result') | |
| else: | |
| print(f"Error fetching end block for {date_str} on chain {chain_id}") | |
| return None, None | |
| if end_block is None: | |
| print(f"No end block found for chain {chain_id} on {date_str}") | |
| return None, None | |
| print(f"End block for chain {chain_id} on {date_str}: {end_block}") | |
| return start_block, end_block | |
| def get_transactions_mode_all(address): | |
| url = f'https://explorer.mode.network/api/v2/addresses/{address}/transactions' | |
| headers = { | |
| 'accept': 'application/json' | |
| } | |
| response = requests.get(url, headers=headers) | |
| return response.json()['items'] | |
| def get_transactions(api_keys, wallet_address, chain_name, start_block, end_block): | |
| """Retrieve transactions for the given wallet address, chain, and block range using the Etherscan or similar API.""" | |
| base_url = { | |
| 'optimism': "https://api-optimistic.etherscan.io/api", | |
| 'base': "https://api.basescan.org/api", | |
| 'ethereum': "https://api.etherscan.io/api", | |
| 'mode': "https://eth-sepolia.blockscout.com/api" | |
| }.get(chain_name) | |
| if not base_url: | |
| print(f"Invalid chain name: {chain_name}") | |
| return [] | |
| params = { | |
| 'module': 'account', | |
| 'action': 'txlist', | |
| 'address': wallet_address, | |
| 'startblock': start_block, | |
| 'endblock': end_block, | |
| 'sort': 'asc', | |
| 'apikey': api_keys.get(chain_name) | |
| } | |
| response = requests.get(base_url, params=params) | |
| data = response.json() | |
| time.sleep(1) | |
| if data['status'] != '1': | |
| print(f"Error: {data['message']}") | |
| return [] | |
| valid_transactions = [tx for tx in data['result'] if tx['isError'] == '0'] | |
| return valid_transactions | |
| def get_mode_transactions(transactions_all_base,date_str): | |
| target_date = datetime.strptime(date_str, "%Y-%m-%d").date() | |
| start_of_day = datetime.combine(target_date, datetime.min.time()) | |
| if target_date == datetime.now().date(): | |
| end_of_day = datetime.now() # Use the current time if the target date is today | |
| else: | |
| end_of_day = datetime.combine(target_date, datetime.max.time()) | |
| start_timestamp = start_of_day.isoformat() | |
| end_timestamp = end_of_day.isoformat() | |
| processed_list = [] | |
| for transaction in transactions_all_base: | |
| # Filter transactions based on the timestamp | |
| if start_timestamp <= transaction['timestamp'] <= end_timestamp: | |
| transaction_timestamp = datetime.strptime(transaction['timestamp'], "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S") | |
| processed_transaction = { | |
| 'hash': transaction['hash'], | |
| 'timeStamp': transaction_timestamp, | |
| 'from': transaction['from']['hash'], | |
| 'to': transaction['to']['hash'], | |
| 'value': transaction['value'] | |
| } | |
| processed_list.append(processed_transaction) | |
| return processed_list | |
| def date_range(start_date, end_date): | |
| """Generates a range of dates from start_date to end_date inclusive.""" | |
| start_dt = datetime.strptime(start_date, "%Y-%m-%d") | |
| end_dt = datetime.strptime(end_date, "%Y-%m-%d") | |
| delta = timedelta(days=1) | |
| current_dt = start_dt | |
| while current_dt <= end_dt: | |
| yield current_dt.strftime("%Y-%m-%d") | |
| current_dt += delta | |
| def fetch_transactions(): | |
| api_keys = { | |
| 'optimism': 'XQ72JA5XZ51QC7TG1W295AAIF4KTV92K1K', | |
| 'base': '4BFQMVW1QUKEPVDA4VW711CF4462682CY8', | |
| 'ethereum': '3GRYJGX55W3QWCEKGREF4H53AFHCAIVVR7', | |
| 'mode': 'f651444d-2916-4f37-bfd8-e70cc8232eb7' | |
| } | |
| base_urls = { | |
| 10: "https://api-optimistic.etherscan.io/api", | |
| 8453: "https://api.basescan.org/api", | |
| 1: "https://api.etherscan.io/api", | |
| 3443: "https://eth-sepolia.blockscout.com/api" | |
| } | |
| current_date = datetime.today().strftime("%Y-%m-%d") | |
| csv_filename = 'daily_transactions_new.csv' | |
| if os.path.exists(csv_filename): | |
| df_existing = pd.read_csv(csv_filename) | |
| if 'date' in df_existing: | |
| last_date_in_csv = df_existing['date'].max() | |
| else: | |
| df_existing['date'] = pd.to_datetime(df_existing['timestamp']).dt.date | |
| last_date_in_csv = df_existing['date'].max() | |
| else: | |
| df_existing = pd.DataFrame() | |
| last_date_in_csv = '2024-09-19' | |
| start_date = (datetime.strptime(last_date_in_csv, "%Y-%m-%d") + timedelta(days=1)).strftime("%Y-%m-%d") | |
| chains = { | |
| 10: ('optimism', service_safes_optimism), | |
| 8453: ('base', service_safes_base), | |
| 1: ('ethereum', service_safes_eth), | |
| 3443: ('mode', service_safes_mode) | |
| } | |
| all_transactions = df_existing.to_dict('records') if not df_existing.empty else [] | |
| for chain_id, (chain_name, service_safes) in chains.items(): | |
| base_url = base_urls[chain_id] | |
| api_key = api_keys[chain_name] | |
| for agent_address, safe_address in service_safes.items(): | |
| print(f"\nProcessing {chain_name.capitalize()} for agent address {agent_address} (safe address {safe_address})...") | |
| if chain_name == 'mode': | |
| transactions_all_base = get_transactions_mode_all(safe_address) | |
| for single_date in date_range(start_date, current_date): | |
| if chain_name == 'mode': | |
| print(f"Processing date {single_date} for chain {chain_name}...") | |
| transactions = get_mode_transactions(transactions_all_base,single_date) | |
| else: | |
| print(f"Processing date {single_date} for chain {chain_name}...") | |
| start_block, end_block = get_block_range_for_date(chain_id, single_date, api_key, base_url) | |
| if start_block is None or end_block is None: | |
| print(f"Skipping date {single_date} for chain {chain_name} due to missing block data.") | |
| continue | |
| print(f"Start Block: {start_block}, End Block: {end_block} for date {single_date}") | |
| transactions = get_transactions(api_keys, safe_address, chain_name, start_block, end_block) | |
| if transactions: | |
| print(f"Found {len(transactions)} transactions on {single_date} for {chain_name.capitalize()} safe address {safe_address}:") | |
| for tx in transactions: | |
| if chain_name != 'mode': | |
| print(f"Transaction Hash: {tx['hash']} on {chain_name.capitalize()} at {tx['timeStamp']}") | |
| tx_time = datetime.fromtimestamp(int(tx['timeStamp'])) | |
| all_transactions.append({ | |
| 'chain': chain_name, | |
| 'agent_address': agent_address, | |
| 'safe_address': safe_address, | |
| 'date': single_date, | |
| 'transaction_hash': tx['hash'], | |
| 'timestamp': tx_time, | |
| 'from': tx['from'], | |
| 'to': tx['to'], | |
| 'value_eth': int(tx['value']) / 1e18 # Convert value to ETH | |
| }) | |
| else: | |
| all_transactions.append({ | |
| 'chain': chain_name, | |
| 'agent_address': agent_address, | |
| 'safe_address': safe_address, | |
| 'date': single_date, | |
| 'transaction_hash': tx['hash'], | |
| 'timestamp': tx['timeStamp'], | |
| 'from': tx['from'], | |
| 'to': tx['to'], | |
| 'value_eth': int(tx['value']) / 1e18 # Convert value to ETH | |
| }) | |
| else: | |
| print(f"No transactions found for agent address {agent_address} (safe address {safe_address}) on {single_date} on {chain_name.capitalize()}.") | |
| df_transactions_new = pd.DataFrame(all_transactions) | |
| df_transactions_new.to_csv(csv_filename, index=False) | |
| return df_transactions_new | |
| def create_transcation_visualizations(): | |
| df_transactions_new = fetch_transactions() | |
| df_transactions_new['timestamp'] = pd.to_datetime(df_transactions_new['timestamp']) | |
| # Group by date and chain, count transactions | |
| daily_counts = df_transactions_new.groupby([df_transactions_new['timestamp'].dt.date, 'chain']).size().unstack(fill_value=0) | |
| # Ensure required chains are present | |
| chains = ['optimism', 'base', 'ethereum', 'mode'] | |
| for chain in chains: | |
| if chain not in daily_counts.columns: | |
| daily_counts[chain] = 0 | |
| daily_counts = daily_counts[chains] | |
| # Read the original data | |
| daily_counts['timestamp'] = daily_counts.index | |
| daily_counts['timestamp'] = pd.to_datetime(daily_counts['timestamp']) | |
| daily_counts = daily_counts.reset_index(drop=True) | |
| # Get min and max dates | |
| min_date = daily_counts['timestamp'].min() | |
| max_date = daily_counts['timestamp'].max() | |
| # Create complete date range | |
| full_date_range = pd.date_range(start=min_date, end=max_date, freq='D') | |
| # Create a new dataframe with all dates | |
| complete_df = pd.DataFrame({'timestamp': full_date_range}) | |
| complete_df = complete_df.merge(daily_counts, on='timestamp', how='left') | |
| complete_df = complete_df.fillna(0) | |
| daily_counts = complete_df | |
| # Convert timestamp to datetime | |
| daily_counts['timestamp'] = pd.to_datetime(daily_counts['timestamp']) | |
| # Create a new dataframe with 12-hour slots | |
| new_rows = [] | |
| for _, row in daily_counts.iterrows(): | |
| # Create first 12-hour slot (0-12) | |
| slot1 = row.copy() | |
| # slot1['timestamp'] = row['timestamp'].replace(hour=0) | |
| # # Create second 12-hour slot (12-24) | |
| new_rows.extend([slot1]) | |
| slot2 = row.copy() | |
| if slot2['timestamp'].dayofweek == 6: # 6 represents Sunday | |
| slot2[chains] = 0 | |
| slot2['timestamp'] = row['timestamp'].replace(hour=12) | |
| new_rows.extend([slot2]) | |
| # Create new dataframe with 12-hour slots | |
| hourly_counts = pd.DataFrame(new_rows).sort_values('timestamp') | |
| # Prepare data for plotting | |
| dates = hourly_counts['timestamp'].tolist() | |
| values = hourly_counts[chains].to_numpy() | |
| # Create the figure | |
| fig = go.Figure() | |
| # Create arrays for positioning and width | |
| # Convert dates to datetime objects first | |
| date_objects = pd.to_datetime(dates) | |
| # Create numeric indices for x-axis | |
| x_numeric = np.arange(len(dates)) | |
| # Create arrays for positioning and width | |
| width_array = [] | |
| for i, date in enumerate(date_objects): | |
| width_array.append(1.0) # Full width for other days | |
| # Get Monday indices for tick positions | |
| monday_indices = [i for i, date in enumerate(date_objects) if date.dayofweek == 0] | |
| monday_labels = [date_objects[i].strftime('%m-%d') for i in monday_indices] | |
| # Add traces for each series | |
| fig.add_trace(go.Bar( | |
| name='Optimism', | |
| x=x_numeric, | |
| y=values[:,0], | |
| marker_color='blue', | |
| opacity=0.7, | |
| text=None, | |
| width=width_array, | |
| textposition='none', | |
| )) | |
| fig.add_trace(go.Bar( | |
| name='Base', | |
| x=x_numeric, | |
| y=values[:,1], | |
| marker_color='purple', | |
| opacity=0.7, | |
| text=None, | |
| textposition='none', | |
| width=width_array, | |
| )) | |
| fig.add_trace(go.Bar( | |
| name='Ethereum', | |
| x=x_numeric, | |
| y=values[:,2], | |
| marker_color='darkgreen', | |
| opacity=0.7, | |
| text=None, | |
| width=width_array, | |
| textposition='none', | |
| )) | |
| fig.add_trace(go.Bar( | |
| name='Mode', | |
| x=x_numeric, | |
| y=values[:,3], | |
| marker_color='orange', | |
| opacity=0.7, | |
| text=None, | |
| width=width_array, | |
| textposition='none', | |
| )) | |
| # Update layout with numeric x-axis | |
| fig.update_layout( | |
| title='Chain Daily Activity : Transactions', | |
| xaxis_title='Date', | |
| yaxis_title='Daily Transactions Count', | |
| barmode='stack', | |
| showlegend=True, | |
| legend_title_text='Transaction Chain', | |
| height=600, | |
| bargap=0, | |
| bargroupgap=0, | |
| xaxis=dict( | |
| tickangle=-45, | |
| tickmode='array', | |
| ticktext=monday_labels, | |
| tickvals=monday_indices, | |
| ), | |
| template='plotly_white', | |
| hoverlabel=dict( | |
| font_size=12, | |
| ), | |
| ) | |
| # Update hover template | |
| for trace in fig.data: | |
| trace.update( | |
| hovertemplate="<b>Date:</b> %{text}<br>" + | |
| "<b>" + trace.name + ":</b> %{y}<br>" + | |
| "<extra></extra>", | |
| text=[d.strftime('%Y-%m-%d') for d in date_objects] # Add date text for hover | |
| ) | |
| # Show the plot | |
| return fig | |
| def create_active_agents_visualizations(): | |
| df_transactions_new = fetch_transactions() | |
| df_transactions_new['timestamp'] = pd.to_datetime(df_transactions_new['timestamp']) | |
| # Extract week and day information | |
| df_transactions_new['week_start'] = df_transactions_new['timestamp'].dt.to_period('W').apply(lambda r: r.start_time) | |
| df_transactions_new['weekday'] = df_transactions_new['timestamp'].dt.weekday | |
| # Count unique agents per day | |
| daily_agents = df_transactions_new.groupby(['week_start', 'weekday'])['agent_address'].nunique().reset_index() | |
| # Generate all possible combinations of week_start and weekday up to today | |
| today = datetime.today() | |
| yesterday = today - timedelta(days=1) | |
| valid_weekdays = [(ws, wd) for ws, wd in product(daily_agents['week_start'].unique(), range(7)) if ws + timedelta(days=wd) <= yesterday] | |
| all_combinations = pd.DataFrame(valid_weekdays, columns=['week_start', 'weekday']) | |
| # Merge with daily_agents to fill missing days with zero | |
| daily_agents = all_combinations.merge(daily_agents, on=['week_start', 'weekday'], how='left').fillna(0) | |
| # Compute average unique agents per week | |
| weekly_avg_agents = daily_agents.groupby('week_start')['agent_address'].mean().reset_index() | |
| weekly_avg_agents.rename(columns={'agent_address': 'avg_daily_active_agents'}, inplace=True) | |
| # Prepare data for plotting | |
| weeks = weekly_avg_agents['week_start'].unique() | |
| avg_agents_per_week = weekly_avg_agents['avg_daily_active_agents'] | |
| # Create the bar chart | |
| fig = go.Figure() | |
| fig.add_trace(go.Bar( | |
| x=[f'{week.strftime("%b %d")}' for week in weeks], | |
| y=avg_agents_per_week, | |
| marker_color='blue', | |
| opacity=0.7, | |
| text=None, | |
| hoverlabel=dict( | |
| font_size=12, | |
| ), | |
| )) | |
| # Update layout | |
| fig.update_layout( | |
| title='Daily Active Agents: Weekly Average Number of Agents with at Least 1 Transaction Daily', | |
| xaxis_title='Week', | |
| yaxis_title='Average Number of Active Agents', | |
| xaxis=dict( | |
| tickangle=-45 # Rotate x-axis labels to 45 degrees | |
| ), | |
| height=600, | |
| width=1000, | |
| bargap=0, | |
| bargroupgap=0.2, | |
| template='plotly_white' | |
| ) | |
| return fig | |
| # Gradio interface | |
| def dashboard(): | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Valory Transactions Dashboard") | |
| # Fetch and display visualizations | |
| with gr.Tab("Transactions"): | |
| fig_tx_chain = create_transcation_visualizations() | |
| gr.Plot(fig_tx_chain) | |
| with gr.Tab("DAA"): | |
| fig_active_agents = create_active_agents_visualizations() | |
| gr.Plot(fig_active_agents) | |
| # Add more tabs as needed... | |
| return demo | |
| # Launch the dashboard | |
| if __name__ == "__main__": | |
| dashboard().launch() |