Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add weth lending strategy #8

Merged
merged 14 commits into from
Dec 10, 2024
10 changes: 10 additions & 0 deletions operate/services/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,16 @@ def _build_docker(
# "SKILL_TRADER_ABCI_MODELS_PARAMS_ARGS_MECH_REQUEST_PRICE=10000000000000000" # noqa
# ) # noqa

# temporary fix: remove extra volume
for service_name, service_data in deployment["services"].items():
if "abci" in service_name:
# Access the volumes list in this service
volumes = service_data.get("volumes", [])

# Remove './data:/data:Z' if it's in the volumes list
if "./data:/data:Z" in volumes:
volumes.remove("./data:/data:Z")

with (build / DOCKER_COMPOSE_YAML).open("w", encoding="utf-8") as stream:
yaml_dump(data=deployment, stream=stream)

Expand Down
5 changes: 5 additions & 0 deletions report.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ def generate_report():
usdc_balance_formatted = safe_info.get('usdc_balance_formatted', 'N/A')
_print_status("USDC Balance", usdc_balance_formatted)

# Check for OLAS balance on Staking chain
if chain_name.lower() == optimus_config.staking_chain:
olas_balance_formatted = safe_info.get('olas_balance_formatted', 'N/A')
_print_status("OLAS Balance", olas_balance_formatted)

# Low balance check
safe_threshold_wei = chain_config.get("chain_data", {}).get("user_params", {}).get("fund_requirements", {}).get("safe")
if safe_threshold_wei:
Expand Down
99 changes: 74 additions & 25 deletions run_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from dataclasses import dataclass
from pathlib import Path
from decimal import Decimal, ROUND_UP
from enum import Enum

import requests
import yaml
Expand All @@ -43,7 +44,7 @@
from termcolor import colored
from web3 import Web3
from web3.types import Wei, TxParams
from typing import Dict, Any
from typing import Dict, Any, Optional

from operate.account.user import UserAccount
from operate.cli import OperateApp
Expand Down Expand Up @@ -94,6 +95,12 @@
DEFAULT_MAX_FEE = 20000000
use_default_max_fee = True

class Strategy(Enum):
"""Strategy type"""
MerklPoolSearchStrategy = "merkl_pools_search"
BalancerPoolSearchStrategy = "balancer_pools_search"
SturdyLendingStrategy = "asset_lending"

def estimate_priority_fee(
web3_object: Web3,
block_number: int,
Expand Down Expand Up @@ -136,7 +143,7 @@ def get_masked_input(prompt: str) -> str:
password = ""
sys.stdout.write(prompt)
sys.stdout.flush()

fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
Expand Down Expand Up @@ -176,6 +183,7 @@ class OptimusConfig(LocalResource):
staking_chain: t.Optional[str] = None
principal_chain: t.Optional[str] = None
investment_funding_requirements: t.Optional[Dict[str, Any]] = None
selected_strategies: t.Optional[list[str]] = None

@classmethod
def from_json(cls, obj: t.Dict) -> "LocalResource":
Expand Down Expand Up @@ -391,6 +399,9 @@ def configure_local_config() -> OptimusConfig:

print()

if optimus_config.selected_strategies is None:
optimus_config.selected_strategies = [Strategy.MerklPoolSearchStrategy.value, Strategy.BalancerPoolSearchStrategy.value, Strategy.SturdyLendingStrategy.value]

optimus_config.store()
return optimus_config

Expand Down Expand Up @@ -425,7 +436,7 @@ def get_service_template(config: OptimusConfig) -> ServiceTemplate:
home_chain_id = "34443"
return ServiceTemplate({
"name": "Optimus",
"hash": "bafybeiazaphqrn65tvscbubjvuh6mzmodqp3inwayjmye2jjweu3uea7wi",
"hash": "bafybeigp444eluxn77x5qdtuqok45gbhrg7l2ptdgntafldbay3x7bpsja",

"description": "Optimus",
"image": "https://gateway.autonolas.tech/ipfs/bafybeiaakdeconw7j5z76fgghfdjmsr6tzejotxcwnvmp3nroaw3glgyve",
Expand All @@ -439,7 +450,7 @@ def get_service_template(config: OptimusConfig) -> ServiceTemplate:
"nft": "bafybeiaakdeconw7j5z76fgghfdjmsr6tzejotxcwnvmp3nroaw3glgyve",
"cost_of_bond": COST_OF_BOND_STAKING if config.staking_chain == "mode" else COST_OF_BOND,
"threshold": 1,
"use_staking": config.use_staking and config.staking_chain == "mode", ###
"use_staking": config.use_staking and config.staking_chain == "mode",
"fund_requirements": FundRequirementsTemplate(
{
"agent": SUGGESTED_TOP_UP_DEFAULT * 5,
Expand Down Expand Up @@ -580,26 +591,59 @@ def fetch_investing_funding_requirements(chain_name: str) -> None:

optimus_config.store()

def calculate_fund_requirement(rpc, fee_history_blocks: int, gas_amount: int, fee_history_percentile: int = 50, priority_fee_increase_boundary: int = 200) -> int:
if rpc is None:
def calculate_fund_requirement(
rpc: str,
fee_history_blocks: int,
gas_amount: int,
fee_history_percentile: int = 50,
safety_margin: int = 500_000_000_000_000
) -> Optional[int]:
"""
Calculate the estimated fund requirement for a transaction.

Args:
rpc (str): RPC URL of the Ethereum node.
fee_history_blocks (int): Number of recent blocks for fee history.
gas_amount (int): Gas amount required for the transaction.
fee_history_percentile (int): Percentile for priority fee (default: 50).
safety_margin (int): Safety margin in wei (default: 500,000,000,000,000).

Returns:
Optional[int]: Estimated fund requirement in wei, or None if unavailable.
"""
if not rpc:
print("RPC URL is required.")
return None

web3 = Web3(Web3.HTTPProvider(rpc))
block_number = web3.eth.block_number
latest_block = web3.eth.get_block("latest")
base_fee = latest_block.get("baseFeePerGas")

if base_fee is None or block_number is None:

try:
web3 = Web3(Web3.HTTPProvider(rpc))
block_number = web3.eth.block_number
fee_history = web3.eth.fee_history(
fee_history_blocks, block_number, [fee_history_percentile]
)
except Exception as e:
return None

priority_fee = estimate_priority_fee(web3, block_number, fee_history_blocks, fee_history_percentile, priority_fee_increase_boundary)
if priority_fee is None:

if not fee_history or 'baseFeePerGas' not in fee_history:
return None

base_fees = fee_history.get('baseFeePerGas', [])
priority_fees = [reward[0] for reward in fee_history.get('reward', []) if reward]

if not base_fees or not priority_fees:
return None

try:
# Calculate averages
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

# Calculate fund requirement
fund_requirement = int((average_gas_price * gas_amount) + safety_margin)
return fund_requirement
except Exception as e:
return None

gas_price = base_fee + priority_fee
safety_margin = 500_000_000_000_000
fund_requirement = int((gas_price * gas_amount) + safety_margin)
return fund_requirement

def fetch_agent_fund_requirement(chain_id, rpc, fee_history_blocks: int = 500000) -> int:
gas_amount = 50_000_000
Expand All @@ -608,8 +652,12 @@ def fetch_agent_fund_requirement(chain_id, rpc, fee_history_blocks: int = 500000

return calculate_fund_requirement(rpc, fee_history_blocks, gas_amount)

def fetch_operator_fund_requirement(chain_id, rpc, fee_history_blocks: int = 500000) -> int:
gas_amount = 30_000_000
def fetch_operator_fund_requirement(chain_id, rpc, service_exists: bool = True, fee_history_blocks: int = 500000) -> int:
if service_exists:
gas_amount = 5_000_000
else:
gas_amount = 30_000_000

if use_default_max_fee:
return DEFAULT_MAX_FEE * gas_amount

Expand Down Expand Up @@ -695,7 +743,7 @@ def main() -> None:
if agent_fund_requirement is None:
agent_fund_requirement = chain_config.chain_data.user_params.fund_requirements.agent

operational_fund_req = fetch_operator_fund_requirement(chain_id, chain_config.ledger_config.rpc)
operational_fund_req = fetch_operator_fund_requirement(chain_id, chain_config.ledger_config.rpc, service_exists)
if operational_fund_req is None:
operational_fund_req = chain_metadata.get("operationalFundReq")

Expand Down Expand Up @@ -860,7 +908,8 @@ def main() -> None:
"MIN_SWAP_AMOUNT_THRESHOLD": optimus_config.min_swap_amount_threshold,
"ALLOWED_CHAINS": json.dumps(optimus_config.allowed_chains),
"TARGET_INVESTMENT_CHAINS": json.dumps(optimus_config.target_investment_chains),
"INITIAL_ASSETS": json.dumps(initial_assets)
"INITIAL_ASSETS": json.dumps(initial_assets),
"SELECTED_STRATEGIES": json.dumps(optimus_config.selected_strategies)
}
apply_env_vars(env_vars)
print("Skipping local deployment")
Expand Down
21 changes: 19 additions & 2 deletions wallet_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(message)s')

USDC_ABI = [{
TOKEN_ABI = [{
"constant": True,
"inputs": [{"name": "_owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "balance", "type": "uint256"}],
"type": "function"
}]

OLAS_ADDRESS = "0xcfD1D50ce23C46D3Cf6407487B2F8934e96DC8f9"

def load_config():
try:
optimus_config = load_local_config()
Expand Down Expand Up @@ -69,13 +71,22 @@ def get_balance(web3, address):

def get_usdc_balance(web3, address, chain_name):
try:
usdc_contract = web3.eth.contract(address=USDC_ADDRESS, abi=USDC_ABI)
usdc_contract = web3.eth.contract(address=USDC_ADDRESS, abi=TOKEN_ABI)
balance = usdc_contract.functions.balanceOf(address).call()
return Decimal(balance) / Decimal(1e6) # USDC has 6 decimal places
except Exception as e:
print(f"Error getting USDC balance for address {address}: {e}")
return Decimal(0)

def get_olas_balance(web3, address, chain_name):
try:
olas_address = OLAS_ADDRESS[chain_name]
olas_contract = web3.eth.contract(address=OLAS_ADDRESS, abi=TOKEN_ABI)
balance = olas_contract.functions.balanceOf(address).call()
return Decimal(balance) / Decimal(1e18) # OLAS has 18 decimal places
except Exception as e:
print(f"Error getting OLAS balance for address {address}: {e}")
return Decimal(0)
class DecimalEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Decimal):
Expand Down Expand Up @@ -148,6 +159,12 @@ def save_wallet_info():
safe_balances[chain_name]["usdc_balance"] = usdc_balance
safe_balances[chain_name]["usdc_balance_formatted"] = f"{usdc_balance:.2f} USDC"

# Get USDC balance for Principal Chain
if chain_name.lower() == optimus_config.staking_chain:
olas_balance = get_olas_balance(web3, safe_address, chain_name.lower())
safe_balances[chain_name]["olas_balance"] = usdc_balance
safe_balances[chain_name]["olas_balance_formatted"] = f"{olas_balance:.6f} OLAS"

except Exception as e:
print(f"An error occurred while processing chain ID {chain_id}: {e}")
continue
Expand Down
Loading