|
| 1 | +# report.py |
1 | 2 | import json
|
2 | 3 | from datetime import datetime
|
3 |
| -from pathlib import Path |
4 |
| -from web3 import Web3 |
5 |
| -import docker |
6 |
| -# Import necessary functions and constants from run_service.py |
| 4 | +import logging |
| 5 | +from decimal import Decimal, getcontext |
| 6 | + |
7 | 7 | from run_service import (
|
8 | 8 | get_local_config,
|
9 | 9 | get_service_template,
|
10 |
| - wei_to_token, |
11 |
| - get_erc20_balance, |
12 | 10 | CHAIN_ID_TO_METADATA,
|
13 |
| - USDC_ADDRESS, |
14 | 11 | OPERATE_HOME,
|
15 |
| - OptimusConfig, |
16 | 12 | )
|
17 | 13 |
|
18 |
| -# Import necessary functions from wallet_info.py |
19 |
| -from wallet_info import save_wallet_info |
20 |
| -from staking_report import staking_report |
21 |
| -# Existing imports and functions... |
22 |
| -OUTPUT_WIDTH = 80 |
23 |
| - |
24 |
| - |
25 |
| -class ColorCode: |
26 |
| - """Terminal color codes""" |
27 |
| - |
28 |
| - GREEN = "\033[92m" |
29 |
| - RED = "\033[91m" |
30 |
| - YELLOW = "\033[93m" |
31 |
| - RESET = "\033[0m" |
32 |
| - |
33 |
| - |
34 |
| -def _color_string(text: str, color_code: str) -> str: |
35 |
| - return f"{color_code}{text}{ColorCode.RESET}" |
36 |
| - |
37 |
| - |
38 |
| -def _color_bool( |
39 |
| - is_true: bool, true_string: str = "True", false_string: str = "False" |
40 |
| -) -> str: |
41 |
| - if is_true: |
42 |
| - return _color_string(true_string, ColorCode.GREEN) |
43 |
| - return _color_string(false_string, ColorCode.RED) |
44 |
| - |
45 |
| - |
46 |
| -def _color_percent(p: float, multiplier: float = 100, symbol: str = "%") -> str: |
47 |
| - if p >= 0: |
48 |
| - return f"{p*multiplier:.2f} {symbol}" |
49 |
| - return _color_string(f"{p*multiplier:.2f} {symbol}", ColorCode.RED) |
50 |
| - |
51 |
| -def _print_section_header(header: str) -> None: |
52 |
| - print("\n\n" + header) |
53 |
| - print("=" * OUTPUT_WIDTH) |
54 |
| - |
55 |
| - |
56 |
| -def _print_subsection_header(header: str) -> None: |
57 |
| - print("\n" + header) |
58 |
| - print("-" * OUTPUT_WIDTH) |
59 |
| - |
60 |
| - |
61 |
| -def _print_status(key: str, value: str, message: str = "") -> None: |
62 |
| - print(f"{key:<30}{value:<10} {message or ''}") |
63 |
| - |
64 |
| -def _get_agent_status() -> str: |
65 |
| - client = docker.from_env() |
66 |
| - optmius_abci_container = ( |
67 |
| - client.containers.get("optimus_abci_0") |
68 |
| - if "optimus_abci_0" in [c.name for c in client.containers.list()] |
69 |
| - else None |
70 |
| - ) |
71 |
| - is_running = optmius_abci_container |
72 |
| - return _color_bool(is_running, "Running", "Stopped") |
73 |
| - |
74 |
| -def wei_to_unit(wei: int) -> float: |
75 |
| - """Convert Wei to unit.""" |
76 |
| - return wei / 1e18 |
77 |
| - |
| 14 | +from utils import ( |
| 15 | + _print_section_header, |
| 16 | + _print_subsection_header, |
| 17 | + _print_status, |
| 18 | + wei_to_eth, |
| 19 | + _warning_message, |
| 20 | + get_chain_name, |
| 21 | + load_operator_address, |
| 22 | + validate_config, |
| 23 | + _get_agent_status, |
| 24 | +) |
78 | 25 |
|
79 |
| -def wei_to_token(wei: int, token: str = "xDAI") -> str: |
80 |
| - """Convert Wei to token.""" |
81 |
| - return f"{wei_to_unit(wei):.2f} {token}" |
| 26 | +from wallet_info import save_wallet_info, load_config as load_wallet_config |
| 27 | +from staking_report import staking_report |
82 | 28 |
|
83 |
| -def wei_to_olas(wei: int) -> str: |
84 |
| - """Converts and formats wei to WxDAI.""" |
85 |
| - return "{:.2f} OLAS".format(wei_to_unit(wei)) |
| 29 | +# Set decimal precision |
| 30 | +getcontext().prec = 18 |
86 | 31 |
|
87 |
| -def _warning_message(current_value: int, threshold: int = 0, message: str = "") -> str: |
88 |
| - default_message = _color_string( |
89 |
| - f"- Balance too low. Threshold is {wei_to_unit(threshold):.2f}.", |
90 |
| - ColorCode.YELLOW, |
91 |
| - ) |
92 |
| - if current_value < threshold: |
93 |
| - return ( |
94 |
| - _color_string(f"{message}", ColorCode.YELLOW) |
95 |
| - if message |
96 |
| - else default_message |
97 |
| - ) |
98 |
| - return "" |
| 32 | +# Configure logging |
| 33 | +logging.basicConfig(level=logging.INFO, format='%(message)s') |
99 | 34 |
|
100 | 35 | def load_wallet_info():
|
101 | 36 | save_wallet_info()
|
102 | 37 | file_path = OPERATE_HOME / "wallets" / "wallet_info.json"
|
103 |
| - with open(file_path, "r") as f: |
104 |
| - return json.load(f) |
105 |
| - |
106 |
| -def load_config(): |
107 |
| - optimus_config = get_local_config() |
108 |
| - service_template = get_service_template(optimus_config) |
109 |
| - service_hash = service_template["hash"] |
110 |
| - |
111 |
| - config_path = OPERATE_HOME / f"services/{service_hash}/config.json" |
112 |
| - with open(config_path, "r") as f: |
113 |
| - return json.load(f) |
114 |
| - |
115 |
| -def load_operator_address(): |
116 |
| - ethereum_json_path = OPERATE_HOME / "wallets" / "ethereum.json" |
117 |
| - with open(ethereum_json_path, "r") as f: |
118 |
| - ethereum_data = json.load(f) |
119 |
| - # Pick the address from safes where safe chain is 4 |
120 |
| - return ethereum_data["safes"]["4"] |
121 |
| - |
122 |
| -def check_service_status(): |
123 |
| - # Placeholder for actual status checking logic |
124 |
| - return "Stopped" |
125 |
| - |
126 |
| -def wei_to_eth(wei_value): |
127 |
| - return Web3.from_wei(wei_value, 'ether') |
| 38 | + try: |
| 39 | + with open(file_path, "r") as f: |
| 40 | + return json.load(f) |
| 41 | + except FileNotFoundError: |
| 42 | + print(f"Error: Wallet info file not found at {file_path}") |
| 43 | + return {} |
| 44 | + except json.JSONDecodeError: |
| 45 | + print("Error: Wallet info file contains invalid JSON.") |
| 46 | + return {} |
128 | 47 |
|
129 | 48 | def generate_report():
|
130 |
| - # First, update the wallet info |
131 |
| - wallet_info = load_wallet_info() |
132 |
| - |
133 |
| - operator_address = load_operator_address() |
134 |
| - config = load_config() |
135 |
| - # Service Report Header |
136 |
| - print("") |
137 |
| - print("==============") |
138 |
| - print("Optimus Service report") |
139 |
| - print("Generated on: ", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) |
140 |
| - print("==============") |
141 |
| - # Now load the updated wallet info |
142 |
| - |
143 |
| - |
144 |
| - staking_report(config) |
145 |
| - home_chain_id = config.get("home_chain_id") |
146 |
| - service_id = config["chain_configs"][home_chain_id]["chain_data"]["token"] |
147 |
| - |
148 |
| - |
149 |
| - |
150 |
| - # Service Section |
151 |
| - _print_section_header("Service") |
152 |
| - _print_status("ID", str(service_id)) |
153 |
| - |
154 |
| - # Agent Section |
155 |
| - agent_status = _get_agent_status() |
156 |
| - _print_subsection_header("Agent") |
157 |
| - _print_status("Status (on this machine)", agent_status) |
158 |
| - _print_status("Address", wallet_info['main_wallet_address']) |
159 |
| - # _print_status( |
160 |
| - # " Balance", |
161 |
| - # f"{wei_to_xdai(agent_xdai)} {_warning_message(agent_xdai, AGENT_XDAI_BALANCE_THRESHOLD)}", |
162 |
| - # ) #to-do warning message and threshold |
163 |
| - |
164 |
| - |
165 |
| - # Agent balances across chains |
166 |
| - for chain_id, chain_config in config["chain_configs"].items(): |
167 |
| - chain_name = CHAIN_ID_TO_METADATA[int(chain_id)]["name"] |
168 |
| - balance_info = wallet_info['main_wallet_balances'].get(chain_name, {}) |
169 |
| - _print_status(f"{chain_name} Balance",f"{balance_info.get('balance_formatted', 'N/A')}") |
170 |
| - # Get the agent threshold from the config |
171 |
| - ######TO-DO get the agent threshold from the config |
172 |
| - # agent_threshold_wei = chain_config["chain_data"]["user_params"]["fund_requirements"]["agent"] |
173 |
| - # agent_threshold = f"{wei_to_eth(agent_threshold_wei):.3f} ETH" |
174 |
| - |
175 |
| - # # Check for low balance |
176 |
| - # current_balance = float(balance_info.get('balance_formatted', '0').split()[0]) |
177 |
| - # if current_balance < float(agent_threshold.split()[0]): |
178 |
| - # report.append(f"Agent - Balance too low. Threshold is {agent_threshold}.") |
179 |
| - |
180 |
| - # report.append("") |
181 |
| - |
182 |
| - # # Safe Section |
183 |
| - _print_subsection_header("Safe") |
184 |
| - for chain_id, chain_config in config["chain_configs"].items(): |
185 |
| - chain_name = CHAIN_ID_TO_METADATA[int(chain_id)]["name"] |
186 |
| - safe_info = wallet_info['safe_balances'].get(chain_name, {}) |
187 |
| - _print_status(f"Address ({chain_name})",f"{safe_info.get('address', 'N/A')}") |
188 |
| - _print_status(f"{safe_info.get('token', 'ETH')} Balance",f"{safe_info.get('balance_formatted', 'N/A')}") |
189 |
| - |
190 |
| - if 'usdc_balance' in safe_info: |
191 |
| - _print_status("USDC Balance",safe_info['usdc_balance_formatted']) |
192 |
| - print(" ") |
193 |
| - ###### TO-DO Low balance check |
194 |
| - # # Get the safe threshold from the config |
195 |
| - # safe_threshold_wei = chain_config["chain_data"]["user_params"]["fund_requirements"]["safe"] |
196 |
| - # safe_threshold = f"{wei_to_eth(safe_threshold_wei):.3f} ETH" |
197 |
| - |
198 |
| - # # Check for low balance |
199 |
| - # current_balance = float(safe_info.get('balance_formatted', '0').split()[0]) |
200 |
| - # if current_balance < float(safe_threshold.split()[0]): |
201 |
| - # report.append(f"Safe - Balance too low. Threshold is {safe_threshold}.") |
202 |
| - |
203 |
| - # report.append("") |
204 |
| - |
205 |
| - # # Owner/Operator Section |
206 |
| - _print_subsection_header("Owner/Operator") |
207 |
| - _print_status("Address", operator_address) |
208 |
| - for chain_name, balance_info in wallet_info['main_wallet_balances'].items(): |
209 |
| - _print_status(f"{chain_name} Balance",balance_info['balance_formatted']) |
210 |
| - # report.append(f"{chain_name} Balance {balance_info['balance_formatted']}") |
211 |
| - |
212 |
| - |
| 49 | + try: |
| 50 | + # First, update the wallet info |
| 51 | + wallet_info = load_wallet_info() |
| 52 | + if not wallet_info: |
| 53 | + print("Error: Wallet info is empty.") |
| 54 | + return |
| 55 | + |
| 56 | + operator_address = load_operator_address(OPERATE_HOME) |
| 57 | + if not operator_address: |
| 58 | + print("Error: Operator address could not be loaded.") |
| 59 | + return |
| 60 | + |
| 61 | + config = load_wallet_config() |
| 62 | + if not config: |
| 63 | + print("Error: Config is empty.") |
| 64 | + return |
| 65 | + |
| 66 | + if not validate_config(config): |
| 67 | + return |
| 68 | + |
| 69 | + # Service Report Header |
| 70 | + print("") |
| 71 | + print("==============") |
| 72 | + print("Optimus Service Report") |
| 73 | + print("Generated on: ", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) |
| 74 | + print("==============") |
| 75 | + |
| 76 | + staking_report(config) |
| 77 | + home_chain_id = config.get("home_chain_id") |
| 78 | + service_id = config.get("chain_configs", {}).get(str(home_chain_id), {}).get("chain_data", {}).get("token") |
| 79 | + if not service_id: |
| 80 | + print(f"Error: 'token' not found in chain data for chain ID {home_chain_id}.") |
| 81 | + return |
| 82 | + |
| 83 | + # Service Section |
| 84 | + _print_section_header("Service") |
| 85 | + _print_status("ID", str(service_id)) |
| 86 | + |
| 87 | + # Agent Section |
| 88 | + agent_status = _get_agent_status() |
| 89 | + _print_subsection_header("Agent") |
| 90 | + _print_status("Status (on this machine)", agent_status) |
| 91 | + _print_status("Address", wallet_info.get('main_wallet_address', 'N/A')) |
| 92 | + |
| 93 | + for chain_id, chain_config in config.get("chain_configs", {}).items(): |
| 94 | + chain_name = get_chain_name(chain_id, CHAIN_ID_TO_METADATA) |
| 95 | + balance_info = wallet_info.get('main_wallet_balances', {}).get(chain_name, {}) |
| 96 | + balance_formatted = balance_info.get('balance_formatted', 'N/A') |
| 97 | + _print_status(f"{chain_name} Balance", balance_formatted) |
| 98 | + |
| 99 | + # Low balance check |
| 100 | + agent_threshold_wei = chain_config.get("chain_data", {}).get("user_params", {}).get("fund_requirements", {}).get("agent") |
| 101 | + if agent_threshold_wei: |
| 102 | + agent_threshold_eth = wei_to_eth(agent_threshold_wei) |
| 103 | + current_balance_str = balance_formatted.split()[0] |
| 104 | + try: |
| 105 | + current_balance = Decimal(current_balance_str) |
| 106 | + if current_balance < agent_threshold_eth: |
| 107 | + warning_msg = _warning_message(current_balance, agent_threshold_eth, f"Balance below threshold of {agent_threshold_eth:.2f} ETH") |
| 108 | + _print_status("Warning", warning_msg) |
| 109 | + except (ValueError, Exception): |
| 110 | + print(f"Warning: Could not parse balance '{balance_formatted}' for chain '{chain_name}'.") |
| 111 | + |
| 112 | + # Safe Section |
| 113 | + _print_subsection_header("Safe") |
| 114 | + safe_balances = wallet_info.get('safe_balances', {}) |
| 115 | + for chain_id, chain_config in config.get("chain_configs", {}).items(): |
| 116 | + chain_name = get_chain_name(chain_id, CHAIN_ID_TO_METADATA) |
| 117 | + safe_info = safe_balances.get(chain_name, {}) |
| 118 | + _print_status(f"Address ({chain_name})", safe_info.get('address', 'N/A')) |
| 119 | + _print_status(f"{safe_info.get('token', 'ETH')} Balance", safe_info.get('balance_formatted', 'N/A')) |
| 120 | + |
| 121 | + # Check for USDC balance on Ethereum Mainnet |
| 122 | + if chain_id == "1": |
| 123 | + usdc_balance_formatted = safe_info.get('usdc_balance_formatted', 'N/A') |
| 124 | + _print_status("USDC Balance", usdc_balance_formatted) |
| 125 | + |
| 126 | + # Low balance check |
| 127 | + safe_threshold_wei = chain_config.get("chain_data", {}).get("user_params", {}).get("fund_requirements", {}).get("safe") |
| 128 | + if safe_threshold_wei: |
| 129 | + safe_threshold_eth = wei_to_eth(safe_threshold_wei) |
| 130 | + balance_str = safe_info.get('balance_formatted', '0').split()[0] |
| 131 | + try: |
| 132 | + current_balance = Decimal(balance_str) |
| 133 | + if current_balance < safe_threshold_eth: |
| 134 | + warning_msg = _warning_message(current_balance, safe_threshold_eth, f"Balance below threshold of {safe_threshold_eth:.2f} ETH") |
| 135 | + _print_status("Warning", warning_msg) |
| 136 | + except (ValueError, Exception): |
| 137 | + print(f"Warning: Could not parse balance '{balance_str}' for chain '{chain_name}'.") |
| 138 | + print() |
| 139 | + # Owner/Operator Section |
| 140 | + _print_subsection_header("Owner/Operator") |
| 141 | + _print_status("Address", operator_address) |
| 142 | + for chain_name, balance_info in wallet_info.get('main_wallet_balances', {}).items(): |
| 143 | + _print_status(f"{chain_name} Balance", balance_info.get('balance_formatted', 'N/A')) |
| 144 | + |
| 145 | + except Exception as e: |
| 146 | + print(f"An unexpected error occurred in generate_report: {e}") |
213 | 147 |
|
214 | 148 | if __name__ == "__main__":
|
215 | 149 | generate_report()
|
0 commit comments