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

Withdraw to keeping erc20 token on missing places #628

Merged
merged 7 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading