Skip to content

Commit 8b1120b

Browse files
authoredSep 18, 2024··
Merge pull request #11 from valory-xyz/fix/fund-ops
Fix/fund ops
2 parents 1bf2a6b + 7ae87ef commit 8b1120b

12 files changed

+4534
-4062
lines changed
 

‎README.md

+4
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ Once the command has completed, i.e. the service is running, you can see the liv
8585
```bash
8686
docker logs optimus_abci_0 --follow
8787
```
88+
Execute the report command to view a summary of the service status:
8889

90+
```bash
91+
poetry run python report.py
92+
```
8993
To inspect the tree state transition of the current run of the agent run:
9094
```bash
9195
poetry run autonomy analyse logs --from-dir .optimus/services/[service-hash]/deployment/persistent_data/logs/ --agent aea_0 --fsm --reset-db

‎operate/services/manage.py

+69-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from aea.helpers.base import IPFSHash
3434
from aea.helpers.logging import setup_logger
3535
from autonomy.chain.base import registry_contracts
36+
from pycparser.ply.yacc import token
3637

3738
from operate.keys import Key, KeysManager
3839
from operate.ledger import PUBLIC_RPCS
@@ -475,7 +476,7 @@ def deploy_service_onchain_from_safe_single_chain( # pylint: disable=too-many-s
475476
is_update = (
476477
(not is_first_mint)
477478
and (on_chain_hash is not None)
478-
and (on_chain_hash != service.hash or current_agent_id != agent_id)
479+
and (current_agent_id != agent_id)
479480
)
480481

481482
if is_update:
@@ -1102,6 +1103,73 @@ def fund_service( # pylint: disable=too-many-arguments
11021103
rpc=rpc or ledger_config.rpc,
11031104
)
11041105

1106+
def fund_service_erc20( # pylint: disable=too-many-arguments
1107+
self,
1108+
hash: str,
1109+
token: str,
1110+
rpc: t.Optional[str] = None,
1111+
agent_topup: t.Optional[float] = None,
1112+
safe_topup: t.Optional[float] = None,
1113+
agent_fund_threshold: t.Optional[float] = None,
1114+
safe_fund_treshold: t.Optional[float] = None,
1115+
from_safe: bool = True,
1116+
chain_id: str = "10",
1117+
) -> None:
1118+
"""Fund service if required."""
1119+
service = self.load_or_create(hash=hash)
1120+
chain_config = service.chain_configs[chain_id]
1121+
ledger_config = chain_config.ledger_config
1122+
chain_data = chain_config.chain_data
1123+
wallet = self.wallet_manager.load(ledger_config.type)
1124+
ledger_api = wallet.ledger_api(chain_type=ledger_config.chain, rpc=rpc or ledger_config.rpc)
1125+
agent_fund_threshold = (
1126+
agent_fund_threshold
1127+
or chain_data.user_params.fund_requirements.agent
1128+
)
1129+
1130+
for key in service.keys:
1131+
agent_balance = ledger_api.get_balance(address=key.address)
1132+
self.logger.info(f"Agent {key.address} balance: {agent_balance}")
1133+
self.logger.info(f"Required balance: {agent_fund_threshold}")
1134+
if agent_balance < agent_fund_threshold:
1135+
self.logger.info("Funding agents")
1136+
to_transfer = (
1137+
agent_topup
1138+
or chain_data.user_params.fund_requirements.agent
1139+
)
1140+
self.logger.info(f"Transferring {to_transfer} units to {key.address}")
1141+
wallet.transfer_erc20(
1142+
token=token,
1143+
to=key.address,
1144+
amount=int(to_transfer),
1145+
chain_type=ledger_config.chain,
1146+
from_safe=from_safe,
1147+
rpc=rpc or ledger_config.rpc,
1148+
)
1149+
1150+
safe_balance = registry_contracts.erc20.get_instance(ledger_api, token).functions.balanceOf(chain_data.multisig).call()
1151+
safe_fund_treshold = (
1152+
safe_fund_treshold or chain_data.user_params.fund_requirements.safe
1153+
)
1154+
self.logger.info(f"Safe {chain_data.multisig} balance: {safe_balance}")
1155+
self.logger.info(f"Required balance: {safe_fund_treshold}")
1156+
if safe_balance < safe_fund_treshold:
1157+
self.logger.info("Funding safe")
1158+
to_transfer = (
1159+
safe_topup or chain_data.user_params.fund_requirements.safe
1160+
)
1161+
self.logger.info(
1162+
f"Transferring {to_transfer} units to {chain_data.multisig}"
1163+
)
1164+
wallet.transfer_erc20(
1165+
token=token,
1166+
to=t.cast(str, chain_data.multisig),
1167+
amount=int(to_transfer),
1168+
chain_type=ledger_config.chain,
1169+
rpc=rpc or ledger_config.rpc,
1170+
)
1171+
1172+
11051173
async def funding_job(
11061174
self,
11071175
hash: str,

‎operate/services/protocol.py

+1,525-1,520
Large diffs are not rendered by default.

‎operate/utils/gnosis.py

+387-347
Large diffs are not rendered by default.

‎operate/wallet/master.py

+488-436
Large diffs are not rendered by default.

‎poetry.lock

+757-677
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pyproject.toml

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ operate = "operate.cli:main"
1919

2020
[tool.poetry.dependencies]
2121
python = "<3.12,>=3.9"
22-
open-autonomy = "==0.14.14.post2"
23-
open-aea-ledger-cosmos = "==1.53.0"
24-
open-aea-ledger-ethereum = "==1.53.0"
25-
open-aea-ledger-ethereum-flashbots = "==1.53.0"
26-
open-aea-cli-ipfs = "==1.53.0"
22+
open-autonomy = "==0.16.0"
23+
open-aea-ledger-cosmos = "==1.56.0"
24+
open-aea-ledger-ethereum = "==1.56.0"
25+
open-aea-ledger-ethereum-flashbots = "==1.56.0"
26+
open-aea-cli-ipfs = "==1.56.0"
2727
clea = "==0.1.0rc4"
2828
cytoolz = "==0.12.3"
2929
docker = "6.1.2"

‎report.py

+128-194
Original file line numberDiff line numberDiff line change
@@ -1,215 +1,149 @@
1+
# report.py
12
import json
23
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+
77
from run_service import (
88
get_local_config,
99
get_service_template,
10-
wei_to_token,
11-
get_erc20_balance,
1210
CHAIN_ID_TO_METADATA,
13-
USDC_ADDRESS,
1411
OPERATE_HOME,
15-
OptimusConfig,
1612
)
1713

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+
)
7825

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
8228

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
8631

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')
9934

10035
def load_wallet_info():
10136
save_wallet_info()
10237
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 {}
12847

12948
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}")
213147

214148
if __name__ == "__main__":
215149
generate_report()

‎run_service.py

100755100644
+745-587
Large diffs are not rendered by default.

‎staking_report.py

+176-227
Large diffs are not rendered by default.

‎utils.py

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# utils.py
2+
import json
3+
from pathlib import Path
4+
from datetime import datetime
5+
from decimal import Decimal, getcontext
6+
import logging
7+
import docker
8+
from web3 import Web3
9+
from web3.middleware import geth_poa_middleware
10+
from enum import Enum
11+
12+
# Set decimal precision
13+
getcontext().prec = 18
14+
15+
# Configure logging
16+
logging.basicConfig(level=logging.INFO, format='%(message)s')
17+
18+
# Terminal color codes
19+
class ColorCode:
20+
GREEN = "\033[92m"
21+
RED = "\033[91m"
22+
YELLOW = "\033[93m"
23+
RESET = "\033[0m"
24+
25+
class StakingState(Enum):
26+
"""Staking state enumeration for the staking."""
27+
UNSTAKED = 0
28+
STAKED = 1
29+
EVICTED = 2
30+
31+
def _color_string(text: str, color_code: str) -> str:
32+
return f"{color_code}{text}{ColorCode.RESET}"
33+
34+
def _color_bool(is_true: bool, true_string: str = "True", false_string: str = "False") -> str:
35+
if is_true:
36+
return _color_string(true_string, ColorCode.GREEN)
37+
return _color_string(false_string, ColorCode.RED)
38+
39+
def _warning_message(current_value: Decimal, threshold: Decimal, message: str = "") -> str:
40+
default_message = _color_string(
41+
f"- Value too low. Threshold is {threshold:.2f}.",
42+
ColorCode.YELLOW,
43+
)
44+
if current_value < threshold:
45+
return _color_string(message or default_message, ColorCode.YELLOW)
46+
return ""
47+
48+
def _print_section_header(header: str, output_width: int = 80) -> None:
49+
print("\n\n" + header)
50+
print("=" * output_width)
51+
52+
def _print_subsection_header(header: str, output_width: int = 80) -> None:
53+
print("\n" + header)
54+
print("-" * output_width)
55+
56+
def _print_status(key: str, value: str, message: str = "") -> None:
57+
line = f"{key:<30}{value:<20}"
58+
if message:
59+
line += f"{message}"
60+
print(line)
61+
62+
def wei_to_unit(wei: int) -> Decimal:
63+
"""Convert Wei to unit."""
64+
return Decimal(wei) / Decimal(1e18)
65+
66+
def wei_to_token(wei: int, token: str = "xDAI") -> str:
67+
"""Convert Wei to token."""
68+
return f"{wei_to_unit(wei):.2f} {token}"
69+
70+
def wei_to_olas(wei: int) -> str:
71+
"""Converts and formats wei to OLAS."""
72+
return "{:.2f} OLAS".format(wei_to_unit(wei))
73+
74+
def wei_to_eth(wei_value):
75+
return Decimal(wei_value) / Decimal(1e18)
76+
77+
def get_chain_name(chain_id, chain_id_to_metadata):
78+
return chain_id_to_metadata.get(int(chain_id), {}).get("name", f"Chain {chain_id}")
79+
80+
def load_operator_address(operate_home):
81+
ethereum_json_path = operate_home / "wallets" / "ethereum.json"
82+
try:
83+
with open(ethereum_json_path, "r") as f:
84+
ethereum_data = json.load(f)
85+
operator_address = ethereum_data.get("safes", {}).get("4")
86+
if not operator_address:
87+
print("Error: Operator address not found for chain ID 4 in the wallet file.")
88+
return None
89+
return operator_address
90+
except FileNotFoundError:
91+
print(f"Error: Ethereum wallet file not found at {ethereum_json_path}")
92+
return None
93+
except json.JSONDecodeError:
94+
print("Error: Ethereum wallet file contains invalid JSON.")
95+
return None
96+
97+
def validate_config(config):
98+
required_keys = ['home_chain_id', 'chain_configs']
99+
for key in required_keys:
100+
if key not in config:
101+
print(f"Error: '{key}' is missing in the configuration.")
102+
return False
103+
return True
104+
105+
def _get_agent_status() -> str:
106+
try:
107+
client = docker.from_env()
108+
container = client.containers.get("optimus_abci_0")
109+
is_running = container.status == "running"
110+
return _color_bool(is_running, "Running", "Stopped")
111+
except docker.errors.NotFound:
112+
return _color_string("Not Found", ColorCode.RED)
113+
except docker.errors.DockerException as e:
114+
print(f"Error: Docker exception occurred - {str(e)}")
115+
return _color_string("Error", ColorCode.RED)

‎wallet_info.py

+135-68
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,83 @@
1+
# wallet_info.py
12
import json
23
from pathlib import Path
34
from web3 import Web3
45
from web3.middleware import geth_poa_middleware
5-
from decimal import Decimal
6+
from decimal import Decimal, getcontext
7+
import logging
8+
69
from run_service import (
710
get_local_config,
811
get_service_template,
9-
wei_to_token,
10-
get_erc20_balance,
1112
CHAIN_ID_TO_METADATA,
1213
USDC_ADDRESS,
1314
OPERATE_HOME,
14-
OptimusConfig,
1515
)
1616

17+
from utils import (
18+
get_chain_name,
19+
load_operator_address,
20+
validate_config,
21+
wei_to_unit,
22+
wei_to_eth,
23+
ColorCode,
24+
)
1725

26+
# Set decimal precision
27+
getcontext().prec = 18
1828

19-
USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
20-
USDC_ABI = [{"constant":True,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"type":"function"}]
29+
# Configure logging
30+
logging.basicConfig(level=logging.INFO, format='%(message)s')
2131

22-
def load_config():
23-
optimus_config = get_local_config()
24-
service_template = get_service_template(optimus_config)
25-
service_hash = service_template["hash"]
32+
# Ensure USDC_ADDRESS is set (if not, define it here)
33+
USDC_ADDRESS = USDC_ADDRESS or "0xA0b86991c6218b36c1d19d4a2e9Eb0cE3606eB48" # Ethereum Mainnet USDC address
2634

27-
config_path = OPERATE_HOME / f"services/{service_hash}/config.json"
28-
with open(config_path, "r") as f:
29-
return json.load(f)
35+
USDC_ABI = [{
36+
"constant": True,
37+
"inputs": [{"name": "_owner", "type": "address"}],
38+
"name": "balanceOf",
39+
"outputs": [{"name": "balance", "type": "uint256"}],
40+
"type": "function"
41+
}]
42+
43+
def load_config():
44+
try:
45+
optimus_config = get_local_config()
46+
service_template = get_service_template(optimus_config)
47+
service_hash = service_template.get("hash")
48+
if not service_hash:
49+
print("Error: Service hash not found in service template.")
50+
return {}
51+
config_path = OPERATE_HOME / f"services/{service_hash}/config.json"
52+
try:
53+
with open(config_path, "r") as f:
54+
return json.load(f)
55+
except FileNotFoundError:
56+
print(f"Error: Config file not found at {config_path}")
57+
return {}
58+
except json.JSONDecodeError:
59+
print("Error: Config file contains invalid JSON.")
60+
return {}
61+
except Exception as e:
62+
print(f"An error occurred while loading the config: {e}")
63+
return {}
3064

3165
def get_balance(web3, address):
32-
balance = web3.eth.get_balance(address)
33-
return float(Web3.from_wei(balance, 'ether'))
66+
try:
67+
balance = web3.eth.get_balance(address)
68+
return Decimal(Web3.from_wei(balance, 'ether'))
69+
except Exception as e:
70+
print(f"Error getting balance for address {address}: {e}")
71+
return Decimal(0)
3472

3573
def get_usdc_balance(web3, address):
36-
usdc_contract = web3.eth.contract(address=USDC_ADDRESS, abi=USDC_ABI)
37-
balance = usdc_contract.functions.balanceOf(address).call()
38-
return float(balance) / 1e6 # USDC has 6 decimal places
74+
try:
75+
usdc_contract = web3.eth.contract(address=USDC_ADDRESS, abi=USDC_ABI)
76+
balance = usdc_contract.functions.balanceOf(address).call()
77+
return Decimal(balance) / Decimal(1e6) # USDC has 6 decimal places
78+
except Exception as e:
79+
print(f"Error getting USDC balance for address {address}: {e}")
80+
return Decimal(0)
3981

4082
class DecimalEncoder(json.JSONEncoder):
4183
def default(self, o):
@@ -45,67 +87,92 @@ def default(self, o):
4587

4688
def save_wallet_info():
4789
config = load_config()
48-
main_wallet_address = config['keys'][0]['address']
49-
90+
if not config:
91+
print("Error: Configuration could not be loaded.")
92+
return
93+
94+
main_wallet_address = config.get('keys', [{}])[0].get('address')
95+
if not main_wallet_address:
96+
print("Error: Main wallet address not found in configuration.")
97+
return
98+
5099
main_balances = {}
51100
safe_balances = {}
52-
53-
for chain_id, chain_config in config['chain_configs'].items():
54-
rpc_url = chain_config['ledger_config']['rpc']
55-
web3 = Web3(Web3.HTTPProvider(rpc_url))
56-
web3.middleware_onion.inject(geth_poa_middleware, layer=0)
57-
58-
chain_name = {
59-
"1": "Ethereum Mainnet",
60-
"10": "Optimism",
61-
"8453": "Base"
62-
}.get(chain_id, f"Chain {chain_id}")
63-
64-
# Get main wallet balance
65-
main_balance = get_balance(web3, main_wallet_address)
66-
main_balances[chain_name] = {
67-
"token": "ETH",
68-
"balance": main_balance,
69-
"balance_formatted": f"{main_balance:.6f} ETH"
70-
}
71-
72-
# Get Safe balance
73-
safe_address = chain_config['chain_data']['multisig']
74-
safe_balance = get_balance(web3, safe_address)
75-
safe_balances[chain_name] = {
76-
"address": safe_address,
77-
"token": "ETH",
78-
"balance": safe_balance,
79-
"balance_formatted": f"{safe_balance:.6f} ETH"
80-
}
81-
82-
# Get USDC balance for Ethereum Mainnet
83-
if chain_id == "1":
84-
usdc_balance = get_usdc_balance(web3, safe_address)
85-
safe_balances[chain_name]["usdc_balance"] = usdc_balance
86-
safe_balances[chain_name]["usdc_balance_formatted"] = f"{usdc_balance:.2f} USDC"
87-
101+
102+
for chain_id, chain_config in config.get('chain_configs', {}).items():
103+
rpc_url = chain_config.get('ledger_config', {}).get('rpc')
104+
if not rpc_url:
105+
print(f"Error: RPC URL not found for chain ID {chain_id}.")
106+
continue
107+
108+
try:
109+
web3 = Web3(Web3.HTTPProvider(rpc_url))
110+
if chain_id != "1": # Ethereum Mainnet
111+
web3.middleware_onion.inject(geth_poa_middleware, layer=0)
112+
113+
chain_name = get_chain_name(chain_id, CHAIN_ID_TO_METADATA)
114+
115+
# Get main wallet balance
116+
main_balance = get_balance(web3, main_wallet_address)
117+
main_balances[chain_name] = {
118+
"token": "ETH",
119+
"balance": main_balance,
120+
"balance_formatted": f"{main_balance:.6f} ETH"
121+
}
122+
123+
# Get Safe balance
124+
safe_address = chain_config.get('chain_data', {}).get('multisig')
125+
if not safe_address:
126+
print(f"Error: Safe address not found for chain ID {chain_id}.")
127+
continue
128+
129+
safe_balance = get_balance(web3, safe_address)
130+
safe_balances[chain_name] = {
131+
"address": safe_address,
132+
"token": "ETH",
133+
"balance": safe_balance,
134+
"balance_formatted": f"{safe_balance:.6f} ETH"
135+
}
136+
137+
# Get USDC balance for Ethereum Mainnet (chain ID 1)
138+
if chain_id == "1":
139+
usdc_balance = get_usdc_balance(web3, safe_address)
140+
safe_balances[chain_name]["usdc_balance"] = usdc_balance
141+
safe_balances[chain_name]["usdc_balance_formatted"] = f"{usdc_balance:.2f} USDC"
142+
143+
except Exception as e:
144+
print(f"An error occurred while processing chain ID {chain_id}: {e}")
145+
continue
146+
88147
wallet_info = {
89148
"main_wallet_address": main_wallet_address,
90149
"main_wallet_balances": main_balances,
91-
"safe_addresses": {chain_id: config['chain_configs'][chain_id]['chain_data']['multisig'] for chain_id in config['chain_configs']},
150+
"safe_addresses": {
151+
chain_id: chain_config.get('chain_data', {}).get('multisig', 'N/A')
152+
for chain_id, chain_config in config.get('chain_configs', {}).items()
153+
},
92154
"safe_balances": safe_balances,
93155
"chain_configs": {
94156
chain_id: {
95-
"rpc": chain_config['ledger_config']['rpc'],
96-
"multisig": chain_config['chain_data']['multisig'],
97-
"token": chain_config['chain_data']['token'],
98-
"fund_requirements": chain_config['chain_data']['user_params']['fund_requirements']
157+
"rpc": chain_config.get('ledger_config', {}).get('rpc'),
158+
"multisig": chain_config.get('chain_data', {}).get('multisig'),
159+
"token": chain_config.get('chain_data', {}).get('token'),
160+
"fund_requirements": chain_config.get('chain_data', {}).get('user_params', {}).get('fund_requirements')
99161
}
100-
for chain_id, chain_config in config['chain_configs'].items()
162+
for chain_id, chain_config in config.get('chain_configs', {}).items()
101163
}
102164
}
103-
165+
104166
file_path = OPERATE_HOME / "wallets" / "wallet_info.json"
105-
with open(file_path, "w") as f:
106-
json.dump(wallet_info, f, indent=2, cls=DecimalEncoder)
107-
108-
print(f"Wallet information saved to {file_path}")
167+
try:
168+
with open(file_path, "w") as f:
169+
json.dump(wallet_info, f, indent=2, cls=DecimalEncoder)
170+
print(f"Wallet info saved to {file_path}")
171+
except Exception as e:
172+
print(f"Error saving wallet info to {file_path}: {e}")
109173

110174
if __name__ == "__main__":
111-
save_wallet_info()
175+
try:
176+
save_wallet_info()
177+
except Exception as e:
178+
print(f"An unexpected error occurred: {e}")

0 commit comments

Comments
 (0)
Please sign in to comment.