-
Notifications
You must be signed in to change notification settings - Fork 9
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
496 seer market creation on pmat #606
Changes from 6 commits
4b548df
5137ab0
e47ad88
e1c2b73
116a8df
513f95e
3fc886d
8dedd33
69e8689
288bc45
dbf833a
6ecbbd2
b8ef5f6
0440b0f
28dcf42
95b5705
8092d5c
9ee97ab
f4e4c78
133132d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
from eth_typing import ChecksumAddress | ||
from web3 import Web3 | ||
|
||
from prediction_market_agent_tooling.config import APIKeys | ||
from prediction_market_agent_tooling.gtypes import xDai | ||
from prediction_market_agent_tooling.markets.seer.seer_contracts import ( | ||
SeerMarketFactory, | ||
) | ||
from prediction_market_agent_tooling.tools.contract import ( | ||
to_gnosis_chain_contract, | ||
init_collateral_token_contract, | ||
auto_deposit_collateral_token, | ||
) | ||
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC | ||
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei | ||
|
||
|
||
def seer_create_market_tx( | ||
api_keys: APIKeys, | ||
initial_funds: xDai, | ||
question: str, | ||
opening_time: DatetimeUTC, | ||
language: str, | ||
outcomes: list[str], | ||
auto_deposit: bool, | ||
category: str, | ||
min_bond_xdai: xDai, | ||
web3: Web3 | None = None, | ||
) -> ChecksumAddress: | ||
web3 = web3 or SeerMarketFactory.get_web3() # Default to Gnosis web3. | ||
initial_funds_wei = xdai_to_wei(initial_funds) | ||
|
||
factory_contract = SeerMarketFactory() | ||
collateral_token_address = factory_contract.collateral_token(web3=web3) | ||
collateral_token_contract = to_gnosis_chain_contract( | ||
init_collateral_token_contract(collateral_token_address, web3) | ||
) | ||
|
||
if auto_deposit: | ||
auto_deposit_collateral_token( | ||
collateral_token_contract=collateral_token_contract, | ||
api_keys=api_keys, | ||
amount_wei=initial_funds_wei, | ||
web3=web3, | ||
) | ||
|
||
# In case of ERC4626, obtained (for example) sDai out of xDai could be lower than the `amount_wei`, so we need to handle it. | ||
initial_funds_in_shares = collateral_token_contract.get_in_shares( | ||
amount=initial_funds_wei, web3=web3 | ||
) | ||
|
||
# Approve the market maker to withdraw our collateral token. | ||
collateral_token_contract.approve( | ||
api_keys=api_keys, | ||
for_address=factory_contract.address, | ||
amount_wei=initial_funds_in_shares, | ||
web3=web3, | ||
) | ||
|
||
# Create the market. | ||
params = factory_contract.build_market_params( | ||
market_question=question, | ||
outcomes=outcomes, | ||
opening_time=opening_time, | ||
language=language, | ||
category=category, | ||
min_bond_xdai=min_bond_xdai, | ||
) | ||
factory_contract.create_categorical_market( | ||
api_keys=api_keys, params=params, web3=web3 | ||
) | ||
# ToDo - Add liquidity to market on Swapr (https://github.com/gnosis/prediction-market-agent-tooling/issues/497) | ||
|
||
# Fetch newly created market | ||
count_markets = factory_contract.market_count(web3=web3) | ||
new_market_address = factory_contract.market_at_index(count_markets - 1, web3=web3) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If two people create a market simultaneously, won't this go out of sync? This function could potentially return another market address. Can't it be obtained from logs or something like in Omen case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good one - implemented following the example. dbf833a#diff-dabd5162b0a22fe87d2b96038a6b2a36ac33875dd92c30b0cac59a80a7405c21R85-R92 |
||
return new_market_address |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,83 @@ | ||||||||||||||||||||||||||||||||||||||||
import os | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
from web3 import Web3 | ||||||||||||||||||||||||||||||||||||||||
from web3.types import TxReceipt | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.config import APIKeys | ||||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.gtypes import ( | ||||||||||||||||||||||||||||||||||||||||
ABI, | ||||||||||||||||||||||||||||||||||||||||
ChecksumAddress, | ||||||||||||||||||||||||||||||||||||||||
xDai, | ||||||||||||||||||||||||||||||||||||||||
xdai_type, | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.markets.seer.data_models import ( | ||||||||||||||||||||||||||||||||||||||||
CreateCategoricalMarketsParams, | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.tools.contract import ( | ||||||||||||||||||||||||||||||||||||||||
abi_field_validator, | ||||||||||||||||||||||||||||||||||||||||
ContractOnGnosisChain, | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC | ||||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
class SeerMarketFactory(ContractOnGnosisChain): | ||||||||||||||||||||||||||||||||||||||||
# https://gnosisscan.io/address/0x83183da839ce8228e31ae41222ead9edbb5cdcf1#code. | ||||||||||||||||||||||||||||||||||||||||
abi: ABI = abi_field_validator( | ||||||||||||||||||||||||||||||||||||||||
os.path.join( | ||||||||||||||||||||||||||||||||||||||||
os.path.dirname(os.path.realpath(__file__)), | ||||||||||||||||||||||||||||||||||||||||
"../../abis/seer_market_factory.abi.json", | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
address: ChecksumAddress = Web3.to_checksum_address( | ||||||||||||||||||||||||||||||||||||||||
"0x83183da839ce8228e31ae41222ead9edbb5cdcf1" | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
@staticmethod | ||||||||||||||||||||||||||||||||||||||||
def build_market_params( | ||||||||||||||||||||||||||||||||||||||||
market_question: str, | ||||||||||||||||||||||||||||||||||||||||
outcomes: list[str], | ||||||||||||||||||||||||||||||||||||||||
opening_time: DatetimeUTC, | ||||||||||||||||||||||||||||||||||||||||
min_bond_xdai: xDai = xdai_type(10), | ||||||||||||||||||||||||||||||||||||||||
gabrielfior marked this conversation as resolved.
Show resolved
Hide resolved
gabrielfior marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||
language: str = "en_US", | ||||||||||||||||||||||||||||||||||||||||
category: str = "misc", | ||||||||||||||||||||||||||||||||||||||||
) -> CreateCategoricalMarketsParams: | ||||||||||||||||||||||||||||||||||||||||
return CreateCategoricalMarketsParams( | ||||||||||||||||||||||||||||||||||||||||
market_name=market_question, | ||||||||||||||||||||||||||||||||||||||||
token_names=[ | ||||||||||||||||||||||||||||||||||||||||
o.upper() for o in outcomes | ||||||||||||||||||||||||||||||||||||||||
], # Following usual token names on Seer (YES,NO). | ||||||||||||||||||||||||||||||||||||||||
kongzii marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||
min_bond=xdai_to_wei(min_bond_xdai), | ||||||||||||||||||||||||||||||||||||||||
opening_time=int(opening_time.timestamp()), | ||||||||||||||||||||||||||||||||||||||||
outcomes=outcomes, | ||||||||||||||||||||||||||||||||||||||||
lang=language, | ||||||||||||||||||||||||||||||||||||||||
category=category, | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def market_count(self, web3: Web3 | None = None) -> int: | ||||||||||||||||||||||||||||||||||||||||
count: int = self.call("marketCount", web3=web3) | ||||||||||||||||||||||||||||||||||||||||
return count | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def market_at_index(self, index: int, web3: Web3 | None = None) -> ChecksumAddress: | ||||||||||||||||||||||||||||||||||||||||
market_address: str = self.call("markets", function_params=[index], web3=web3) | ||||||||||||||||||||||||||||||||||||||||
return Web3.to_checksum_address(market_address) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def collateral_token(self, web3: Web3 | None = None) -> ChecksumAddress: | ||||||||||||||||||||||||||||||||||||||||
collateral_token_address: str = self.call("collateralToken", web3=web3) | ||||||||||||||||||||||||||||||||||||||||
return Web3.to_checksum_address(collateral_token_address) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def create_categorical_market( | ||||||||||||||||||||||||||||||||||||||||
self, | ||||||||||||||||||||||||||||||||||||||||
api_keys: APIKeys, | ||||||||||||||||||||||||||||||||||||||||
params: CreateCategoricalMarketsParams, | ||||||||||||||||||||||||||||||||||||||||
web3: Web3 | None = None, | ||||||||||||||||||||||||||||||||||||||||
) -> TxReceipt: | ||||||||||||||||||||||||||||||||||||||||
receipt_tx = self.send( | ||||||||||||||||||||||||||||||||||||||||
api_keys=api_keys, | ||||||||||||||||||||||||||||||||||||||||
function_name="createCategoricalMarket", | ||||||||||||||||||||||||||||||||||||||||
function_params=[params.model_dump(by_alias=True)], | ||||||||||||||||||||||||||||||||||||||||
web3=web3, | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
return receipt_tx | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
# ToDo - Also return event NewMarket, emitted by this contract | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Implement event handling for NewMarket event. The TODO comment indicates that the Consider updating the def create_categorical_market(
self,
api_keys: APIKeys,
params: CreateCategoricalMarketsParams,
web3: Web3 | None = None,
) -> TxReceipt:
receipt_tx = self.send(
api_keys=api_keys,
function_name="createCategoricalMarket",
function_params=[params.model_dump(by_alias=True)],
web3=web3,
)
+ # Parse NewMarket event from receipt
+ new_market_event = self.get_event_from_receipt(receipt_tx, "NewMarket")
+ if not new_market_event:
+ raise ValueError("NewMarket event not found in transaction receipt")
return receipt_tx 📝 Committable suggestion
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from datetime import datetime | ||
|
||
import typer | ||
from web3 import Web3 | ||
|
||
from prediction_market_agent_tooling.config import APIKeys | ||
from prediction_market_agent_tooling.gtypes import private_key_type, xdai_type, xDai | ||
from prediction_market_agent_tooling.loggers import logger | ||
from prediction_market_agent_tooling.markets.omen.data_models import ( | ||
OMEN_BINARY_MARKET_OUTCOMES, | ||
) | ||
from prediction_market_agent_tooling.markets.seer.seer import seer_create_market_tx | ||
from prediction_market_agent_tooling.tools.utils import DatetimeUTC | ||
|
||
|
||
def main( | ||
question: str = typer.Option(), | ||
opening_time: datetime = typer.Option(), | ||
category: str = typer.Option(), | ||
initial_funds: str = typer.Option(), | ||
from_private_key: str = typer.Option(), | ||
safe_address: str = typer.Option(None), | ||
min_bond_xdai: xDai = typer.Option(xdai_type(10)), | ||
language: str = typer.Option("en"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both accepted - I default to |
||
outcomes: list[str] = typer.Option(OMEN_BINARY_MARKET_OUTCOMES), | ||
auto_deposit: bool = typer.Option(False), | ||
) -> None: | ||
""" | ||
Helper script to create a market on Omen, usage: | ||
|
||
```bash | ||
python scripts/create_market_seer.py \ | ||
--question "Will GNO reach $500 by the end of the 2024?" \ | ||
--opening_time "2024-12-31T23:59:59" \ | ||
--category cryptocurrency \ | ||
--initial-funds 0.01 \ | ||
--from-private-key your-private-key | ||
``` | ||
""" | ||
safe_address_checksum = ( | ||
Web3.to_checksum_address(safe_address) if safe_address else None | ||
) | ||
api_keys = APIKeys( | ||
BET_FROM_PRIVATE_KEY=private_key_type(from_private_key), | ||
SAFE_ADDRESS=safe_address_checksum, | ||
) | ||
market = seer_create_market_tx( | ||
api_keys=api_keys, | ||
initial_funds=xdai_type(initial_funds), | ||
question=question, | ||
opening_time=DatetimeUTC.from_datetime(opening_time), | ||
category=category, | ||
language=language, | ||
outcomes=outcomes, | ||
auto_deposit=auto_deposit, | ||
min_bond_xdai=min_bond_xdai, | ||
) | ||
logger.info(f"Market created: {market}") | ||
|
||
|
||
if __name__ == "__main__": | ||
typer.run(main) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
from web3 import Web3 | ||
|
||
from prediction_market_agent_tooling.config import APIKeys | ||
from prediction_market_agent_tooling.markets.seer.data_models import ( | ||
CreateCategoricalMarketsParams, | ||
) | ||
from prediction_market_agent_tooling.markets.seer.seer_contracts import ( | ||
SeerMarketFactory, | ||
) | ||
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC | ||
|
||
|
||
def build_params() -> CreateCategoricalMarketsParams: | ||
return SeerMarketFactory.build_market_params( | ||
market_question="test test test", | ||
outcomes=["Yes", "No"], | ||
opening_time=DatetimeUTC.now(), | ||
language="en_US", | ||
category="misc", | ||
) | ||
|
||
|
||
def test_create_market(local_web3: Web3, test_keys: APIKeys) -> None: | ||
factory = SeerMarketFactory() | ||
num_initial_markets = factory.market_count(web3=local_web3) | ||
params = build_params() | ||
tx_receipt = factory.create_categorical_market( | ||
api_keys=test_keys, params=params, web3=local_web3 | ||
) | ||
|
||
num_final_markets = factory.market_count(web3=local_web3) | ||
assert num_initial_markets + 1 == num_final_markets |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's optional, then default should be None. Or it's not optional, and the value should be empty string if not present? (first case is cleaner)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed on Slack, closing this since a default value is required to use t.Optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is misunderstanding.
On Slack we discussed doing
question_start: t.Optional[str] = Field(None, alias="questionStart")
, which means that value will default toNone
, if not provided, which is correct behaviour.question_start: t.Optional[str] = Field(alias="questionStart", default="")
is incorrect behaviour, because if value is not provided, it will be""
(which surprising to user, why an empty string?), but it will never beNone
(although it should be, because it's typed as Optional)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree that t.Optional -> and default value "" is not correct.
Note that I tested setting e.g.
question_start = None
and the send tx fails. Hence I will change the type but keep the default value, i.e.None
is not accepted.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 133132d