Skip to content

Commit

Permalink
Withdraw to keeping erc20 token on missing places (#628)
Browse files Browse the repository at this point in the history
  • Loading branch information
kongzii authored Feb 27, 2025
1 parent 8ee5948 commit f713bce
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 20 deletions.
15 changes: 15 additions & 0 deletions prediction_market_agent_tooling/markets/omen/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
64 changes: 54 additions & 10 deletions prediction_market_agent_tooling/markets/omen/omen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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:
Expand All @@ -1094,13 +1100,28 @@ 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,
condition_id=market.condition.id,
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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -1224,21 +1256,33 @@ 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,
condition_id=condition_id,
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] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
21 changes: 20 additions & 1 deletion prediction_market_agent_tooling/tools/tokens/auto_withdraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(
Expand All @@ -28,21 +32,36 @@ 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)
and collateral_token_contract.get_asset_token_contract().address
== 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(
Expand Down
2 changes: 2 additions & 0 deletions prediction_market_agent_tooling/tools/tokens/main_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
4 changes: 2 additions & 2 deletions scripts/bet_omen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion scripts/create_market_omen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions scripts/create_market_seer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion scripts/create_markets_from_sheet_omen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion scripts/sell_all_omen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit f713bce

Please sign in to comment.