diff --git a/prediction_market_agent_tooling/markets/omen/data_models.py b/prediction_market_agent_tooling/markets/omen/data_models.py index c382f28c..21a8b8b4 100644 --- a/prediction_market_agent_tooling/markets/omen/data_models.py +++ b/prediction_market_agent_tooling/markets/omen/data_models.py @@ -23,6 +23,11 @@ Resolution, ResolvedBet, ) +from prediction_market_agent_tooling.tools.contract import ( + ContractERC20OnGnosisChain, + init_collateral_token_contract, + to_gnosis_chain_contract, +) from prediction_market_agent_tooling.tools.utils import ( BPS_CONSTANT, DatetimeUTC, @@ -168,6 +173,16 @@ def index_set(self) -> int: def collateral_token_contract_address_checksummed(self) -> ChecksumAddress: return Web3.to_checksum_address(self.collateralTokenAddress) + def get_collateral_token_contract( + self, web3: Web3 | None + ) -> ContractERC20OnGnosisChain: + web3 = web3 or ContractERC20OnGnosisChain.get_web3() + return to_gnosis_chain_contract( + init_collateral_token_contract( + self.collateral_token_contract_address_checksummed, web3 + ) + ) + class OmenUserPosition(BaseModel): id: HexBytes diff --git a/prediction_market_agent_tooling/markets/omen/omen.py b/prediction_market_agent_tooling/markets/omen/omen.py index df26926d..f482a364 100644 --- a/prediction_market_agent_tooling/markets/omen/omen.py +++ b/prediction_market_agent_tooling/markets/omen/omen.py @@ -80,6 +80,7 @@ from prediction_market_agent_tooling.tools.tokens.auto_withdraw import ( auto_withdraw_collateral_token, ) +from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN from prediction_market_agent_tooling.tools.utils import ( DatetimeUTC, calculate_sell_amount_in_collateral, @@ -190,11 +191,10 @@ def liquidate_existing_positions( for position_outcome, token_amount in prev_position.amounts.items(): position_outcome_bool = get_boolean_outcome(position_outcome) if position_outcome_bool != bet_outcome: - # We keep it as collateral since we want to place a bet immediately after this function. self.sell_tokens( outcome=position_outcome_bool, amount=token_amount, - auto_withdraw=False, + auto_withdraw=True, web3=web3, api_keys=api_keys, ) @@ -259,7 +259,7 @@ def sell_tokens( self, outcome: bool, amount: TokenAmount, - auto_withdraw: bool = False, + auto_withdraw: bool = True, api_keys: APIKeys | None = None, web3: Web3 | None = None, ) -> str: @@ -408,8 +408,12 @@ def redeem_winnings(api_keys: APIKeys) -> None: @staticmethod def get_trade_balance(api_keys: APIKeys, web3: Web3 | None = None) -> xDai: - return get_total_balance( - address=api_keys.bet_from_address, web3=web3, sum_xdai=True, sum_wxdai=True + native_token_balance = get_balances(api_keys.bet_from_address, web3=web3).xdai + return xdai_type( + wei_to_xdai( + KEEPING_ERC20_TOKEN.balanceOf(api_keys.bet_from_address, web3=web3) + ) + + native_token_balance ) @staticmethod @@ -1060,6 +1064,7 @@ def omen_fund_market_tx( def omen_redeem_full_position_tx( api_keys: APIKeys, market: OmenAgentMarket, + auto_withdraw: bool = True, web3: Web3 | None = None, ) -> None: """ @@ -1071,6 +1076,7 @@ def omen_redeem_full_position_tx( market_contract: OmenFixedProductMarketMakerContract = market.get_contract() conditional_token_contract = OmenConditionalTokenContract() + collateral_token_contract = market_contract.get_collateral_token_contract(web3) # Verify, that markets uses conditional tokens that we expect. if market_contract.conditionalTokens() != conditional_token_contract.address: @@ -1094,6 +1100,7 @@ def omen_redeem_full_position_tx( logger.debug("Market not yet resolved, not possible to claim") return + original_balance = collateral_token_contract.balanceOf(from_address, web3=web3) conditional_token_contract.redeemPositions( api_keys=api_keys, collateral_token_address=market.collateral_token_contract_address_checksummed, @@ -1101,6 +1108,20 @@ def omen_redeem_full_position_tx( index_sets=market.condition.index_sets, web3=web3, ) + new_balance = collateral_token_contract.balanceOf(from_address, web3=web3) + balance_diff = wei_type(new_balance - original_balance) + + logger.info( + f"Redeemed {wei_to_xdai(balance_diff)} {collateral_token_contract.symbol_cached(web3=web3)} from market {market.question=} ({market.url})." + ) + + if auto_withdraw: + auto_withdraw_collateral_token( + collateral_token_contract=collateral_token_contract, + amount_wei=balance_diff, + api_keys=api_keys, + web3=web3, + ) def get_conditional_tokens_balance_for_market( @@ -1139,13 +1160,14 @@ def omen_remove_fund_market_tx( market: OmenAgentMarket, shares: Wei | None, web3: Web3 | None = None, + auto_withdraw: bool = True, ) -> None: """ Removes funding from a given OmenMarket (moving the funds from the OmenMarket to the ConditionalTokens contract), and finally calls the `mergePositions` method which transfers collateralToken from the ConditionalTokens contract to the address corresponding to `from_private_key`. Warning: Liquidity removal works on the principle of getting market's shares, not the collateral token itself. - After we remove funding, using the `mergePositions` we get `min(shares per index)` of wxDai back, but the remaining shares can be converted back only after the market is resolved. + After we remove funding, using the `mergePositions` we get `min(shares per index)` of collateral token back, but the remaining shares can be converted back only after the market is resolved. That can be done using the `redeem_from_all_user_positions` function below. """ from_address = api_keys.bet_from_address @@ -1189,16 +1211,26 @@ def omen_remove_fund_market_tx( ) new_balance = market_collateral_token_contract.balanceOf(from_address, web3=web3) + balance_diff = wei_type(new_balance - original_balance) logger.debug(f"Result from merge positions {result}") logger.info( - f"Withdrawn {new_balance - original_balance} {market_collateral_token_contract.symbol_cached(web3=web3)} from liquidity at {market.url=}." + f"Withdrawn {wei_to_xdai(balance_diff)} {market_collateral_token_contract.symbol_cached(web3=web3)} from liquidity at {market.url=}." ) + if auto_withdraw: + auto_withdraw_collateral_token( + collateral_token_contract=market_collateral_token_contract, + amount_wei=balance_diff, + api_keys=api_keys, + web3=web3, + ) + def redeem_from_all_user_positions( api_keys: APIKeys, web3: Web3 | None = None, + auto_withdraw: bool = True, ) -> None: """ Redeems from all user positions where the user didn't redeem yet. @@ -1224,8 +1256,11 @@ def redeem_from_all_user_positions( logger.info( f"[{index + 1} / {len(user_positions)}] Processing redeem from {user_position.id=}." ) + collateral_token_contract = ( + user_position.position.get_collateral_token_contract(web3=web3) + ) - original_balances = get_balances(public_key, web3) + original_balance = collateral_token_contract.balanceOf(public_key, web3=web3) conditional_token_contract.redeemPositions( api_keys=api_keys, collateral_token_address=user_position.position.collateral_token_contract_address_checksummed, @@ -1233,12 +1268,21 @@ def redeem_from_all_user_positions( index_sets=user_position.position.indexSets, web3=web3, ) - new_balances = get_balances(public_key, web3) + new_balance = collateral_token_contract.balanceOf(public_key, web3=web3) + balance_diff = wei_type(new_balance - original_balance) logger.info( - f"Redeemed {new_balances.wxdai - original_balances.wxdai} wxDai from position {user_position.id=}." + f"Redeemed {wei_to_xdai(balance_diff)} {collateral_token_contract.symbol_cached(web3=web3)} from position {user_position.id=}." ) + if auto_withdraw: + auto_withdraw_collateral_token( + collateral_token_contract=collateral_token_contract, + amount_wei=balance_diff, + api_keys=api_keys, + web3=web3, + ) + def get_binary_market_p_yes_history(market: OmenAgentMarket) -> list[Probability]: history: list[Probability] = [] diff --git a/prediction_market_agent_tooling/tools/omen/sell_positions.py b/prediction_market_agent_tooling/tools/omen/sell_positions.py index 107b0015..e0647e23 100644 --- a/prediction_market_agent_tooling/tools/omen/sell_positions.py +++ b/prediction_market_agent_tooling/tools/omen/sell_positions.py @@ -13,7 +13,7 @@ def sell_all( api_keys: APIKeys, closing_later_than_days: int, - auto_withdraw: bool = False, + auto_withdraw: bool = True, ) -> None: """ Helper function to sell all existing outcomes on Omen that would resolve later than in X days. diff --git a/prediction_market_agent_tooling/tools/tokens/auto_withdraw.py b/prediction_market_agent_tooling/tools/tokens/auto_withdraw.py index fc0b13b9..03736d28 100644 --- a/prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +++ b/prediction_market_agent_tooling/tools/tokens/auto_withdraw.py @@ -2,6 +2,7 @@ from prediction_market_agent_tooling.config import APIKeys from prediction_market_agent_tooling.gtypes import Wei +from prediction_market_agent_tooling.loggers import logger from prediction_market_agent_tooling.tools.contract import ( ContractERC20BaseClass, ContractERC4626BaseClass, @@ -12,7 +13,10 @@ ) from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN from prediction_market_agent_tooling.tools.utils import should_not_happen -from prediction_market_agent_tooling.tools.web3_utils import remove_fraction +from prediction_market_agent_tooling.tools.web3_utils import ( + remove_fraction, + wei_to_xdai, +) def auto_withdraw_collateral_token( @@ -28,8 +32,17 @@ def auto_withdraw_collateral_token( slippage, ) + if not amount_wei: + logger.warning( + f"Amount to withdraw is zero, skipping withdrawal of {collateral_token_contract.symbol_cached(web3)}." + ) + return + if collateral_token_contract.address == KEEPING_ERC20_TOKEN.address: # Do nothing, as this is the token we want to keep. + logger.info( + f"Collateral token {collateral_token_contract.symbol_cached(web3)} is the same as KEEPING_ERC20_TOKEN. Not withdrawing." + ) return elif ( isinstance(collateral_token_contract, ContractERC4626BaseClass) @@ -37,12 +50,18 @@ def auto_withdraw_collateral_token( == KEEPING_ERC20_TOKEN.address ): # If the ERC4626 is backed by KEEPING_ERC20_TOKEN, we can withdraw it directly, no need to go through DEX. + logger.info( + f"Withdrawing {wei_to_xdai(amount_wei)} from {collateral_token_contract.symbol_cached(web3)} into {KEEPING_ERC20_TOKEN.symbol_cached(web3)}" + ) collateral_token_contract.withdraw( api_keys, amount_wei, web3=web3, ) elif isinstance(collateral_token_contract, ContractERC20BaseClass): + logger.info( + f"Swapping {wei_to_xdai(amount_wei)} {collateral_token_contract.symbol_cached(web3)} into {KEEPING_ERC20_TOKEN.symbol_cached(web3)}" + ) # Otherwise, DEX will handle the rest of token swaps. # First, convert `amount_wei` from xDai-based value into the collateral token-based value. collateral_amount_wei = get_buy_token_amount( diff --git a/prediction_market_agent_tooling/tools/tokens/main_token.py b/prediction_market_agent_tooling/tools/tokens/main_token.py index 6eebc548..cbef68a8 100644 --- a/prediction_market_agent_tooling/tools/tokens/main_token.py +++ b/prediction_market_agent_tooling/tools/tokens/main_token.py @@ -7,7 +7,9 @@ # This is the token where agents will hold their funds, # except for a small portion that will be kept in the native token of the network to pay for the fees. +# Auto deposit must work from native token into this token. # If changed, then keep in mind that we assume this token is equal to 1 USD. +# Also if changed, `withdraw_wxdai_to_xdai_to_keep_balance` will require update. KEEPING_ERC20_TOKEN = ContractDepositableWrapperERC20OnGnosisChain( address=WRAPPED_XDAI_CONTRACT_ADDRESS ) diff --git a/pyproject.toml b/pyproject.toml index 4d994504..9b5439e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "prediction-market-agent-tooling" -version = "0.59.2" +version = "0.60.0" description = "Tools to benchmark, deploy and monitor prediction market agents." authors = ["Gnosis"] readme = "README.md" diff --git a/scripts/bet_omen.py b/scripts/bet_omen.py index 4228c68d..ba922f8c 100644 --- a/scripts/bet_omen.py +++ b/scripts/bet_omen.py @@ -23,7 +23,7 @@ def buy( safe_address: str = typer.Option(default=None), market_id: str = typer.Option(), outcome: str = typer.Option(), - auto_deposit: bool = typer.Option(False), + auto_deposit: bool = typer.Option(True), ) -> None: """ Helper script to place a bet on Omen, usage: @@ -61,7 +61,7 @@ def sell( safe_address: str = typer.Option(default=None), market_id: str = typer.Option(), outcome: str = typer.Option(), - auto_withdraw: bool = typer.Option(False), + auto_withdraw: bool = typer.Option(True), ) -> None: """ Helper script to sell outcome of an existing bet on Omen, usage: diff --git a/scripts/create_market_omen.py b/scripts/create_market_omen.py index f6d42f2f..448a2f53 100644 --- a/scripts/create_market_omen.py +++ b/scripts/create_market_omen.py @@ -29,7 +29,7 @@ def main( fee_perc: float = typer.Option(OMEN_DEFAULT_MARKET_FEE_PERC), language: str = typer.Option("en"), outcomes: list[str] = typer.Option(OMEN_BINARY_MARKET_OUTCOMES), - auto_deposit: bool = typer.Option(False), + auto_deposit: bool = typer.Option(True), ) -> None: """ Helper script to create a market on Omen, usage: diff --git a/scripts/create_market_seer.py b/scripts/create_market_seer.py index ac7da6ee..d8507081 100644 --- a/scripts/create_market_seer.py +++ b/scripts/create_market_seer.py @@ -23,7 +23,7 @@ def main( min_bond_xdai: int = typer.Option(0.01), language: str = typer.Option("en_US"), outcomes: list[str] = typer.Option(OMEN_BINARY_MARKET_OUTCOMES), - auto_deposit: bool = typer.Option(False), + auto_deposit: bool = typer.Option(True), ) -> None: """ Creates a market on Seer. @@ -38,7 +38,7 @@ def main( min_bond_xdai (int, optional): The minimum bond in xDai. Defaults to 0.01 xDai. language (str, optional): The language of the market. Defaults to "en". outcomes (list[str], optional): The outcomes for the market. Defaults to OMEN_BINARY_MARKET_OUTCOMES. - auto_deposit (bool, optional): Whether to automatically deposit funds. Defaults to False. + auto_deposit (bool, optional): Whether to automatically deposit funds. Defaults to True. Returns: None diff --git a/scripts/create_markets_from_sheet_omen.py b/scripts/create_markets_from_sheet_omen.py index 7abeed36..ae0875d1 100644 --- a/scripts/create_markets_from_sheet_omen.py +++ b/scripts/create_markets_from_sheet_omen.py @@ -31,7 +31,7 @@ def main( fee_perc: float = typer.Option(OMEN_DEFAULT_MARKET_FEE_PERC), language: str = typer.Option("en"), outcomes: list[str] = typer.Option(OMEN_BINARY_MARKET_OUTCOMES), - auto_deposit: bool = typer.Option(False), + auto_deposit: bool = typer.Option(True), ) -> None: """ Helper script to create markets on Omen, usage: diff --git a/scripts/sell_all_omen.py b/scripts/sell_all_omen.py index ace7f230..94093100 100644 --- a/scripts/sell_all_omen.py +++ b/scripts/sell_all_omen.py @@ -10,7 +10,7 @@ def main( from_private_key: str, closing_later_than_days: int = 7, safe_address: str | None = None, - auto_withdraw: bool = False, + auto_withdraw: bool = True, ) -> None: """ Helper script to sell all existing outcomes on Omen that would resolve later than in X days.