prediction-market-agent-tooling 0.49.0__py3-none-any.whl → 0.49.2__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/abis/debuggingcontract.abi.json +29 -0
- prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +171 -0
- prediction_market_agent_tooling/benchmark/benchmark.py +0 -93
- prediction_market_agent_tooling/config.py +16 -0
- prediction_market_agent_tooling/deploy/agent.py +62 -7
- prediction_market_agent_tooling/deploy/betting_strategy.py +83 -3
- prediction_market_agent_tooling/markets/agent_market.py +5 -3
- prediction_market_agent_tooling/markets/data_models.py +1 -1
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +1 -1
- prediction_market_agent_tooling/markets/omen/data_models.py +240 -5
- prediction_market_agent_tooling/markets/omen/omen.py +44 -25
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +180 -33
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +1 -1
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +45 -0
- prediction_market_agent_tooling/markets/polymarket/api.py +1 -1
- prediction_market_agent_tooling/monitor/monitor.py +3 -3
- prediction_market_agent_tooling/monitor/monitor_app.py +2 -2
- prediction_market_agent_tooling/tools/contract.py +50 -1
- prediction_market_agent_tooling/tools/httpx_cached_client.py +11 -0
- prediction_market_agent_tooling/tools/ipfs/ipfs_handler.py +33 -0
- prediction_market_agent_tooling/tools/is_predictable.py +1 -1
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +23 -8
- prediction_market_agent_tooling/tools/tavily_storage/tavily_models.py +1 -1
- prediction_market_agent_tooling/tools/utils.py +28 -4
- prediction_market_agent_tooling/tools/web3_utils.py +7 -0
- {prediction_market_agent_tooling-0.49.0.dist-info → prediction_market_agent_tooling-0.49.2.dist-info}/METADATA +3 -1
- {prediction_market_agent_tooling-0.49.0.dist-info → prediction_market_agent_tooling-0.49.2.dist-info}/RECORD +30 -26
- {prediction_market_agent_tooling-0.49.0.dist-info → prediction_market_agent_tooling-0.49.2.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.49.0.dist-info → prediction_market_agent_tooling-0.49.2.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.49.0.dist-info → prediction_market_agent_tooling-0.49.2.dist-info}/entry_points.txt +0 -0
@@ -5,6 +5,7 @@ from datetime import datetime, timedelta
|
|
5
5
|
from enum import Enum
|
6
6
|
|
7
7
|
from web3 import Web3
|
8
|
+
from web3.constants import HASH_ZERO
|
8
9
|
|
9
10
|
from prediction_market_agent_tooling.config import APIKeys
|
10
11
|
from prediction_market_agent_tooling.gtypes import (
|
@@ -20,10 +21,15 @@ from prediction_market_agent_tooling.gtypes import (
|
|
20
21
|
Wei,
|
21
22
|
int_to_hexbytes,
|
22
23
|
wei_type,
|
23
|
-
xdai_type,
|
24
24
|
)
|
25
25
|
from prediction_market_agent_tooling.markets.omen.data_models import (
|
26
26
|
INVALID_ANSWER_HEX_BYTES,
|
27
|
+
ConditionPreparationEvent,
|
28
|
+
ContractPrediction,
|
29
|
+
FPMMFundingAddedEvent,
|
30
|
+
OmenFixedProductMarketMakerCreationEvent,
|
31
|
+
RealitioLogNewQuestionEvent,
|
32
|
+
format_realitio_question,
|
27
33
|
)
|
28
34
|
from prediction_market_agent_tooling.tools.contract import (
|
29
35
|
ContractDepositableWrapperERC20OnGnosisChain,
|
@@ -38,7 +44,6 @@ from prediction_market_agent_tooling.tools.web3_utils import (
|
|
38
44
|
ZERO_BYTES,
|
39
45
|
byte32_to_ipfscidv0,
|
40
46
|
ipfscidv0_to_byte32,
|
41
|
-
xdai_to_wei,
|
42
47
|
)
|
43
48
|
|
44
49
|
|
@@ -84,6 +89,10 @@ class OmenOracleContract(ContractOnGnosisChain):
|
|
84
89
|
)
|
85
90
|
|
86
91
|
|
92
|
+
def build_parent_collection_id() -> HexStr:
|
93
|
+
return HASH_ZERO # Taken from Olas
|
94
|
+
|
95
|
+
|
87
96
|
class OmenConditionalTokenContract(ContractOnGnosisChain):
|
88
97
|
# Contract ABI taken from https://gnosisscan.io/address/0xCeAfDD6bc0bEF976fdCd1112955828E00543c0Ce#code.
|
89
98
|
abi: ABI = abi_field_validator(
|
@@ -177,8 +186,8 @@ class OmenConditionalTokenContract(ContractOnGnosisChain):
|
|
177
186
|
api_keys: APIKeys,
|
178
187
|
collateral_token_address: HexAddress,
|
179
188
|
condition_id: HexBytes,
|
180
|
-
parent_collection_id: HexStr,
|
181
189
|
index_sets: t.List[int],
|
190
|
+
parent_collection_id: HexStr = build_parent_collection_id(),
|
182
191
|
web3: Web3 | None = None,
|
183
192
|
) -> TxReceipt:
|
184
193
|
return self.send(
|
@@ -247,8 +256,8 @@ class OmenConditionalTokenContract(ContractOnGnosisChain):
|
|
247
256
|
outcomes_slot_count: int,
|
248
257
|
tx_params: t.Optional[TxParams] = None,
|
249
258
|
web3: Web3 | None = None,
|
250
|
-
) ->
|
251
|
-
|
259
|
+
) -> ConditionPreparationEvent:
|
260
|
+
receipt_tx = self.send(
|
252
261
|
api_keys=api_keys,
|
253
262
|
function_name="prepareCondition",
|
254
263
|
function_params=[
|
@@ -260,6 +269,15 @@ class OmenConditionalTokenContract(ContractOnGnosisChain):
|
|
260
269
|
web3=web3,
|
261
270
|
)
|
262
271
|
|
272
|
+
event_logs = (
|
273
|
+
self.get_web3_contract(web3=web3)
|
274
|
+
.events.ConditionPreparation()
|
275
|
+
.process_receipt(receipt_tx)
|
276
|
+
)
|
277
|
+
cond_event = ConditionPreparationEvent(**event_logs[0]["args"])
|
278
|
+
|
279
|
+
return cond_event
|
280
|
+
|
263
281
|
|
264
282
|
class OmenFixedProductMarketMakerContract(ContractOnGnosisChain):
|
265
283
|
# File content taken from https://github.com/protofire/omen-exchange/blob/master/app/src/abi/marketMaker.json.
|
@@ -412,7 +430,8 @@ class sDaiContract(ContractERC4626OnGnosisChain):
|
|
412
430
|
)
|
413
431
|
|
414
432
|
|
415
|
-
|
433
|
+
OMEN_DEFAULT_MARKET_FEE_PERC = 0.02 # 2% fee from the buying shares amount.
|
434
|
+
REALITY_DEFAULT_FINALIZATION_TIMEOUT = timedelta(days=3)
|
416
435
|
|
417
436
|
|
418
437
|
class OmenFixedProductMarketMakerFactoryContract(ContractOnGnosisChain):
|
@@ -433,14 +452,15 @@ class OmenFixedProductMarketMakerFactoryContract(ContractOnGnosisChain):
|
|
433
452
|
condition_id: HexBytes,
|
434
453
|
initial_funds_wei: Wei,
|
435
454
|
collateral_token_address: ChecksumAddress,
|
436
|
-
fee:
|
455
|
+
fee: Wei, # This is actually fee in %, 'where 100% == 1 xDai'.
|
456
|
+
distribution_hint: list[OmenOutcomeToken] | None = None,
|
437
457
|
tx_params: t.Optional[TxParams] = None,
|
438
458
|
web3: Web3 | None = None,
|
439
|
-
) ->
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
459
|
+
) -> tuple[
|
460
|
+
OmenFixedProductMarketMakerCreationEvent, FPMMFundingAddedEvent, TxReceipt
|
461
|
+
]:
|
462
|
+
web3 = web3 or self.get_web3()
|
463
|
+
receipt_tx = self.send(
|
444
464
|
api_keys=api_keys,
|
445
465
|
function_name="create2FixedProductMarketMaker",
|
446
466
|
function_params=dict(
|
@@ -450,19 +470,41 @@ class OmenFixedProductMarketMakerFactoryContract(ContractOnGnosisChain):
|
|
450
470
|
conditionalTokens=OmenConditionalTokenContract().address,
|
451
471
|
collateralToken=collateral_token_address,
|
452
472
|
conditionIds=[condition_id],
|
453
|
-
fee=
|
473
|
+
fee=fee,
|
454
474
|
initialFunds=initial_funds_wei,
|
455
|
-
distributionHint=[],
|
475
|
+
distributionHint=distribution_hint or [],
|
456
476
|
),
|
457
477
|
tx_params=tx_params,
|
458
478
|
web3=web3,
|
459
479
|
)
|
460
480
|
|
481
|
+
market_event_logs = (
|
482
|
+
self.get_web3_contract(web3=web3)
|
483
|
+
.events.FixedProductMarketMakerCreation()
|
484
|
+
.process_receipt(receipt_tx)
|
485
|
+
)
|
486
|
+
market_event = OmenFixedProductMarketMakerCreationEvent(
|
487
|
+
**market_event_logs[0]["args"]
|
488
|
+
)
|
489
|
+
funding_event_logs = (
|
490
|
+
self.get_web3_contract(web3=web3)
|
491
|
+
.events.FPMMFundingAdded()
|
492
|
+
.process_receipt(receipt_tx)
|
493
|
+
)
|
494
|
+
funding_event = FPMMFundingAddedEvent(**funding_event_logs[0]["args"])
|
495
|
+
|
496
|
+
return market_event, funding_event, receipt_tx
|
497
|
+
|
461
498
|
|
462
499
|
class Arbitrator(str, Enum):
|
463
|
-
|
500
|
+
KLEROS_511_JURORS_WITHOUT_APPEAL = "kleros_511_jurors_without_appeal"
|
501
|
+
KLEROS_31_JURORS_WITH_APPEAL = "kleros_31_jurors_with_appeal"
|
464
502
|
DXDAO = "dxdao"
|
465
503
|
|
504
|
+
@property
|
505
|
+
def is_kleros(self) -> bool:
|
506
|
+
return self.value.startswith("kleros")
|
507
|
+
|
466
508
|
|
467
509
|
class OmenDxDaoContract(ContractOnGnosisChain):
|
468
510
|
# Contract ABI taken from https://gnosisscan.io/address/0xFe14059344b74043Af518d12931600C0f52dF7c5#code.
|
@@ -485,9 +527,22 @@ class OmenKlerosContract(ContractOnGnosisChain):
|
|
485
527
|
"../../abis/omen_kleros.abi.json",
|
486
528
|
)
|
487
529
|
)
|
488
|
-
|
489
|
-
|
490
|
-
)
|
530
|
+
|
531
|
+
@staticmethod
|
532
|
+
def from_arbitrator(arbitrator: "Arbitrator") -> "OmenKlerosContract":
|
533
|
+
"""
|
534
|
+
See https://docs.kleros.io/developer/deployment-addresses for all available addresses.
|
535
|
+
"""
|
536
|
+
if arbitrator == Arbitrator.KLEROS_511_JURORS_WITHOUT_APPEAL:
|
537
|
+
address = "0xe40DD83a262da3f56976038F1554Fe541Fa75ecd"
|
538
|
+
|
539
|
+
elif arbitrator == Arbitrator.KLEROS_31_JURORS_WITH_APPEAL:
|
540
|
+
address = "0x29f39de98d750eb77b5fafb31b2837f079fce222"
|
541
|
+
|
542
|
+
else:
|
543
|
+
raise ValueError(f"Unsupported arbitrator: {arbitrator=}")
|
544
|
+
|
545
|
+
return OmenKlerosContract(address=Web3.to_checksum_address(address))
|
491
546
|
|
492
547
|
|
493
548
|
class OmenRealitioContract(ContractOnGnosisChain):
|
@@ -506,8 +561,8 @@ class OmenRealitioContract(ContractOnGnosisChain):
|
|
506
561
|
def get_arbitrator_contract(
|
507
562
|
arbitrator: Arbitrator,
|
508
563
|
) -> ContractOnGnosisChain:
|
509
|
-
if arbitrator
|
510
|
-
return OmenKlerosContract()
|
564
|
+
if arbitrator.is_kleros:
|
565
|
+
return OmenKlerosContract.from_arbitrator(arbitrator)
|
511
566
|
if arbitrator == Arbitrator.DXDAO:
|
512
567
|
return OmenDxDaoContract()
|
513
568
|
raise ValueError(f"Unknown arbitrator: {arbitrator}")
|
@@ -521,25 +576,25 @@ class OmenRealitioContract(ContractOnGnosisChain):
|
|
521
576
|
language: str,
|
522
577
|
arbitrator: Arbitrator,
|
523
578
|
opening: datetime,
|
524
|
-
timeout: timedelta
|
579
|
+
timeout: timedelta,
|
525
580
|
nonce: int | None = None,
|
526
581
|
tx_params: t.Optional[TxParams] = None,
|
527
582
|
web3: Web3 | None = None,
|
528
|
-
) ->
|
583
|
+
) -> RealitioLogNewQuestionEvent:
|
529
584
|
"""
|
530
585
|
After the question is created, you can find it at https://reality.eth.link/app/#!/creator/{from_address}.
|
531
586
|
"""
|
587
|
+
web3 = web3 or self.get_web3()
|
532
588
|
arbitrator_contract_address = self.get_arbitrator_contract(arbitrator).address
|
533
589
|
# See https://realitio.github.io/docs/html/contracts.html#templates
|
534
590
|
# for possible template ids and how to format the question.
|
535
591
|
template_id = 2
|
536
|
-
realitio_question =
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
]
|
592
|
+
realitio_question = format_realitio_question(
|
593
|
+
question=question,
|
594
|
+
outcomes=outcomes,
|
595
|
+
category=category,
|
596
|
+
language=language,
|
597
|
+
template_id=template_id,
|
543
598
|
)
|
544
599
|
receipt_tx = self.send(
|
545
600
|
api_keys=api_keys,
|
@@ -557,10 +612,15 @@ class OmenRealitioContract(ContractOnGnosisChain):
|
|
557
612
|
tx_params=tx_params,
|
558
613
|
web3=web3,
|
559
614
|
)
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
615
|
+
|
616
|
+
event_logs = (
|
617
|
+
self.get_web3_contract(web3=web3)
|
618
|
+
.events.LogNewQuestion()
|
619
|
+
.process_receipt(receipt_tx)
|
620
|
+
)
|
621
|
+
question_event = RealitioLogNewQuestionEvent(**event_logs[0]["args"])
|
622
|
+
|
623
|
+
return question_event
|
564
624
|
|
565
625
|
def submitAnswer(
|
566
626
|
self,
|
@@ -670,6 +730,93 @@ class OmenRealitioContract(ContractOnGnosisChain):
|
|
670
730
|
) -> TxReceipt:
|
671
731
|
return self.send(api_keys=api_keys, function_name="withdraw", web3=web3)
|
672
732
|
|
733
|
+
def getOpeningTS(
|
734
|
+
self,
|
735
|
+
question_id: HexBytes,
|
736
|
+
web3: Web3 | None = None,
|
737
|
+
) -> int:
|
738
|
+
ts: int = self.call(
|
739
|
+
function_name="getOpeningTS",
|
740
|
+
function_params=[question_id],
|
741
|
+
web3=web3,
|
742
|
+
)
|
743
|
+
return ts
|
744
|
+
|
745
|
+
def getFinalizeTS(
|
746
|
+
self,
|
747
|
+
question_id: HexBytes,
|
748
|
+
web3: Web3 | None = None,
|
749
|
+
) -> int:
|
750
|
+
ts: int = self.call(
|
751
|
+
function_name="getFinalizeTS",
|
752
|
+
function_params=[question_id],
|
753
|
+
web3=web3,
|
754
|
+
)
|
755
|
+
return ts
|
756
|
+
|
757
|
+
def isFinalized(
|
758
|
+
self,
|
759
|
+
question_id: HexBytes,
|
760
|
+
web3: Web3 | None = None,
|
761
|
+
) -> bool:
|
762
|
+
is_finalized: bool = self.call(
|
763
|
+
function_name="isFinalized",
|
764
|
+
function_params=[question_id],
|
765
|
+
web3=web3,
|
766
|
+
)
|
767
|
+
return is_finalized
|
768
|
+
|
769
|
+
def isPendingArbitration(
|
770
|
+
self,
|
771
|
+
question_id: HexBytes,
|
772
|
+
web3: Web3 | None = None,
|
773
|
+
) -> bool:
|
774
|
+
is_pending_arbitration: bool = self.call(
|
775
|
+
function_name="isPendingArbitration",
|
776
|
+
function_params=[question_id],
|
777
|
+
web3=web3,
|
778
|
+
)
|
779
|
+
return is_pending_arbitration
|
780
|
+
|
781
|
+
|
782
|
+
class OmenAgentResultMappingContract(ContractOnGnosisChain):
|
783
|
+
# Contract ABI taken from built https://github.com/gnosis/labs-contracts.
|
784
|
+
|
785
|
+
abi: ABI = abi_field_validator(
|
786
|
+
os.path.join(
|
787
|
+
os.path.dirname(os.path.realpath(__file__)),
|
788
|
+
"../../abis/omen_agentresultmapping.abi.json",
|
789
|
+
)
|
790
|
+
)
|
791
|
+
|
792
|
+
address: ChecksumAddress = Web3.to_checksum_address(
|
793
|
+
"0x260E1077dEA98e738324A6cEfB0EE9A272eD471a"
|
794
|
+
)
|
795
|
+
|
796
|
+
def get_predictions(
|
797
|
+
self,
|
798
|
+
market_address: ChecksumAddress,
|
799
|
+
web3: Web3 | None = None,
|
800
|
+
) -> list[ContractPrediction]:
|
801
|
+
prediction_tuples = self.call(
|
802
|
+
"getPredictions", function_params=[market_address], web3=web3
|
803
|
+
)
|
804
|
+
return [ContractPrediction.from_tuple(p) for p in prediction_tuples]
|
805
|
+
|
806
|
+
def add_prediction(
|
807
|
+
self,
|
808
|
+
api_keys: APIKeys,
|
809
|
+
market_address: ChecksumAddress,
|
810
|
+
prediction: ContractPrediction,
|
811
|
+
web3: Web3 | None = None,
|
812
|
+
) -> TxReceipt:
|
813
|
+
return self.send(
|
814
|
+
api_keys=api_keys,
|
815
|
+
function_name="addPrediction",
|
816
|
+
function_params=[market_address, prediction.model_dump(by_alias=True)],
|
817
|
+
web3=web3,
|
818
|
+
)
|
819
|
+
|
673
820
|
|
674
821
|
class OmenThumbnailMapping(ContractOnGnosisChain):
|
675
822
|
# Contract ABI taken from built https://github.com/gnosis/labs-contracts.
|
@@ -240,7 +240,7 @@ def omen_resolve_market_tx(
|
|
240
240
|
web3: Web3 | None = None,
|
241
241
|
) -> None:
|
242
242
|
"""
|
243
|
-
Market can be resolved
|
243
|
+
Market can be resolved after the answer if finalized on Reality.
|
244
244
|
"""
|
245
245
|
oracle_contract = OmenOracleContract()
|
246
246
|
oracle_contract.resolve(
|
@@ -20,6 +20,7 @@ from prediction_market_agent_tooling.loggers import logger
|
|
20
20
|
from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
|
21
21
|
from prediction_market_agent_tooling.markets.omen.data_models import (
|
22
22
|
OMEN_BINARY_MARKET_OUTCOMES,
|
23
|
+
ContractPrediction,
|
23
24
|
OmenBet,
|
24
25
|
OmenMarket,
|
25
26
|
OmenPosition,
|
@@ -60,6 +61,8 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
60
61
|
|
61
62
|
OMEN_IMAGE_MAPPING_GRAPH_URL = "https://gateway-arbitrum.network.thegraph.com/api/{graph_api_key}/subgraphs/id/EWN14ciGK53PpUiKSm7kMWQ6G4iz3tDrRLyZ1iXMQEdu"
|
62
63
|
|
64
|
+
OMEN_AGENT_RESULT_MAPPING_GRAPH_URL = "https://gateway-arbitrum.network.thegraph.com/api/{graph_api_key}/subgraphs/id/GoE3UFyc8Gg9xzv92oinonyhRCphpGu62qB2Eh2XvJ8F"
|
65
|
+
|
63
66
|
INVALID_ANSWER = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
64
67
|
|
65
68
|
def __init__(self) -> None:
|
@@ -96,6 +99,12 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
96
99
|
)
|
97
100
|
)
|
98
101
|
|
102
|
+
self.omen_agent_result_mapping_subgraph = self.sg.load_subgraph(
|
103
|
+
self.OMEN_AGENT_RESULT_MAPPING_GRAPH_URL.format(
|
104
|
+
graph_api_key=keys.graph_api_key.get_secret_value()
|
105
|
+
)
|
106
|
+
)
|
107
|
+
|
99
108
|
def _get_fields_for_bets(self, bets_field: FieldPath) -> list[FieldPath]:
|
100
109
|
markets = bets_field.fpmm
|
101
110
|
fields_for_markets = self._get_fields_for_markets(markets)
|
@@ -310,6 +319,16 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
310
319
|
sort_by_field = (
|
311
320
|
self.trades_subgraph.FixedProductMarketMaker.openingTimestamp
|
312
321
|
)
|
322
|
+
case SortBy.HIGHEST_LIQUIDITY:
|
323
|
+
sort_direction = "desc"
|
324
|
+
sort_by_field = (
|
325
|
+
self.trades_subgraph.FixedProductMarketMaker.liquidityMeasure
|
326
|
+
)
|
327
|
+
case SortBy.LOWEST_LIQUIDITY:
|
328
|
+
sort_direction = "asc"
|
329
|
+
sort_by_field = (
|
330
|
+
self.trades_subgraph.FixedProductMarketMaker.liquidityMeasure
|
331
|
+
)
|
313
332
|
case SortBy.NONE:
|
314
333
|
sort_direction = None
|
315
334
|
sort_by_field = None
|
@@ -797,3 +816,29 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
797
816
|
if image_url is not None
|
798
817
|
else None
|
799
818
|
)
|
819
|
+
|
820
|
+
def get_agent_results_for_market(
|
821
|
+
self, market_id: HexAddress | None = None
|
822
|
+
) -> list[ContractPrediction]:
|
823
|
+
where_stms = {}
|
824
|
+
if market_id:
|
825
|
+
where_stms["marketAddress"] = market_id.lower()
|
826
|
+
|
827
|
+
prediction_added = (
|
828
|
+
self.omen_agent_result_mapping_subgraph.Query.predictionAddeds(
|
829
|
+
where=where_stms,
|
830
|
+
orderBy="blockNumber",
|
831
|
+
orderDirection="asc",
|
832
|
+
)
|
833
|
+
)
|
834
|
+
fields = [
|
835
|
+
prediction_added.publisherAddress,
|
836
|
+
prediction_added.ipfsHash,
|
837
|
+
prediction_added.txHashes,
|
838
|
+
prediction_added.estimatedProbabilityBps,
|
839
|
+
]
|
840
|
+
result = self.sg.query_json(fields)
|
841
|
+
items = self._parse_items_from_json(result)
|
842
|
+
if not items:
|
843
|
+
return []
|
844
|
+
return [ContractPrediction.model_validate(i) for i in items]
|
@@ -2,8 +2,8 @@ import typing as t
|
|
2
2
|
|
3
3
|
import requests
|
4
4
|
import tenacity
|
5
|
-
from loguru import logger
|
6
5
|
|
6
|
+
from prediction_market_agent_tooling.loggers import logger
|
7
7
|
from prediction_market_agent_tooling.markets.polymarket.data_models import (
|
8
8
|
POLYMARKET_FALSE_OUTCOME,
|
9
9
|
POLYMARKET_TRUE_OUTCOME,
|
@@ -27,8 +27,8 @@ from prediction_market_agent_tooling.markets.data_models import Resolution, Reso
|
|
27
27
|
from prediction_market_agent_tooling.tools.parallelism import par_map
|
28
28
|
from prediction_market_agent_tooling.tools.utils import (
|
29
29
|
DatetimeWithTimezone,
|
30
|
-
add_utc_timezone_validator,
|
31
30
|
check_not_none,
|
31
|
+
convert_to_utc_datetime,
|
32
32
|
should_not_happen,
|
33
33
|
)
|
34
34
|
|
@@ -49,10 +49,10 @@ class DeployedAgent(BaseModel):
|
|
49
49
|
raw_env_vars: dict[str, str] | None = None
|
50
50
|
|
51
51
|
_add_timezone_validator_start_time = field_validator("start_time")(
|
52
|
-
|
52
|
+
convert_to_utc_datetime
|
53
53
|
)
|
54
54
|
_add_timezone_validator_end_time = field_validator("end_time")(
|
55
|
-
|
55
|
+
convert_to_utc_datetime
|
56
56
|
)
|
57
57
|
|
58
58
|
def model_dump_prefixed(self) -> dict[str, t.Any]:
|
@@ -28,8 +28,8 @@ from prediction_market_agent_tooling.monitor.monitor import (
|
|
28
28
|
from prediction_market_agent_tooling.monitor.monitor_settings import MonitorSettings
|
29
29
|
from prediction_market_agent_tooling.tools.utils import (
|
30
30
|
DatetimeWithTimezone,
|
31
|
-
add_utc_timezone_validator,
|
32
31
|
check_not_none,
|
32
|
+
convert_to_utc_datetime,
|
33
33
|
utcnow,
|
34
34
|
)
|
35
35
|
|
@@ -104,7 +104,7 @@ def monitor_app(
|
|
104
104
|
st.selectbox(label="Market type", options=enabled_market_types, index=0)
|
105
105
|
)
|
106
106
|
start_time: DatetimeWithTimezone | None = (
|
107
|
-
|
107
|
+
convert_to_utc_datetime(
|
108
108
|
datetime.combine(
|
109
109
|
t.cast(
|
110
110
|
# This will be always a date for us, so casting.
|
@@ -6,6 +6,7 @@ from contextlib import contextmanager
|
|
6
6
|
|
7
7
|
from pydantic import BaseModel, field_validator
|
8
8
|
from web3 import Web3
|
9
|
+
from web3.contract.contract import Contract as Web3Contract
|
9
10
|
|
10
11
|
from prediction_market_agent_tooling.config import APIKeys
|
11
12
|
from prediction_market_agent_tooling.gtypes import (
|
@@ -21,7 +22,11 @@ from prediction_market_agent_tooling.tools.gnosis_rpc import (
|
|
21
22
|
GNOSIS_NETWORK_ID,
|
22
23
|
GNOSIS_RPC_URL,
|
23
24
|
)
|
24
|
-
from prediction_market_agent_tooling.tools.utils import
|
25
|
+
from prediction_market_agent_tooling.tools.utils import (
|
26
|
+
DatetimeWithTimezone,
|
27
|
+
should_not_happen,
|
28
|
+
utc_timestamp_to_utc_datetime,
|
29
|
+
)
|
25
30
|
from prediction_market_agent_tooling.tools.web3_utils import (
|
26
31
|
call_function_on_contract,
|
27
32
|
send_function_on_contract_tx,
|
@@ -73,6 +78,10 @@ class ContractBaseClass(BaseModel):
|
|
73
78
|
{}
|
74
79
|
) # Can be used to hold values that aren't going to change after getting them for the first time, as for example `symbol` of an ERC-20 token.
|
75
80
|
|
81
|
+
def get_web3_contract(self, web3: Web3 | None = None) -> Web3Contract:
|
82
|
+
web3 = web3 or self.get_web3()
|
83
|
+
return web3.eth.contract(address=self.address, abi=self.abi)
|
84
|
+
|
76
85
|
def call(
|
77
86
|
self,
|
78
87
|
function_name: str,
|
@@ -423,6 +432,46 @@ class ContractERC4626OnGnosisChain(
|
|
423
432
|
return to_gnosis_chain_contract(super().get_asset_token_contract(web3=web3))
|
424
433
|
|
425
434
|
|
435
|
+
class DebuggingContract(ContractOnGnosisChain):
|
436
|
+
# Contract ABI taken from https://gnosisscan.io/address/0x5Aa82E068aE6a6a1C26c42E5a59520a74Cdb8998#code.
|
437
|
+
abi: ABI = abi_field_validator(
|
438
|
+
os.path.join(
|
439
|
+
os.path.dirname(os.path.realpath(__file__)),
|
440
|
+
"../abis/debuggingcontract.abi.json",
|
441
|
+
)
|
442
|
+
)
|
443
|
+
address: ChecksumAddress = Web3.to_checksum_address(
|
444
|
+
"0x5Aa82E068aE6a6a1C26c42E5a59520a74Cdb8998"
|
445
|
+
)
|
446
|
+
|
447
|
+
def getNow(
|
448
|
+
self,
|
449
|
+
web3: Web3 | None = None,
|
450
|
+
) -> int:
|
451
|
+
now: int = self.call(
|
452
|
+
function_name="getNow",
|
453
|
+
web3=web3,
|
454
|
+
)
|
455
|
+
return now
|
456
|
+
|
457
|
+
def get_now(
|
458
|
+
self,
|
459
|
+
web3: Web3 | None = None,
|
460
|
+
) -> DatetimeWithTimezone:
|
461
|
+
return utc_timestamp_to_utc_datetime(self.getNow(web3))
|
462
|
+
|
463
|
+
def inc(
|
464
|
+
self,
|
465
|
+
api_keys: APIKeys,
|
466
|
+
web3: Web3 | None = None,
|
467
|
+
) -> TxReceipt:
|
468
|
+
return self.send(
|
469
|
+
api_keys=api_keys,
|
470
|
+
function_name="inc",
|
471
|
+
web3=web3,
|
472
|
+
)
|
473
|
+
|
474
|
+
|
426
475
|
def contract_implements_function(
|
427
476
|
contract_address: ChecksumAddress,
|
428
477
|
function_name: str,
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import hishel
|
2
|
+
|
3
|
+
|
4
|
+
class HttpxCachedClient:
|
5
|
+
def __init__(self) -> None:
|
6
|
+
storage = hishel.FileStorage(ttl=3600, check_ttl_every=600)
|
7
|
+
controller = hishel.Controller(force_cache=True)
|
8
|
+
self.client = hishel.CacheClient(storage=storage, controller=controller)
|
9
|
+
|
10
|
+
def get_client(self) -> hishel.CacheClient:
|
11
|
+
return self.client
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import json
|
2
|
+
import tempfile
|
3
|
+
|
4
|
+
from pinatapy import PinataPy
|
5
|
+
|
6
|
+
from prediction_market_agent_tooling.config import APIKeys
|
7
|
+
from prediction_market_agent_tooling.gtypes import IPFSCIDVersion0
|
8
|
+
from prediction_market_agent_tooling.markets.omen.data_models import IPFSAgentResult
|
9
|
+
|
10
|
+
|
11
|
+
class IPFSHandler:
|
12
|
+
def __init__(self, api_keys: APIKeys):
|
13
|
+
self.pinata = PinataPy(
|
14
|
+
api_keys.pinata_api_key.get_secret_value(),
|
15
|
+
api_keys.pinata_api_secret.get_secret_value(),
|
16
|
+
)
|
17
|
+
|
18
|
+
def upload_file(self, file_path: str) -> IPFSCIDVersion0:
|
19
|
+
return IPFSCIDVersion0(
|
20
|
+
self.pinata.pin_file_to_ipfs(file_path, save_absolute_paths=False)[
|
21
|
+
"IpfsHash"
|
22
|
+
]
|
23
|
+
)
|
24
|
+
|
25
|
+
def store_agent_result(self, agent_result: IPFSAgentResult) -> IPFSCIDVersion0:
|
26
|
+
with tempfile.NamedTemporaryFile(mode="r+", encoding="utf-8") as json_file:
|
27
|
+
json.dump(agent_result.model_dump(), json_file, indent=4)
|
28
|
+
json_file.flush()
|
29
|
+
ipfs_hash = self.upload_file(json_file.name)
|
30
|
+
return ipfs_hash
|
31
|
+
|
32
|
+
def unpin_file(self, hash_to_remove: str) -> None:
|
33
|
+
self.pinata.remove_pin_from_ipfs(hash_to_remove=hash_to_remove)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import tenacity
|
2
|
-
from loguru import logger
|
3
2
|
|
4
3
|
from prediction_market_agent_tooling.config import APIKeys
|
4
|
+
from prediction_market_agent_tooling.loggers import logger
|
5
5
|
from prediction_market_agent_tooling.tools.cache import persistent_inmemory_cache
|
6
6
|
from prediction_market_agent_tooling.tools.langfuse_ import (
|
7
7
|
get_langfuse_langchain_config,
|
@@ -6,6 +6,7 @@ from langfuse import Langfuse
|
|
6
6
|
from langfuse.client import TraceWithDetails
|
7
7
|
from pydantic import BaseModel
|
8
8
|
|
9
|
+
from prediction_market_agent_tooling.loggers import logger
|
9
10
|
from prediction_market_agent_tooling.markets.data_models import (
|
10
11
|
PlacedTrade,
|
11
12
|
ProbabilisticAnswer,
|
@@ -13,7 +14,7 @@ from prediction_market_agent_tooling.markets.data_models import (
|
|
13
14
|
TradeType,
|
14
15
|
)
|
15
16
|
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
16
|
-
from prediction_market_agent_tooling.tools.utils import
|
17
|
+
from prediction_market_agent_tooling.tools.utils import convert_to_utc_datetime
|
17
18
|
|
18
19
|
|
19
20
|
class ProcessMarketTrace(BaseModel):
|
@@ -23,11 +24,11 @@ class ProcessMarketTrace(BaseModel):
|
|
23
24
|
trades: list[PlacedTrade]
|
24
25
|
|
25
26
|
@property
|
26
|
-
def buy_trade(self) -> PlacedTrade:
|
27
|
+
def buy_trade(self) -> PlacedTrade | None:
|
27
28
|
buy_trades = [t for t in self.trades if t.trade_type == TradeType.BUY]
|
28
|
-
if len(buy_trades)
|
29
|
-
|
30
|
-
|
29
|
+
if len(buy_trades) > 1:
|
30
|
+
raise ValueError("Unhandled logic, check it outm please!")
|
31
|
+
return buy_trades[0] if buy_trades else None
|
31
32
|
|
32
33
|
@staticmethod
|
33
34
|
def from_langfuse_trace(
|
@@ -134,8 +135,10 @@ def get_trace_for_bet(
|
|
134
135
|
traces_for_bet: list[ProcessMarketTrace] = []
|
135
136
|
for t in traces:
|
136
137
|
# Cannot use exact comparison due to gas fees
|
137
|
-
if
|
138
|
-
t.buy_trade
|
138
|
+
if (
|
139
|
+
t.buy_trade
|
140
|
+
and t.buy_trade.outcome == bet.outcome
|
141
|
+
and np.isclose(t.buy_trade.amount.amount, bet.amount.amount)
|
139
142
|
):
|
140
143
|
traces_for_bet.append(t)
|
141
144
|
|
@@ -146,9 +149,21 @@ def get_trace_for_bet(
|
|
146
149
|
else:
|
147
150
|
# In-case there are multiple traces for the same market, get the closest
|
148
151
|
# trace to the bet
|
152
|
+
bet_timestamp = convert_to_utc_datetime(bet.created_time)
|
149
153
|
closest_trace_index = get_closest_datetime_from_list(
|
150
|
-
|
154
|
+
bet_timestamp,
|
151
155
|
[t.timestamp for t in traces_for_bet],
|
152
156
|
)
|
153
157
|
|
158
|
+
# Sanity check: Let's say the upper bound for time between
|
159
|
+
# `agent.process_market` being called and the bet being placed is 20
|
160
|
+
# minutes
|
161
|
+
candidate_trace = traces_for_bet[closest_trace_index]
|
162
|
+
if abs(candidate_trace.timestamp - bet_timestamp).total_seconds() > 1200:
|
163
|
+
logger.info(
|
164
|
+
f"Closest trace to bet has timestamp {candidate_trace.timestamp}, "
|
165
|
+
f"but bet was created at {bet_timestamp}. Not matching"
|
166
|
+
)
|
167
|
+
return None
|
168
|
+
|
154
169
|
return traces_for_bet[closest_trace_index]
|