From b3511ea924605044b42638508039e8513e1efdfe Mon Sep 17 00:00:00 2001 From: Divya-Solulab Date: Fri, 11 Oct 2024 14:16:09 +0530 Subject: [PATCH 1/5] add: add suggest funding report --- suggest_funding_report.py | 154 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 suggest_funding_report.py diff --git a/suggest_funding_report.py b/suggest_funding_report.py new file mode 100644 index 0000000..65402a0 --- /dev/null +++ b/suggest_funding_report.py @@ -0,0 +1,154 @@ +import json +from datetime import datetime +import logging +from decimal import Decimal, getcontext, ROUND_UP +from pathlib import Path +from typing import Any, Tuple + +from run_service import ( + CHAIN_ID_TO_METADATA, + OPERATE_HOME, +) + +from utils import ( + _print_subsection_header, + _print_status, + get_chain_name, + load_operator_address, + validate_config, +) + +from wallet_info import save_wallet_info, load_config as load_wallet_config + +# Set decimal precision +getcontext().prec = 18 +GAS_COSTS_JSON_PATH = Path(".optimus") / "gas_costs.json" +FUNDING_MULTIPLIER = Decimal(10) +ROUNDING_PRECISION = Decimal('0.0001') + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(message)s') + +class ColorCode: + """Color code""" + GREEN = "\033[92m" + RED = "\033[91m" + YELLOW = "\033[93m" + RESET = "\033[0m" + +def load_wallet_info() -> dict: + """Load wallet info from file.""" + save_wallet_info() + file_path = OPERATE_HOME / "wallets" / "wallet_info.json" + return _load_json_file(file_path, "Wallet info") + +def load_gas_costs(file_path: Path) -> dict: + """Load gas costs details from file.""" + return _load_json_file(file_path, "Gas costs") + +def generate_gas_cost_report(): + """Generate and print the gas cost report.""" + try: + gas_costs = load_gas_costs(GAS_COSTS_JSON_PATH) + wallet_info = load_wallet_info() + if not wallet_info: + print("Error: Wallet info is empty.") + return + + operator_address = load_operator_address(OPERATE_HOME) + if not operator_address: + print("Error: Operator address could not be loaded.") + return + + config = load_wallet_config() + if not config: + print("Error: Config is empty.") + return + + if not validate_config(config): + return + + _print_report_header() + + for chain_id, _ in config.get("chain_configs", {}).items(): + chain_name = get_chain_name(chain_id, CHAIN_ID_TO_METADATA) + balance_info = wallet_info.get('main_wallet_balances', {}).get(chain_name, {}) + agent_address = wallet_info.get('main_wallet_address', 'N/A') + analyze_and_report_gas_costs(gas_costs, balance_info, chain_id, chain_name, agent_address) + + except Exception as e: + print(f"An unexpected error occurred in generate_gas_cost_report: {e}") + +def _load_json_file(file_path: Path, description: str) -> dict: + """Helper function to load JSON data from a file.""" + try: + with open(file_path, "r", encoding="utf-8") as file: + return json.load(file) + except FileNotFoundError: + print(f"Error: {description} file not found at {file_path}") + return {} + except json.JSONDecodeError: + print(f"Error: {description} file contains invalid JSON.") + return {} + +def analyze_and_report_gas_costs(gas_costs: dict, balance_info: Any, chain_id: int, chain_name: str, agent_address: str) -> None: + """Analyze gas costs and suggest funding amount.""" + _print_subsection_header(f"Funding Recommendation for {chain_name}") + + transactions = gas_costs.get(chain_id, []) + if not transactions: + no_data_message = f"[{chain_name}] No transaction data available for analysis." + print(_color_string(no_data_message, ColorCode.YELLOW)) + return + + total_gas_cost = sum(Decimal(tx["gas_cost"]) for tx in transactions) + average_gas_price = sum(Decimal(tx["gas_price"]) for tx in transactions) / Decimal(len(transactions)) + average_gas_cost = total_gas_cost / Decimal(len(transactions)) + + funding_needed, funding_suggestion = _calculate_funding_needed(average_gas_cost, balance_info) + _report_funding_status(chain_name, balance_info, average_gas_cost, average_gas_price, funding_suggestion, funding_needed, agent_address) + +def _calculate_funding_needed(average_gas_cost: Decimal, balance_info: Any) -> Tuple[Decimal,Decimal]: + """Calculate the funding needed based on average gas cost and current balance.""" + funding_suggestion = Decimal(average_gas_cost) * FUNDING_MULTIPLIER / Decimal(1e18) + return max(funding_suggestion - Decimal(balance_info.get('balance', 0)), Decimal(0)), funding_suggestion + +def _report_funding_status(chain_name: str, balance_info: Any, average_gas_cost: Decimal, average_gas_price: Decimal, funding_suggestion: Decimal, funding_needed: Decimal, agent_address: str): + """Report the funding status and suggestions.""" + _print_status(f"[{chain_name}] Current Balance ", balance_info.get('balance_formatted', 'N/A')) + _print_status(f"[{chain_name}] Average Gas Cost (WEI) ", average_gas_cost) + _print_status(f"[{chain_name}] Average Gas Price (WEI) ", average_gas_price) + _print_status(f"[{chain_name}] Funds needed to execute atleast next {FUNDING_MULTIPLIER} transactions (ETH) ", funding_suggestion) + + average_gas_cost_eth = average_gas_cost / Decimal(1e18) + current_balance = Decimal(balance_info.get('balance', 0)) + transactions_supported = current_balance / average_gas_cost_eth + + if funding_needed <= 0: + funding_message = f"[{chain_name}] Your current balance is sufficient for future transactions." + print(_color_string(funding_message, ColorCode.GREEN)) + elif transactions_supported < 5: + funding_needed_rounded = _round_up(funding_needed, ROUNDING_PRECISION) + funding_message = f"[{chain_name}] BALANCE TOO LOW! Urgently fund your agent {agent_address} at least {funding_needed_rounded} ETH to ensure smooth operation" + print(_color_string(funding_message, ColorCode.RED)) + else: + funding_needed_rounded = _round_up(funding_needed, ROUNDING_PRECISION) + funding_message = f"[{chain_name}] Please fund your agent {agent_address} at least {funding_needed_rounded} ETH to cover future transaction costs" + print(_color_string(funding_message, ColorCode.YELLOW)) + +def _round_up(value: Decimal, precision: Decimal) -> Decimal: + """Round up a Decimal value to a specified precision.""" + return value.quantize(precision, rounding=ROUND_UP) + +def _color_string(text: str, color_code: str) -> str: + return f"{color_code}{text}{ColorCode.RESET}" + +def _print_report_header(): + """Print the header for the gas cost report.""" + print("\n==============") + print("Suggested Funding Report") + print("Generated on: ", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) + print("==============") + +if __name__ == "__main__": + generate_gas_cost_report() \ No newline at end of file From a02178ca5c627e4c79f8d05e8b7dd92db5852dfe Mon Sep 17 00:00:00 2001 From: Divya-Solulab Date: Wed, 16 Oct 2024 11:07:08 +0530 Subject: [PATCH 2/5] fix: update messages --- suggest_funding_report.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/suggest_funding_report.py b/suggest_funding_report.py index 65402a0..b385c97 100644 --- a/suggest_funding_report.py +++ b/suggest_funding_report.py @@ -25,7 +25,7 @@ GAS_COSTS_JSON_PATH = Path(".optimus") / "gas_costs.json" FUNDING_MULTIPLIER = Decimal(10) ROUNDING_PRECISION = Decimal('0.0001') - +MIN_TRANSACTIONS_SUPPORTED = 5 # Configure logging logging.basicConfig(level=logging.INFO, format='%(message)s') @@ -127,13 +127,13 @@ def _report_funding_status(chain_name: str, balance_info: Any, average_gas_cost: if funding_needed <= 0: funding_message = f"[{chain_name}] Your current balance is sufficient for future transactions." print(_color_string(funding_message, ColorCode.GREEN)) - elif transactions_supported < 5: + elif transactions_supported < MIN_TRANSACTIONS_SUPPORTED: funding_needed_rounded = _round_up(funding_needed, ROUNDING_PRECISION) - funding_message = f"[{chain_name}] BALANCE TOO LOW! Urgently fund your agent {agent_address} at least {funding_needed_rounded} ETH to ensure smooth operation" + funding_message = f"[{chain_name}] BALANCE TOO LOW! Please urgently fund your agent {agent_address} with at least {funding_needed_rounded} ETH to ensure smooth operation" print(_color_string(funding_message, ColorCode.RED)) else: funding_needed_rounded = _round_up(funding_needed, ROUNDING_PRECISION) - funding_message = f"[{chain_name}] Please fund your agent {agent_address} at least {funding_needed_rounded} ETH to cover future transaction costs" + funding_message = f"[{chain_name}] Please fund your agent {agent_address} with at least {funding_needed_rounded} ETH to cover future transaction costs" print(_color_string(funding_message, ColorCode.YELLOW)) def _round_up(value: Decimal, precision: Decimal) -> Decimal: From 1abc6ae55dcd84a22e3fa105c21720f26a23df0f Mon Sep 17 00:00:00 2001 From: Divya-Solulab Date: Wed, 16 Oct 2024 18:52:55 +0530 Subject: [PATCH 3/5] fix: calculate avg gas price --- suggest_funding_report.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/suggest_funding_report.py b/suggest_funding_report.py index b385c97..9205fc6 100644 --- a/suggest_funding_report.py +++ b/suggest_funding_report.py @@ -3,7 +3,8 @@ import logging from decimal import Decimal, getcontext, ROUND_UP from pathlib import Path -from typing import Any, Tuple +from typing import Any, Tuple, Dict +from web3 import Web3 from run_service import ( CHAIN_ID_TO_METADATA, @@ -74,7 +75,8 @@ def generate_gas_cost_report(): chain_name = get_chain_name(chain_id, CHAIN_ID_TO_METADATA) balance_info = wallet_info.get('main_wallet_balances', {}).get(chain_name, {}) agent_address = wallet_info.get('main_wallet_address', 'N/A') - analyze_and_report_gas_costs(gas_costs, balance_info, chain_id, chain_name, agent_address) + chain_rpc = wallet_info.get("chain_configs").get(str(chain_id)).get('rpc') + analyze_and_report_gas_costs(gas_costs, balance_info, chain_id, chain_name, agent_address, chain_rpc) except Exception as e: print(f"An unexpected error occurred in generate_gas_cost_report: {e}") @@ -91,23 +93,38 @@ def _load_json_file(file_path: Path, description: str) -> dict: print(f"Error: {description} file contains invalid JSON.") return {} -def analyze_and_report_gas_costs(gas_costs: dict, balance_info: Any, chain_id: int, chain_name: str, agent_address: str) -> None: +def analyze_and_report_gas_costs(gas_costs: dict, balance_info: Any, chain_id: int, chain_name: str, agent_address: str, chain_rpc: str) -> None: """Analyze gas costs and suggest funding amount.""" _print_subsection_header(f"Funding Recommendation for {chain_name}") transactions = gas_costs.get(chain_id, []) + average_gas_price = _calculate_average_gas_price(chain_rpc) if not transactions: - no_data_message = f"[{chain_name}] No transaction data available for analysis." - print(_color_string(no_data_message, ColorCode.YELLOW)) - return - - total_gas_cost = sum(Decimal(tx["gas_cost"]) for tx in transactions) - average_gas_price = sum(Decimal(tx["gas_price"]) for tx in transactions) / Decimal(len(transactions)) - average_gas_cost = total_gas_cost / Decimal(len(transactions)) + average_gas_used = 2_000_000 + else: + total_gas_used = sum(Decimal(tx["gas_used"]) for tx in transactions) + average_gas_used = total_gas_used / Decimal(len(transactions)) + average_gas_cost = average_gas_used * average_gas_price funding_needed, funding_suggestion = _calculate_funding_needed(average_gas_cost, balance_info) _report_funding_status(chain_name, balance_info, average_gas_cost, average_gas_price, funding_suggestion, funding_needed, agent_address) +def _calculate_average_gas_price(rpc, fee_history_blocks: int = 7000) -> Decimal: + web3 = Web3(Web3.HTTPProvider(rpc)) + block_number = web3.eth.block_number + fee_history = web3.eth.fee_history( + fee_history_blocks, block_number, [50] + ) + base_fees = fee_history['baseFeePerGas'] + priority_fees = [reward[0] for reward in fee_history['reward'] if reward] + + # Calculate average fees + average_base_fee = sum(base_fees) / len(base_fees) + average_priority_fee = sum(priority_fees) / len(priority_fees) + + average_gas_price = average_base_fee + average_priority_fee + return Decimal(average_gas_price) + def _calculate_funding_needed(average_gas_cost: Decimal, balance_info: Any) -> Tuple[Decimal,Decimal]: """Calculate the funding needed based on average gas cost and current balance.""" funding_suggestion = Decimal(average_gas_cost) * FUNDING_MULTIPLIER / Decimal(1e18) From 98db3947da00895d380d5096112f22661da5f26d Mon Sep 17 00:00:00 2001 From: Divya-Solulab Date: Wed, 16 Oct 2024 19:03:38 +0530 Subject: [PATCH 4/5] fix: update service hash --- run_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_service.py b/run_service.py index 9a9f46d..8de14de 100644 --- a/run_service.py +++ b/run_service.py @@ -358,7 +358,7 @@ def get_service_template(config: OptimusConfig) -> ServiceTemplate: """Get the service template""" return ServiceTemplate({ "name": "Optimus", - "hash": "bafybeige2z6ek4qictsutpyc4njfokrabvm3w7fffq343iltf5xssxzol4", + "hash": "bafybeibiiuhqronhgkxjo7x5xve24lkbqom5rqcjxg7vrl6jwavfyypmhu", "description": "Optimus", "image": "https://operate.olas.network/_next/image?url=%2Fimages%2Fprediction-agent.png&w=3840&q=75", From 7b883fd1ebbd714c0ae8168182d21eb01ef05f64 Mon Sep 17 00:00:00 2001 From: Divya-Solulab Date: Wed, 16 Oct 2024 19:22:59 +0530 Subject: [PATCH 5/5] fix: change average gas requirement --- suggest_funding_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suggest_funding_report.py b/suggest_funding_report.py index 9205fc6..de60882 100644 --- a/suggest_funding_report.py +++ b/suggest_funding_report.py @@ -100,7 +100,7 @@ def analyze_and_report_gas_costs(gas_costs: dict, balance_info: Any, chain_id: i transactions = gas_costs.get(chain_id, []) average_gas_price = _calculate_average_gas_price(chain_rpc) if not transactions: - average_gas_used = 2_000_000 + average_gas_used = 2_00_000 else: total_gas_used = sum(Decimal(tx["gas_used"]) for tx in transactions) average_gas_used = total_gas_used / Decimal(len(transactions))