prediction-market-agent-tooling 0.48.18__py3-none-any.whl → 0.49.1__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 +86 -13
- prediction_market_agent_tooling/deploy/betting_strategy.py +5 -35
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +2 -1
- prediction_market_agent_tooling/markets/agent_market.py +14 -6
- prediction_market_agent_tooling/markets/data_models.py +14 -0
- prediction_market_agent_tooling/markets/manifold/api.py +3 -1
- prediction_market_agent_tooling/markets/manifold/manifold.py +7 -2
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +6 -1
- prediction_market_agent_tooling/markets/omen/data_models.py +247 -6
- prediction_market_agent_tooling/markets/omen/omen.py +77 -43
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +179 -33
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +35 -0
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +1 -1
- prediction_market_agent_tooling/monitor/markets/polymarket.py +4 -0
- 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/ipfs/ipfs_handler.py +33 -0
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +27 -12
- prediction_market_agent_tooling/tools/utils.py +28 -4
- prediction_market_agent_tooling/tools/web3_utils.py +7 -0
- {prediction_market_agent_tooling-0.48.18.dist-info → prediction_market_agent_tooling-0.49.1.dist-info}/METADATA +2 -1
- {prediction_market_agent_tooling-0.48.18.dist-info → prediction_market_agent_tooling-0.49.1.dist-info}/RECORD +30 -27
- {prediction_market_agent_tooling-0.48.18.dist-info → prediction_market_agent_tooling-0.49.1.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.48.18.dist-info → prediction_market_agent_tooling-0.49.1.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.48.18.dist-info → prediction_market_agent_tooling-0.49.1.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,7 @@ 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.
|
416
434
|
|
417
435
|
|
418
436
|
class OmenFixedProductMarketMakerFactoryContract(ContractOnGnosisChain):
|
@@ -433,14 +451,15 @@ class OmenFixedProductMarketMakerFactoryContract(ContractOnGnosisChain):
|
|
433
451
|
condition_id: HexBytes,
|
434
452
|
initial_funds_wei: Wei,
|
435
453
|
collateral_token_address: ChecksumAddress,
|
436
|
-
fee:
|
454
|
+
fee: Wei, # This is actually fee in %, 'where 100% == 1 xDai'.
|
455
|
+
distribution_hint: list[OmenOutcomeToken] | None = None,
|
437
456
|
tx_params: t.Optional[TxParams] = None,
|
438
457
|
web3: Web3 | None = None,
|
439
|
-
) ->
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
458
|
+
) -> tuple[
|
459
|
+
OmenFixedProductMarketMakerCreationEvent, FPMMFundingAddedEvent, TxReceipt
|
460
|
+
]:
|
461
|
+
web3 = web3 or self.get_web3()
|
462
|
+
receipt_tx = self.send(
|
444
463
|
api_keys=api_keys,
|
445
464
|
function_name="create2FixedProductMarketMaker",
|
446
465
|
function_params=dict(
|
@@ -450,19 +469,41 @@ class OmenFixedProductMarketMakerFactoryContract(ContractOnGnosisChain):
|
|
450
469
|
conditionalTokens=OmenConditionalTokenContract().address,
|
451
470
|
collateralToken=collateral_token_address,
|
452
471
|
conditionIds=[condition_id],
|
453
|
-
fee=
|
472
|
+
fee=fee,
|
454
473
|
initialFunds=initial_funds_wei,
|
455
|
-
distributionHint=[],
|
474
|
+
distributionHint=distribution_hint or [],
|
456
475
|
),
|
457
476
|
tx_params=tx_params,
|
458
477
|
web3=web3,
|
459
478
|
)
|
460
479
|
|
480
|
+
market_event_logs = (
|
481
|
+
self.get_web3_contract(web3=web3)
|
482
|
+
.events.FixedProductMarketMakerCreation()
|
483
|
+
.process_receipt(receipt_tx)
|
484
|
+
)
|
485
|
+
market_event = OmenFixedProductMarketMakerCreationEvent(
|
486
|
+
**market_event_logs[0]["args"]
|
487
|
+
)
|
488
|
+
funding_event_logs = (
|
489
|
+
self.get_web3_contract(web3=web3)
|
490
|
+
.events.FPMMFundingAdded()
|
491
|
+
.process_receipt(receipt_tx)
|
492
|
+
)
|
493
|
+
funding_event = FPMMFundingAddedEvent(**funding_event_logs[0]["args"])
|
494
|
+
|
495
|
+
return market_event, funding_event, receipt_tx
|
496
|
+
|
461
497
|
|
462
498
|
class Arbitrator(str, Enum):
|
463
|
-
|
499
|
+
KLEROS_511_JURORS_WITHOUT_APPEAL = "kleros_511_jurors_without_appeal"
|
500
|
+
KLEROS_31_JURORS_WITH_APPEAL = "kleros_31_jurors_with_appeal"
|
464
501
|
DXDAO = "dxdao"
|
465
502
|
|
503
|
+
@property
|
504
|
+
def is_kleros(self) -> bool:
|
505
|
+
return self.value.startswith("kleros")
|
506
|
+
|
466
507
|
|
467
508
|
class OmenDxDaoContract(ContractOnGnosisChain):
|
468
509
|
# Contract ABI taken from https://gnosisscan.io/address/0xFe14059344b74043Af518d12931600C0f52dF7c5#code.
|
@@ -485,9 +526,22 @@ class OmenKlerosContract(ContractOnGnosisChain):
|
|
485
526
|
"../../abis/omen_kleros.abi.json",
|
486
527
|
)
|
487
528
|
)
|
488
|
-
|
489
|
-
|
490
|
-
)
|
529
|
+
|
530
|
+
@staticmethod
|
531
|
+
def from_arbitrator(arbitrator: "Arbitrator") -> "OmenKlerosContract":
|
532
|
+
"""
|
533
|
+
See https://docs.kleros.io/developer/deployment-addresses for all available addresses.
|
534
|
+
"""
|
535
|
+
if arbitrator == Arbitrator.KLEROS_511_JURORS_WITHOUT_APPEAL:
|
536
|
+
address = "0xe40DD83a262da3f56976038F1554Fe541Fa75ecd"
|
537
|
+
|
538
|
+
elif arbitrator == Arbitrator.KLEROS_31_JURORS_WITH_APPEAL:
|
539
|
+
address = "0x29f39de98d750eb77b5fafb31b2837f079fce222"
|
540
|
+
|
541
|
+
else:
|
542
|
+
raise ValueError(f"Unsupported arbitrator: {arbitrator=}")
|
543
|
+
|
544
|
+
return OmenKlerosContract(address=Web3.to_checksum_address(address))
|
491
545
|
|
492
546
|
|
493
547
|
class OmenRealitioContract(ContractOnGnosisChain):
|
@@ -506,8 +560,8 @@ class OmenRealitioContract(ContractOnGnosisChain):
|
|
506
560
|
def get_arbitrator_contract(
|
507
561
|
arbitrator: Arbitrator,
|
508
562
|
) -> ContractOnGnosisChain:
|
509
|
-
if arbitrator
|
510
|
-
return OmenKlerosContract()
|
563
|
+
if arbitrator.is_kleros:
|
564
|
+
return OmenKlerosContract.from_arbitrator(arbitrator)
|
511
565
|
if arbitrator == Arbitrator.DXDAO:
|
512
566
|
return OmenDxDaoContract()
|
513
567
|
raise ValueError(f"Unknown arbitrator: {arbitrator}")
|
@@ -521,25 +575,25 @@ class OmenRealitioContract(ContractOnGnosisChain):
|
|
521
575
|
language: str,
|
522
576
|
arbitrator: Arbitrator,
|
523
577
|
opening: datetime,
|
524
|
-
timeout: timedelta
|
578
|
+
timeout: timedelta,
|
525
579
|
nonce: int | None = None,
|
526
580
|
tx_params: t.Optional[TxParams] = None,
|
527
581
|
web3: Web3 | None = None,
|
528
|
-
) ->
|
582
|
+
) -> RealitioLogNewQuestionEvent:
|
529
583
|
"""
|
530
584
|
After the question is created, you can find it at https://reality.eth.link/app/#!/creator/{from_address}.
|
531
585
|
"""
|
586
|
+
web3 = web3 or self.get_web3()
|
532
587
|
arbitrator_contract_address = self.get_arbitrator_contract(arbitrator).address
|
533
588
|
# See https://realitio.github.io/docs/html/contracts.html#templates
|
534
589
|
# for possible template ids and how to format the question.
|
535
590
|
template_id = 2
|
536
|
-
realitio_question =
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
]
|
591
|
+
realitio_question = format_realitio_question(
|
592
|
+
question=question,
|
593
|
+
outcomes=outcomes,
|
594
|
+
category=category,
|
595
|
+
language=language,
|
596
|
+
template_id=template_id,
|
543
597
|
)
|
544
598
|
receipt_tx = self.send(
|
545
599
|
api_keys=api_keys,
|
@@ -557,10 +611,15 @@ class OmenRealitioContract(ContractOnGnosisChain):
|
|
557
611
|
tx_params=tx_params,
|
558
612
|
web3=web3,
|
559
613
|
)
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
614
|
+
|
615
|
+
event_logs = (
|
616
|
+
self.get_web3_contract(web3=web3)
|
617
|
+
.events.LogNewQuestion()
|
618
|
+
.process_receipt(receipt_tx)
|
619
|
+
)
|
620
|
+
question_event = RealitioLogNewQuestionEvent(**event_logs[0]["args"])
|
621
|
+
|
622
|
+
return question_event
|
564
623
|
|
565
624
|
def submitAnswer(
|
566
625
|
self,
|
@@ -670,6 +729,93 @@ class OmenRealitioContract(ContractOnGnosisChain):
|
|
670
729
|
) -> TxReceipt:
|
671
730
|
return self.send(api_keys=api_keys, function_name="withdraw", web3=web3)
|
672
731
|
|
732
|
+
def getOpeningTS(
|
733
|
+
self,
|
734
|
+
question_id: HexBytes,
|
735
|
+
web3: Web3 | None = None,
|
736
|
+
) -> int:
|
737
|
+
ts: int = self.call(
|
738
|
+
function_name="getOpeningTS",
|
739
|
+
function_params=[question_id],
|
740
|
+
web3=web3,
|
741
|
+
)
|
742
|
+
return ts
|
743
|
+
|
744
|
+
def getFinalizeTS(
|
745
|
+
self,
|
746
|
+
question_id: HexBytes,
|
747
|
+
web3: Web3 | None = None,
|
748
|
+
) -> int:
|
749
|
+
ts: int = self.call(
|
750
|
+
function_name="getFinalizeTS",
|
751
|
+
function_params=[question_id],
|
752
|
+
web3=web3,
|
753
|
+
)
|
754
|
+
return ts
|
755
|
+
|
756
|
+
def isFinalized(
|
757
|
+
self,
|
758
|
+
question_id: HexBytes,
|
759
|
+
web3: Web3 | None = None,
|
760
|
+
) -> bool:
|
761
|
+
is_finalized: bool = self.call(
|
762
|
+
function_name="isFinalized",
|
763
|
+
function_params=[question_id],
|
764
|
+
web3=web3,
|
765
|
+
)
|
766
|
+
return is_finalized
|
767
|
+
|
768
|
+
def isPendingArbitration(
|
769
|
+
self,
|
770
|
+
question_id: HexBytes,
|
771
|
+
web3: Web3 | None = None,
|
772
|
+
) -> bool:
|
773
|
+
is_pending_arbitration: bool = self.call(
|
774
|
+
function_name="isPendingArbitration",
|
775
|
+
function_params=[question_id],
|
776
|
+
web3=web3,
|
777
|
+
)
|
778
|
+
return is_pending_arbitration
|
779
|
+
|
780
|
+
|
781
|
+
class OmenAgentResultMappingContract(ContractOnGnosisChain):
|
782
|
+
# Contract ABI taken from built https://github.com/gnosis/labs-contracts.
|
783
|
+
|
784
|
+
abi: ABI = abi_field_validator(
|
785
|
+
os.path.join(
|
786
|
+
os.path.dirname(os.path.realpath(__file__)),
|
787
|
+
"../../abis/omen_agentresultmapping.abi.json",
|
788
|
+
)
|
789
|
+
)
|
790
|
+
|
791
|
+
address: ChecksumAddress = Web3.to_checksum_address(
|
792
|
+
"0x260E1077dEA98e738324A6cEfB0EE9A272eD471a"
|
793
|
+
)
|
794
|
+
|
795
|
+
def get_predictions(
|
796
|
+
self,
|
797
|
+
market_address: ChecksumAddress,
|
798
|
+
web3: Web3 | None = None,
|
799
|
+
) -> list[ContractPrediction]:
|
800
|
+
prediction_tuples = self.call(
|
801
|
+
"getPredictions", function_params=[market_address], web3=web3
|
802
|
+
)
|
803
|
+
return [ContractPrediction.from_tuple(p) for p in prediction_tuples]
|
804
|
+
|
805
|
+
def add_prediction(
|
806
|
+
self,
|
807
|
+
api_keys: APIKeys,
|
808
|
+
market_address: ChecksumAddress,
|
809
|
+
prediction: ContractPrediction,
|
810
|
+
web3: Web3 | None = None,
|
811
|
+
) -> TxReceipt:
|
812
|
+
return self.send(
|
813
|
+
api_keys=api_keys,
|
814
|
+
function_name="addPrediction",
|
815
|
+
function_params=[market_address, prediction.model_dump(by_alias=True)],
|
816
|
+
web3=web3,
|
817
|
+
)
|
818
|
+
|
673
819
|
|
674
820
|
class OmenThumbnailMapping(ContractOnGnosisChain):
|
675
821
|
# Contract ABI taken from built https://github.com/gnosis/labs-contracts.
|
@@ -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)
|
@@ -797,3 +806,29 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
797
806
|
if image_url is not None
|
798
807
|
else None
|
799
808
|
)
|
809
|
+
|
810
|
+
def get_agent_results_for_market(
|
811
|
+
self, market_id: HexAddress | None = None
|
812
|
+
) -> list[ContractPrediction]:
|
813
|
+
where_stms = {}
|
814
|
+
if market_id:
|
815
|
+
where_stms["marketAddress"] = market_id.lower()
|
816
|
+
|
817
|
+
prediction_added = (
|
818
|
+
self.omen_agent_result_mapping_subgraph.Query.predictionAddeds(
|
819
|
+
where=where_stms,
|
820
|
+
orderBy="blockNumber",
|
821
|
+
orderDirection="asc",
|
822
|
+
)
|
823
|
+
)
|
824
|
+
fields = [
|
825
|
+
prediction_added.publisherAddress,
|
826
|
+
prediction_added.ipfsHash,
|
827
|
+
prediction_added.txHashes,
|
828
|
+
prediction_added.estimatedProbabilityBps,
|
829
|
+
]
|
830
|
+
result = self.sg.query_json(fields)
|
831
|
+
items = self._parse_items_from_json(result)
|
832
|
+
if not items:
|
833
|
+
return []
|
834
|
+
return [ContractPrediction.model_validate(i) for i in items]
|
@@ -46,7 +46,7 @@ class PolymarketAgentMarket(AgentMarket):
|
|
46
46
|
def get_tiny_bet_amount(cls) -> BetAmount:
|
47
47
|
raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")
|
48
48
|
|
49
|
-
def place_bet(self, outcome: bool, amount: BetAmount) ->
|
49
|
+
def place_bet(self, outcome: bool, amount: BetAmount) -> str:
|
50
50
|
raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")
|
51
51
|
|
52
52
|
@staticmethod
|
@@ -43,3 +43,7 @@ class DeployedPolymarketAgent(DeployedAgent):
|
|
43
43
|
== MarketType.POLYMARKET.value,
|
44
44
|
) -> t.Sequence["DeployedPolymarketAgent"]:
|
45
45
|
return super().from_all_gcp_functions(filter_=filter_)
|
46
|
+
|
47
|
+
@staticmethod
|
48
|
+
def get_user_id(api_keys: APIKeys) -> str:
|
49
|
+
return api_keys.bet_from_address
|
@@ -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,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)
|
@@ -6,28 +6,29 @@ 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 (
|
11
|
+
PlacedTrade,
|
10
12
|
ProbabilisticAnswer,
|
11
13
|
ResolvedBet,
|
12
|
-
Trade,
|
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):
|
20
21
|
timestamp: datetime
|
21
22
|
market: OmenAgentMarket
|
22
23
|
answer: ProbabilisticAnswer
|
23
|
-
trades: list[
|
24
|
+
trades: list[PlacedTrade]
|
24
25
|
|
25
26
|
@property
|
26
|
-
def buy_trade(self) ->
|
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(
|
@@ -107,10 +108,10 @@ def trace_to_answer(trace: TraceWithDetails) -> ProbabilisticAnswer:
|
|
107
108
|
return ProbabilisticAnswer.model_validate(trace.output["answer"])
|
108
109
|
|
109
110
|
|
110
|
-
def trace_to_trades(trace: TraceWithDetails) -> list[
|
111
|
+
def trace_to_trades(trace: TraceWithDetails) -> list[PlacedTrade]:
|
111
112
|
assert trace.output is not None, "Trace output is None"
|
112
113
|
assert trace.output["trades"] is not None, "Trace output trades is None"
|
113
|
-
return [
|
114
|
+
return [PlacedTrade.model_validate(t) for t in trace.output["trades"]]
|
114
115
|
|
115
116
|
|
116
117
|
def get_closest_datetime_from_list(
|
@@ -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]
|