prediction-market-agent-tooling 0.68.0.dev999__py3-none-any.whl → 0.69.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prediction_market_agent_tooling/chains.py +1 -0
- prediction_market_agent_tooling/config.py +37 -2
- prediction_market_agent_tooling/deploy/agent.py +26 -21
- prediction_market_agent_tooling/deploy/betting_strategy.py +133 -22
- prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +17 -20
- prediction_market_agent_tooling/markets/agent_market.py +27 -9
- prediction_market_agent_tooling/markets/blockchain_utils.py +3 -3
- prediction_market_agent_tooling/markets/markets.py +16 -0
- prediction_market_agent_tooling/markets/omen/data_models.py +3 -18
- prediction_market_agent_tooling/markets/omen/omen.py +26 -11
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +2 -196
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +2 -2
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +13 -11
- prediction_market_agent_tooling/markets/polymarket/api.py +35 -1
- prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
- prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
- prediction_market_agent_tooling/markets/polymarket/data_models.py +33 -5
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +247 -18
- prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
- prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +2 -1
- prediction_market_agent_tooling/markets/seer/data_models.py +41 -6
- prediction_market_agent_tooling/markets/seer/price_manager.py +69 -1
- prediction_market_agent_tooling/markets/seer/seer.py +77 -26
- prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +71 -20
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +67 -0
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +17 -22
- prediction_market_agent_tooling/tools/contract.py +236 -4
- prediction_market_agent_tooling/tools/cow/cow_order.py +13 -8
- prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
- prediction_market_agent_tooling/tools/hexbytes_custom.py +3 -9
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +17 -5
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +2 -2
- prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
- prediction_market_agent_tooling/tools/web3_utils.py +9 -4
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/METADATA +8 -7
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/RECORD +41 -38
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -366
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,6 @@
|
|
1
|
+
from enum import Enum
|
1
2
|
from itertools import chain
|
3
|
+
from typing import Callable
|
2
4
|
|
3
5
|
import numpy as np
|
4
6
|
from scipy.optimize import minimize
|
@@ -9,11 +11,7 @@ from prediction_market_agent_tooling.gtypes import (
|
|
9
11
|
Probability,
|
10
12
|
)
|
11
13
|
from prediction_market_agent_tooling.loggers import logger
|
12
|
-
from prediction_market_agent_tooling.markets.agent_market import AgentMarket
|
13
14
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
14
|
-
from prediction_market_agent_tooling.markets.omen.omen import (
|
15
|
-
calculate_buy_outcome_token,
|
16
|
-
)
|
17
15
|
from prediction_market_agent_tooling.tools.betting_strategies.utils import (
|
18
16
|
BinaryKellyBet,
|
19
17
|
CategoricalKellyBet,
|
@@ -21,6 +19,11 @@ from prediction_market_agent_tooling.tools.betting_strategies.utils import (
|
|
21
19
|
from prediction_market_agent_tooling.tools.utils import check_not_none
|
22
20
|
|
23
21
|
|
22
|
+
class KellyType(str, Enum):
|
23
|
+
SIMPLE = "simple"
|
24
|
+
FULL = "full"
|
25
|
+
|
26
|
+
|
24
27
|
def check_is_valid_probability(probability: float) -> None:
|
25
28
|
if not 0 <= probability <= 1:
|
26
29
|
raise ValueError("Probability must be between 0 and 1")
|
@@ -237,7 +240,7 @@ def get_kelly_bets_categorical_simplified(
|
|
237
240
|
|
238
241
|
|
239
242
|
def get_kelly_bets_categorical_full(
|
240
|
-
|
243
|
+
market_probabilities: list[Probability],
|
241
244
|
estimated_probabilities: list[Probability],
|
242
245
|
confidence: float,
|
243
246
|
max_bet: CollateralToken,
|
@@ -245,6 +248,8 @@ def get_kelly_bets_categorical_full(
|
|
245
248
|
allow_multiple_bets: bool,
|
246
249
|
allow_shorting: bool,
|
247
250
|
multicategorical: bool,
|
251
|
+
# investment amount, outcome index --> received outcome tokens
|
252
|
+
get_buy_token_amount: Callable[[CollateralToken, int], OutcomeToken],
|
248
253
|
bet_precision: int = 6,
|
249
254
|
) -> list[CategoricalKellyBet]:
|
250
255
|
"""
|
@@ -255,18 +260,13 @@ def get_kelly_bets_categorical_full(
|
|
255
260
|
If the agent's probabilities are very close to the market's, returns all-zero bets.
|
256
261
|
multicategorical means that multiple outcomes could be selected as correct ones.
|
257
262
|
"""
|
258
|
-
assert len(
|
263
|
+
assert len(market_probabilities) == len(
|
259
264
|
estimated_probabilities
|
260
265
|
), "Mismatch in number of outcomes"
|
261
|
-
|
262
|
-
market_probabilities = AgentMarket.compute_fpmm_probabilities(
|
263
|
-
[x.as_outcome_wei for x in outcome_pool_sizes]
|
264
|
-
)
|
265
|
-
|
266
266
|
for p in chain(market_probabilities, estimated_probabilities, [confidence]):
|
267
267
|
check_is_valid_probability(p)
|
268
268
|
|
269
|
-
n = len(
|
269
|
+
n = len(market_probabilities)
|
270
270
|
max_bet_value = max_bet.value
|
271
271
|
|
272
272
|
if all(
|
@@ -283,22 +283,17 @@ def get_kelly_bets_categorical_full(
|
|
283
283
|
payout = 0.0
|
284
284
|
if bets[i] >= 0:
|
285
285
|
# If bet on i is positive, we buy outcome i
|
286
|
-
buy_result =
|
287
|
-
|
288
|
-
)
|
289
|
-
payout += buy_result.outcome_tokens_received.value
|
286
|
+
buy_result = get_buy_token_amount(CollateralToken(bets[i]), i)
|
287
|
+
payout += buy_result.value
|
290
288
|
else:
|
291
289
|
# If bet is negative, we "short" outcome i by buying all other outcomes
|
292
290
|
for j in range(n):
|
293
291
|
if j == i:
|
294
292
|
continue
|
295
|
-
buy_result =
|
296
|
-
CollateralToken(abs(bets[i]) / (n - 1)),
|
297
|
-
j,
|
298
|
-
outcome_pool_sizes,
|
299
|
-
fees,
|
293
|
+
buy_result = get_buy_token_amount(
|
294
|
+
CollateralToken(abs(bets[i]) / (n - 1)), j
|
300
295
|
)
|
301
|
-
payout += buy_result.
|
296
|
+
payout += buy_result.value
|
302
297
|
payouts.append(payout)
|
303
298
|
return payouts
|
304
299
|
|
@@ -8,16 +8,21 @@ import eth_abi
|
|
8
8
|
from eth_abi.exceptions import DecodingError
|
9
9
|
from pydantic import BaseModel, field_validator
|
10
10
|
from web3 import Web3
|
11
|
-
from web3.constants import CHECKSUM_ADDRESSS_ZERO
|
11
|
+
from web3.constants import CHECKSUM_ADDRESSS_ZERO, HASH_ZERO
|
12
12
|
from web3.contract.contract import Contract as Web3Contract
|
13
13
|
|
14
|
+
from prediction_market_agent_tooling.chains import POLYGON_CHAIN_ID
|
14
15
|
from prediction_market_agent_tooling.config import APIKeys, RPCConfig
|
15
16
|
from prediction_market_agent_tooling.gtypes import (
|
16
17
|
ABI,
|
17
18
|
ChainID,
|
18
19
|
ChecksumAddress,
|
19
20
|
CollateralToken,
|
21
|
+
HexAddress,
|
22
|
+
HexBytes,
|
23
|
+
HexStr,
|
20
24
|
Nonce,
|
25
|
+
OutcomeWei,
|
21
26
|
TxParams,
|
22
27
|
TxReceipt,
|
23
28
|
Wei,
|
@@ -516,9 +521,19 @@ class ContractOnGnosisChain(ContractBaseClass):
|
|
516
521
|
Contract base class with Gnosis Chain configuration.
|
517
522
|
"""
|
518
523
|
|
524
|
+
# This is defined like so because other chains (like Ethereum) rely on contracts that inherit
|
525
|
+
# from ContractOnGnosisChain. To be re-evaluated on https://github.com/gnosis/prediction-market-agent-tooling/issues/845
|
519
526
|
CHAIN_ID = RPCConfig().chain_id
|
520
527
|
|
521
528
|
|
529
|
+
class ContractOnPolygonChain(ContractBaseClass):
|
530
|
+
"""
|
531
|
+
Contract base class with Gnosis Chain configuration.
|
532
|
+
"""
|
533
|
+
|
534
|
+
CHAIN_ID = POLYGON_CHAIN_ID
|
535
|
+
|
536
|
+
|
522
537
|
class ContractProxyOnGnosisChain(ContractProxyBaseClass, ContractOnGnosisChain):
|
523
538
|
"""
|
524
539
|
Proxy contract base class with Gnosis Chain configuration.
|
@@ -566,6 +581,223 @@ class ContractERC4626OnGnosisChain(
|
|
566
581
|
return to_gnosis_chain_contract(super().get_asset_token_contract(web3=web3))
|
567
582
|
|
568
583
|
|
584
|
+
class PayoutRedemptionEvent(BaseModel):
|
585
|
+
redeemer: HexAddress
|
586
|
+
collateralToken: HexAddress
|
587
|
+
parentCollectionId: HexBytes
|
588
|
+
conditionId: HexBytes
|
589
|
+
indexSets: list[int]
|
590
|
+
payout: Wei
|
591
|
+
|
592
|
+
|
593
|
+
class ConditionPreparationEvent(BaseModel):
|
594
|
+
conditionId: HexBytes
|
595
|
+
oracle: HexAddress
|
596
|
+
questionId: HexBytes
|
597
|
+
outcomeSlotCount: int
|
598
|
+
|
599
|
+
|
600
|
+
class ConditionalTokenContract(ContractBaseClass):
|
601
|
+
# Contract ABI taken from https://gnosisscan.io/address/0xCeAfDD6bc0bEF976fdCd1112955828E00543c0Ce#code.
|
602
|
+
abi: ABI = abi_field_validator(
|
603
|
+
os.path.join(
|
604
|
+
os.path.dirname(os.path.realpath(__file__)),
|
605
|
+
"../abis/omen_fpmm_conditionaltokens.abi.json",
|
606
|
+
)
|
607
|
+
)
|
608
|
+
|
609
|
+
def getConditionId(
|
610
|
+
self,
|
611
|
+
question_id: HexBytes,
|
612
|
+
oracle_address: ChecksumAddress,
|
613
|
+
outcomes_slot_count: int,
|
614
|
+
web3: Web3 | None = None,
|
615
|
+
) -> HexBytes:
|
616
|
+
id_ = HexBytes(
|
617
|
+
self.call(
|
618
|
+
"getConditionId",
|
619
|
+
[oracle_address, question_id, outcomes_slot_count],
|
620
|
+
web3=web3,
|
621
|
+
)
|
622
|
+
)
|
623
|
+
return id_
|
624
|
+
|
625
|
+
def balanceOf(
|
626
|
+
self, from_address: ChecksumAddress, position_id: int, web3: Web3 | None = None
|
627
|
+
) -> OutcomeWei:
|
628
|
+
balance = OutcomeWei(
|
629
|
+
self.call("balanceOf", [from_address, position_id], web3=web3)
|
630
|
+
)
|
631
|
+
return balance
|
632
|
+
|
633
|
+
def isApprovedForAll(
|
634
|
+
self,
|
635
|
+
owner: ChecksumAddress,
|
636
|
+
for_address: ChecksumAddress,
|
637
|
+
web3: Web3 | None = None,
|
638
|
+
) -> bool:
|
639
|
+
is_approved: bool = self.call(
|
640
|
+
"isApprovedForAll", [owner, for_address], web3=web3
|
641
|
+
)
|
642
|
+
return is_approved
|
643
|
+
|
644
|
+
def getCollectionId(
|
645
|
+
self,
|
646
|
+
parent_collection_id: HexStr,
|
647
|
+
condition_id: HexBytes,
|
648
|
+
index_set: int,
|
649
|
+
web3: Web3 | None = None,
|
650
|
+
) -> HexBytes:
|
651
|
+
collection_id = HexBytes(
|
652
|
+
self.call(
|
653
|
+
"getCollectionId",
|
654
|
+
[parent_collection_id, condition_id, index_set],
|
655
|
+
web3=web3,
|
656
|
+
)
|
657
|
+
)
|
658
|
+
return collection_id
|
659
|
+
|
660
|
+
def getPositionId(
|
661
|
+
self,
|
662
|
+
collateral_token_address: ChecksumAddress,
|
663
|
+
collection_id: HexBytes,
|
664
|
+
web3: Web3 | None = None,
|
665
|
+
) -> int:
|
666
|
+
position_id: int = self.call(
|
667
|
+
"getPositionId",
|
668
|
+
[collateral_token_address, collection_id],
|
669
|
+
web3=web3,
|
670
|
+
)
|
671
|
+
return position_id
|
672
|
+
|
673
|
+
def mergePositions(
|
674
|
+
self,
|
675
|
+
api_keys: APIKeys,
|
676
|
+
collateral_token_address: ChecksumAddress,
|
677
|
+
conditionId: HexBytes,
|
678
|
+
index_sets: t.List[int],
|
679
|
+
amount: OutcomeWei,
|
680
|
+
parent_collection_id: HexStr = HASH_ZERO,
|
681
|
+
web3: Web3 | None = None,
|
682
|
+
) -> TxReceipt:
|
683
|
+
return self.send(
|
684
|
+
api_keys=api_keys,
|
685
|
+
function_name="mergePositions",
|
686
|
+
function_params=[
|
687
|
+
collateral_token_address,
|
688
|
+
parent_collection_id,
|
689
|
+
conditionId,
|
690
|
+
index_sets,
|
691
|
+
amount,
|
692
|
+
],
|
693
|
+
web3=web3,
|
694
|
+
)
|
695
|
+
|
696
|
+
def redeemPositions(
|
697
|
+
self,
|
698
|
+
api_keys: APIKeys,
|
699
|
+
collateral_token_address: HexAddress,
|
700
|
+
condition_id: HexBytes,
|
701
|
+
index_sets: t.List[int],
|
702
|
+
parent_collection_id: HexStr = HASH_ZERO,
|
703
|
+
web3: Web3 | None = None,
|
704
|
+
) -> PayoutRedemptionEvent:
|
705
|
+
receipt_tx = self.send(
|
706
|
+
api_keys=api_keys,
|
707
|
+
function_name="redeemPositions",
|
708
|
+
function_params=[
|
709
|
+
collateral_token_address,
|
710
|
+
parent_collection_id,
|
711
|
+
condition_id,
|
712
|
+
index_sets,
|
713
|
+
],
|
714
|
+
web3=web3,
|
715
|
+
)
|
716
|
+
redeem_event_logs = (
|
717
|
+
self.get_web3_contract(web3=web3)
|
718
|
+
.events.PayoutRedemption()
|
719
|
+
.process_receipt(receipt_tx)
|
720
|
+
)
|
721
|
+
redeem_event = PayoutRedemptionEvent(**redeem_event_logs[0]["args"])
|
722
|
+
return redeem_event
|
723
|
+
|
724
|
+
def getOutcomeSlotCount(
|
725
|
+
self, condition_id: HexBytes, web3: Web3 | None = None
|
726
|
+
) -> int:
|
727
|
+
count: int = self.call("getOutcomeSlotCount", [condition_id], web3=web3)
|
728
|
+
return count
|
729
|
+
|
730
|
+
def does_condition_exists(
|
731
|
+
self, condition_id: HexBytes, web3: Web3 | None = None
|
732
|
+
) -> bool:
|
733
|
+
return self.getOutcomeSlotCount(condition_id, web3=web3) > 0
|
734
|
+
|
735
|
+
def is_condition_resolved(
|
736
|
+
self, condition_id: HexBytes, web3: Web3 | None = None
|
737
|
+
) -> bool:
|
738
|
+
# from ConditionalTokens.redeemPositions:
|
739
|
+
# uint den = payoutDenominator[conditionId]; require(den > 0, "result for condition not received yet");
|
740
|
+
payout_for_condition = self.payoutDenominator(condition_id, web3=web3)
|
741
|
+
return payout_for_condition > 0
|
742
|
+
|
743
|
+
def payoutDenominator(
|
744
|
+
self, condition_id: HexBytes, web3: Web3 | None = None
|
745
|
+
) -> int:
|
746
|
+
payoutForCondition: int = self.call(
|
747
|
+
"payoutDenominator", [condition_id], web3=web3
|
748
|
+
)
|
749
|
+
return payoutForCondition
|
750
|
+
|
751
|
+
def setApprovalForAll(
|
752
|
+
self,
|
753
|
+
api_keys: APIKeys,
|
754
|
+
for_address: ChecksumAddress,
|
755
|
+
approve: bool,
|
756
|
+
tx_params: t.Optional[TxParams] = None,
|
757
|
+
web3: Web3 | None = None,
|
758
|
+
) -> TxReceipt:
|
759
|
+
return self.send(
|
760
|
+
api_keys=api_keys,
|
761
|
+
function_name="setApprovalForAll",
|
762
|
+
function_params=[
|
763
|
+
for_address,
|
764
|
+
approve,
|
765
|
+
],
|
766
|
+
tx_params=tx_params,
|
767
|
+
web3=web3,
|
768
|
+
)
|
769
|
+
|
770
|
+
def prepareCondition(
|
771
|
+
self,
|
772
|
+
api_keys: APIKeys,
|
773
|
+
oracle_address: ChecksumAddress,
|
774
|
+
question_id: HexBytes,
|
775
|
+
outcomes_slot_count: int,
|
776
|
+
tx_params: t.Optional[TxParams] = None,
|
777
|
+
web3: Web3 | None = None,
|
778
|
+
) -> ConditionPreparationEvent:
|
779
|
+
receipt_tx = self.send(
|
780
|
+
api_keys=api_keys,
|
781
|
+
function_name="prepareCondition",
|
782
|
+
function_params=[
|
783
|
+
oracle_address,
|
784
|
+
question_id,
|
785
|
+
outcomes_slot_count,
|
786
|
+
],
|
787
|
+
tx_params=tx_params,
|
788
|
+
web3=web3,
|
789
|
+
)
|
790
|
+
|
791
|
+
event_logs = (
|
792
|
+
self.get_web3_contract(web3=web3)
|
793
|
+
.events.ConditionPreparation()
|
794
|
+
.process_receipt(receipt_tx)
|
795
|
+
)
|
796
|
+
cond_event = ConditionPreparationEvent(**event_logs[0]["args"])
|
797
|
+
|
798
|
+
return cond_event
|
799
|
+
|
800
|
+
|
569
801
|
class DebuggingContract(ContractOnGnosisChain):
|
570
802
|
# Contract ABI taken from https://gnosisscan.io/address/0x5Aa82E068aE6a6a1C26c42E5a59520a74Cdb8998#code.
|
571
803
|
abi: ABI = abi_field_validator(
|
@@ -614,12 +846,12 @@ def contract_implements_function(
|
|
614
846
|
look_for_proxy_contract: bool = True,
|
615
847
|
) -> bool:
|
616
848
|
function_signature = f"{function_name}({','.join(function_arg_types or [])})"
|
617
|
-
function_selector = web3.keccak(text=function_signature)[0:4].
|
849
|
+
function_selector = web3.keccak(text=function_signature)[0:4].to_0x_hex()[2:]
|
618
850
|
# 1. Check directly in bytecode
|
619
|
-
bytecode = web3.eth.get_code(contract_address).
|
851
|
+
bytecode = web3.eth.get_code(contract_address).to_0x_hex()
|
620
852
|
if function_selector in bytecode:
|
621
853
|
return True
|
622
|
-
contract_code = web3.eth.get_code(contract_address).
|
854
|
+
contract_code = web3.eth.get_code(contract_address).to_0x_hex()
|
623
855
|
implements = function_selector in contract_code
|
624
856
|
|
625
857
|
# If not found directly and we should check proxies
|
@@ -48,7 +48,7 @@ from prediction_market_agent_tooling.loggers import logger
|
|
48
48
|
from prediction_market_agent_tooling.markets.omen.cow_contracts import (
|
49
49
|
CowGPv2SettlementContract,
|
50
50
|
)
|
51
|
-
from prediction_market_agent_tooling.tools.contract import
|
51
|
+
from prediction_market_agent_tooling.tools.contract import ContractERC20BaseClass
|
52
52
|
from prediction_market_agent_tooling.tools.cow.models import MinimalisticTrade, Order
|
53
53
|
from prediction_market_agent_tooling.tools.cow.semaphore import postgres_rate_limited
|
54
54
|
from prediction_market_agent_tooling.tools.utils import utcnow
|
@@ -166,7 +166,8 @@ def get_buy_token_amount_else_raise(
|
|
166
166
|
def handle_allowance(
|
167
167
|
api_keys: APIKeys,
|
168
168
|
sell_token: ChecksumAddress,
|
169
|
-
|
169
|
+
amount_to_check_wei: Wei,
|
170
|
+
amount_to_set_wei: Wei | None = None,
|
170
171
|
for_address: ChecksumAddress | None = None,
|
171
172
|
web3: Web3 | None = None,
|
172
173
|
) -> None:
|
@@ -174,16 +175,17 @@ def handle_allowance(
|
|
174
175
|
for_address = for_address or Web3.to_checksum_address(
|
175
176
|
CowContractAddress.VAULT_RELAYER.value
|
176
177
|
)
|
177
|
-
current_allowance =
|
178
|
+
current_allowance = ContractERC20BaseClass(address=sell_token).allowance(
|
178
179
|
owner=api_keys.bet_from_address,
|
179
180
|
for_address=for_address,
|
180
181
|
web3=web3,
|
181
182
|
)
|
182
|
-
if current_allowance <
|
183
|
-
|
183
|
+
if current_allowance < amount_to_check_wei:
|
184
|
+
amount_to_set_wei = amount_to_set_wei or amount_to_check_wei
|
185
|
+
ContractERC20BaseClass(address=sell_token).approve(
|
184
186
|
api_keys,
|
185
187
|
for_address=for_address,
|
186
|
-
amount_wei=
|
188
|
+
amount_wei=amount_to_set_wei,
|
187
189
|
web3=web3,
|
188
190
|
)
|
189
191
|
|
@@ -305,7 +307,10 @@ async def swap_tokens_waiting_async(
|
|
305
307
|
wait_order_complete: bool = True,
|
306
308
|
) -> tuple[OrderMetaData | None, CompletedOrder]:
|
307
309
|
handle_allowance(
|
308
|
-
api_keys=api_keys,
|
310
|
+
api_keys=api_keys,
|
311
|
+
sell_token=sell_token,
|
312
|
+
amount_to_check_wei=amount_wei,
|
313
|
+
web3=web3,
|
309
314
|
)
|
310
315
|
valid_to = (utcnow() + timeout).timestamp()
|
311
316
|
order = await place_swap_order(
|
@@ -375,7 +380,7 @@ def get_trades_by_order_uid(
|
|
375
380
|
# Using this until cowpy gets fixed (https://github.com/cowdao-grants/cow-py/issues/35)
|
376
381
|
response = httpx.get(
|
377
382
|
f"https://api.cow.fi/xdai/api/v1/trades",
|
378
|
-
params={"orderUid": order_uid.
|
383
|
+
params={"orderUid": order_uid.to_0x_hex()},
|
379
384
|
)
|
380
385
|
response.raise_for_status()
|
381
386
|
return [MinimalisticTrade.model_validate(i) for i in response.json()]
|
@@ -34,8 +34,20 @@ class DatetimeUTC(datetime):
|
|
34
34
|
def __get_pydantic_core_schema__(
|
35
35
|
cls, source_type: t.Any, handler: GetCoreSchemaHandler
|
36
36
|
) -> CoreSchema:
|
37
|
-
|
38
|
-
return core_schema.
|
37
|
+
# Use union schema to handle int, str, and datetime inputs directly
|
38
|
+
return core_schema.union_schema(
|
39
|
+
[
|
40
|
+
core_schema.no_info_after_validator_function(
|
41
|
+
cls._validate, core_schema.int_schema()
|
42
|
+
),
|
43
|
+
core_schema.no_info_after_validator_function(
|
44
|
+
cls._validate, core_schema.str_schema()
|
45
|
+
),
|
46
|
+
core_schema.no_info_after_validator_function(
|
47
|
+
cls._validate, handler(datetime)
|
48
|
+
),
|
49
|
+
]
|
50
|
+
)
|
39
51
|
|
40
52
|
@staticmethod
|
41
53
|
def from_datetime(dt: datetime) -> "DatetimeUTC":
|
@@ -11,7 +11,7 @@ from pydantic_core.core_schema import (
|
|
11
11
|
with_info_before_validator_function,
|
12
12
|
)
|
13
13
|
|
14
|
-
hex_serializer = plain_serializer_function_ser_schema(function=lambda x: x.
|
14
|
+
hex_serializer = plain_serializer_function_ser_schema(function=lambda x: x.to_0x_hex())
|
15
15
|
|
16
16
|
|
17
17
|
class BaseHex:
|
@@ -60,14 +60,8 @@ class HexBytes(HexBytesBase, BaseHex):
|
|
60
60
|
value = hex_str[2:] if hex_str.startswith("0x") else hex_str
|
61
61
|
return super().fromhex(value)
|
62
62
|
|
63
|
-
def
|
64
|
-
self
|
65
|
-
sep: t.Union[str, bytes] | None = None,
|
66
|
-
bytes_per_sep: t.SupportsIndex = 1,
|
67
|
-
) -> str:
|
68
|
-
"""We enforce a 0x prefix."""
|
69
|
-
x = super().hex(sep, bytes_per_sep) # type: ignore[arg-type]
|
70
|
-
return x if x.startswith("0x") else "0x" + x
|
63
|
+
def __repr__(self) -> str:
|
64
|
+
return f'HexBytes("{self.to_0x_hex()}")'
|
71
65
|
|
72
66
|
@classmethod
|
73
67
|
def __eth_pydantic_validate__(
|
@@ -5,7 +5,9 @@ from langfuse import Langfuse
|
|
5
5
|
from langfuse.client import TraceWithDetails
|
6
6
|
from pydantic import BaseModel
|
7
7
|
|
8
|
+
from prediction_market_agent_tooling.deploy.agent import MarketType
|
8
9
|
from prediction_market_agent_tooling.loggers import logger
|
10
|
+
from prediction_market_agent_tooling.markets.agent_market import AgentMarket
|
9
11
|
from prediction_market_agent_tooling.markets.data_models import (
|
10
12
|
CategoricalProbabilisticAnswer,
|
11
13
|
PlacedTrade,
|
@@ -16,12 +18,13 @@ from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
|
16
18
|
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
17
19
|
WRAPPED_XDAI_CONTRACT_ADDRESS,
|
18
20
|
)
|
21
|
+
from prediction_market_agent_tooling.markets.seer.seer import SeerAgentMarket
|
19
22
|
from prediction_market_agent_tooling.tools.utils import DatetimeUTC
|
20
23
|
|
21
24
|
|
22
25
|
class ProcessMarketTrace(BaseModel):
|
23
26
|
timestamp: int
|
24
|
-
market: OmenAgentMarket
|
27
|
+
market: SeerAgentMarket | OmenAgentMarket
|
25
28
|
answer: CategoricalProbabilisticAnswer
|
26
29
|
trades: list[PlacedTrade]
|
27
30
|
|
@@ -40,13 +43,18 @@ class ProcessMarketTrace(BaseModel):
|
|
40
43
|
def from_langfuse_trace(
|
41
44
|
trace: TraceWithDetails,
|
42
45
|
) -> t.Optional["ProcessMarketTrace"]:
|
43
|
-
market =
|
46
|
+
market = trace_to_agent_market(trace)
|
44
47
|
answer = trace_to_answer(trace)
|
45
48
|
trades = trace_to_trades(trace)
|
46
49
|
|
47
50
|
if not market or not answer or not trades:
|
48
51
|
return None
|
49
52
|
|
53
|
+
if not isinstance(market, (SeerAgentMarket, OmenAgentMarket)):
|
54
|
+
raise ValueError(
|
55
|
+
f"Market type {type(market)} is not supported for ProcessMarketTrace"
|
56
|
+
)
|
57
|
+
|
50
58
|
return ProcessMarketTrace(
|
51
59
|
market=market,
|
52
60
|
answer=answer,
|
@@ -105,17 +113,21 @@ def get_traces_for_agent(
|
|
105
113
|
return all_agent_traces
|
106
114
|
|
107
115
|
|
108
|
-
def
|
116
|
+
def trace_to_agent_market(trace: TraceWithDetails) -> AgentMarket | None:
|
109
117
|
if not trace.input:
|
110
118
|
logger.warning(f"No input in the trace: {trace}")
|
111
119
|
return None
|
112
120
|
if not trace.input["args"]:
|
113
121
|
logger.warning(f"No args in the trace: {trace}")
|
114
122
|
return None
|
115
|
-
assert len(trace.input["args"]) == 2
|
123
|
+
assert len(trace.input["args"]) == 2
|
124
|
+
|
125
|
+
market_type = MarketType(trace.input["args"][0])
|
126
|
+
market_class = market_type.market_class
|
127
|
+
|
116
128
|
try:
|
117
129
|
# If the market model is invalid (e.g. outdated), it will raise an exception
|
118
|
-
market =
|
130
|
+
market = market_class.model_validate(trace.input["args"][1])
|
119
131
|
return market
|
120
132
|
except Exception as e:
|
121
133
|
logger.warning(f"Market not parsed from langfuse because: {e}")
|
@@ -194,7 +194,7 @@ def mint_full_set(
|
|
194
194
|
# of the child market.
|
195
195
|
seer_subgraph_handler = SeerSubgraphHandler()
|
196
196
|
market = seer_subgraph_handler.get_market_by_wrapped_token(
|
197
|
-
|
197
|
+
tokens=[collateral_token_contract.address]
|
198
198
|
)
|
199
199
|
market_collateral_token = Web3.to_checksum_address(market.collateral_token)
|
200
200
|
|
@@ -215,7 +215,7 @@ def mint_full_set(
|
|
215
215
|
handle_allowance(
|
216
216
|
api_keys=api_keys,
|
217
217
|
sell_token=market_collateral_token,
|
218
|
-
|
218
|
+
amount_to_check_wei=collateral_amount_wei,
|
219
219
|
for_address=router.address,
|
220
220
|
web3=web3,
|
221
221
|
)
|
@@ -10,6 +10,9 @@ from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
|
10
10
|
SDAI_CONTRACT_ADDRESS,
|
11
11
|
WRAPPED_XDAI_CONTRACT_ADDRESS,
|
12
12
|
)
|
13
|
+
from prediction_market_agent_tooling.markets.polymarket.polymarket_contracts import (
|
14
|
+
USDCeContract,
|
15
|
+
)
|
13
16
|
from prediction_market_agent_tooling.tools.contract import ContractERC4626OnGnosisChain
|
14
17
|
from prediction_market_agent_tooling.tools.cow.cow_order import (
|
15
18
|
get_buy_token_amount_else_raise,
|
@@ -39,8 +42,8 @@ def get_token_in_usd(amount: CollateralToken, token_address: ChecksumAddress) ->
|
|
39
42
|
# A short cache to not spam CoW and prevent timeouts, but still have relatively fresh data.
|
40
43
|
@cached(TTLCache(maxsize=100, ttl=5 * 60))
|
41
44
|
def get_single_token_to_usd_rate(token_address: ChecksumAddress) -> USD:
|
42
|
-
# (w)xDai
|
43
|
-
if WRAPPED_XDAI_CONTRACT_ADDRESS
|
45
|
+
# (w)xDai and USDC are stablecoins pegged to USD, so use it to estimate USD worth.
|
46
|
+
if token_address in [WRAPPED_XDAI_CONTRACT_ADDRESS, USDCeContract().address]:
|
44
47
|
return USD(1.0)
|
45
48
|
# sDai is ERC4626 with wxDai as asset, we can take the rate directly from there instead of calling CoW.
|
46
49
|
if SDAI_CONTRACT_ADDRESS == token_address:
|
@@ -9,6 +9,7 @@ from eth_typing import URI
|
|
9
9
|
from eth_utils.currency import MAX_WEI, MIN_WEI
|
10
10
|
from pydantic.types import SecretStr
|
11
11
|
from safe_eth.eth import EthereumClient
|
12
|
+
from safe_eth.eth.ethereum_client import TxSpeed
|
12
13
|
from safe_eth.safe.safe import SafeV141
|
13
14
|
from web3 import Web3
|
14
15
|
from web3.constants import HASH_ZERO
|
@@ -104,7 +105,9 @@ def call_function_on_contract(
|
|
104
105
|
function_params: Optional[list[Any] | dict[str, Any]] = None,
|
105
106
|
) -> Any:
|
106
107
|
contract = web3.eth.contract(address=contract_address, abi=contract_abi)
|
107
|
-
output = contract.functions[function_name](
|
108
|
+
output = contract.functions[function_name](
|
109
|
+
*parse_function_params(function_params)
|
110
|
+
).call()
|
108
111
|
return output
|
109
112
|
|
110
113
|
|
@@ -120,9 +123,10 @@ def prepare_tx(
|
|
120
123
|
) -> TxParams:
|
121
124
|
tx_params_new = _prepare_tx_params(web3, from_address, access_list, tx_params)
|
122
125
|
contract = web3.eth.contract(address=contract_address, abi=contract_abi)
|
123
|
-
|
124
126
|
# Build the transaction.
|
125
|
-
function_call = contract.functions[function_name](
|
127
|
+
function_call = contract.functions[function_name](
|
128
|
+
*parse_function_params(function_params)
|
129
|
+
)
|
126
130
|
built_tx_params: TxParams = function_call.build_transaction(tx_params_new)
|
127
131
|
return built_tx_params
|
128
132
|
|
@@ -269,6 +273,7 @@ def send_function_on_contract_tx_using_safe(
|
|
269
273
|
tx_hash, tx = safe_tx.execute(
|
270
274
|
from_private_key.get_secret_value(),
|
271
275
|
tx_nonce=eoa_nonce,
|
276
|
+
eip1559_speed=TxSpeed.FAST,
|
272
277
|
)
|
273
278
|
receipt_tx = web3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout)
|
274
279
|
check_tx_receipt(receipt_tx)
|
@@ -286,7 +291,7 @@ def sign_send_and_get_receipt_tx(
|
|
286
291
|
tx_params_new, private_key=from_private_key.get_secret_value()
|
287
292
|
)
|
288
293
|
# Send the signed transaction.
|
289
|
-
send_tx = web3.eth.send_raw_transaction(signed_tx.
|
294
|
+
send_tx = web3.eth.send_raw_transaction(signed_tx.raw_transaction)
|
290
295
|
# And wait for the receipt.
|
291
296
|
receipt_tx = web3.eth.wait_for_transaction_receipt(send_tx, timeout=timeout)
|
292
297
|
# Verify it didn't fail.
|