Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

404 add methods to pmat for storing prediction into ipfs and adding that to contract mapping #446

Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fb32a29
Small log | PMAT integration for agent result mapping | test
gabrielfior Sep 18, 2024
a56d3da
Subgraph integration for result mapping
gabrielfior Sep 20, 2024
52142ae
WIP
gabrielfior Sep 23, 2024
d8b913f
Merge remote-tracking branch 'origin/main' into 404-add-methods-to-pm…
gabrielfior Sep 29, 2024
f00e37b
Added IPFS upload to DeployableTraderAgent
gabrielfior Sep 29, 2024
aba7c4d
Added test for ipfs upload/unpin
gabrielfior Sep 29, 2024
745e911
Removed test hardcoding
gabrielfior Sep 29, 2024
7a8053b
Local test deploy working
gabrielfior Sep 29, 2024
6c2b66a
Small fixes before PR review
gabrielfior Sep 29, 2024
031bad7
Fixing CI
gabrielfior Sep 29, 2024
6e5a510
Merge remote-tracking branch 'origin/404-add-methods-to-pmat-for-stor…
gabrielfior Sep 30, 2024
80fe85f
Added txHashes to contract prediction being stored on chain
gabrielfior Sep 30, 2024
64a2e38
Merge remote-tracking branch 'refs/remotes/origin/main' into 404-add-…
gabrielfior Sep 30, 2024
17c92f0
Fixed isort
gabrielfior Sep 30, 2024
5a35b9a
Missing secrets
gabrielfior Sep 30, 2024
a8b57a6
Added safety margin
gabrielfior Sep 30, 2024
700864c
Fixing test
gabrielfior Sep 30, 2024
b865db1
Adding new test
gabrielfior Sep 30, 2024
ab1c713
Making tests pass
gabrielfior Sep 30, 2024
5d29c74
local_web3 session scoped
gabrielfior Sep 30, 2024
253f709
Tests finally passing locally
gabrielfior Sep 30, 2024
b26b60d
Fixing unit tests
gabrielfior Sep 30, 2024
b4b022c
Increasing rtol on market_moving bet test
gabrielfior Sep 30, 2024
051399e
Merge remote-tracking branch 'refs/remotes/origin/main' into 404-add-…
gabrielfior Oct 1, 2024
e24c8dd
Fixed isort
gabrielfior Oct 1, 2024
d4fb881
Fixed mypy
gabrielfior Oct 1, 2024
188d5d4
Incrased timeout of test
gabrielfior Oct 1, 2024
8090503
Making tests pass
gabrielfior Oct 1, 2024
01bd17b
Reactivating tests
gabrielfior Oct 1, 2024
3ad038f
Merge remote-tracking branch 'refs/remotes/origin/main' into 404-add-…
gabrielfior Oct 2, 2024
46b128b
Update tests_integration/tools/ipfs/test_ipfs_handler.py
gabrielfior Oct 2, 2024
9c62177
Implemented PR comments
gabrielfior Oct 2, 2024
da6aec0
Extracted local_chain test to other PR
gabrielfior Oct 2, 2024
43b540d
Fixed isort
gabrielfior Oct 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
MANIFOLD_API_KEY=
BET_FROM_PRIVATE_KEY=
OPENAI_API_KEY=
GRAPH_API_KEY=
GRAPH_API_KEY=
PINATA_API_KEY=
PINATA_API_SECRET=
2 changes: 2 additions & 0 deletions .github/workflows/python_ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ env:
GNOSIS_RPC_URL: ${{ secrets.GNOSIS_RPC_URL }}
GRAPH_API_KEY: ${{ secrets.GRAPH_API_KEY }}
METACULUS_API_KEY: ${{ secrets.METACULUS_API_KEY }}
PINATA_API_KEY: ${{ secrets.PINATA_API_KEY }}
PINATA_API_SECRET: ${{ secrets.PINATA_API_SECRET }}

jobs:
mypy:
Expand Down
543 changes: 289 additions & 254 deletions poetry.lock

Large diffs are not rendered by default.

171 changes: 171 additions & 0 deletions prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
[
{
"type": "constructor",
"inputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "addPrediction",
"inputs": [
{
"name": "marketAddress",
"type": "address",
"internalType": "address"
},
{
"name": "prediction",
"type": "tuple",
"internalType": "struct Prediction",
"components": [
{
"name": "publisherAddress",
"type": "address",
"internalType": "address"
},
{
"name": "ipfsHash",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "txHashes",
"type": "bytes32[]",
"internalType": "bytes32[]"
},
{
"name": "estimatedProbabilityBps",
"type": "uint16",
"internalType": "uint16"
}
]
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "getPredictionByIndex",
"inputs": [
{
"name": "marketAddress",
"type": "address",
"internalType": "address"
},
{
"name": "index",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct Prediction",
"components": [
{
"name": "publisherAddress",
"type": "address",
"internalType": "address"
},
{
"name": "ipfsHash",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "txHashes",
"type": "bytes32[]",
"internalType": "bytes32[]"
},
{
"name": "estimatedProbabilityBps",
"type": "uint16",
"internalType": "uint16"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "getPredictions",
"inputs": [
{
"name": "marketAddress",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "tuple[]",
"internalType": "struct Prediction[]",
"components": [
{
"name": "publisherAddress",
"type": "address",
"internalType": "address"
},
{
"name": "ipfsHash",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "txHashes",
"type": "bytes32[]",
"internalType": "bytes32[]"
},
{
"name": "estimatedProbabilityBps",
"type": "uint16",
"internalType": "uint16"
}
]
}
],
"stateMutability": "view"
},
{
"type": "event",
"name": "PredictionAdded",
"inputs": [
{
"name": "marketAddress",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "estimatedProbabilityBps",
"type": "uint16",
"indexed": false,
"internalType": "uint16"
},
{
"name": "publisherAddress",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "txHashes",
"type": "bytes32[]",
"indexed": false,
"internalType": "bytes32[]"
},
{
"name": "ipfsHash",
"type": "bytes32",
"indexed": false,
"internalType": "bytes32"
}
],
"anonymous": false
}
]
15 changes: 15 additions & 0 deletions prediction_market_agent_tooling/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ class APIKeys(BaseSettings):
LANGFUSE_HOST: t.Optional[str] = None
LANGFUSE_DEPLOYMENT_VERSION: t.Optional[str] = None

PINATA_API_KEY: t.Optional[SecretStr] = None
PINATA_API_SECRET: t.Optional[SecretStr] = None

TAVILY_API_KEY: t.Optional[SecretStr] = None

SQLALCHEMY_DB_URL: t.Optional[SecretStr] = None
Expand Down Expand Up @@ -148,6 +151,18 @@ def default_enable_langfuse(self) -> bool:
and self.LANGFUSE_HOST is not None
)

@property
def pinata_api_key(self) -> SecretStr:
return check_not_none(
self.PINATA_API_KEY, "PINATA_API_KEY missing in the environment."
)

@property
def pinata_api_secret(self) -> SecretStr:
return check_not_none(
self.PINATA_API_SECRET, "PINATA_API_SECRET missing in the environment."
)

@property
def tavily_api_key(self) -> SecretStr:
return check_not_none(
Expand Down
63 changes: 58 additions & 5 deletions prediction_market_agent_tooling/deploy/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from pydantic import BaseModel, BeforeValidator, computed_field
from typing_extensions import Annotated
from web3 import Web3

from prediction_market_agent_tooling.config import APIKeys
from prediction_market_agent_tooling.deploy.betting_strategy import (
Expand Down Expand Up @@ -47,17 +48,27 @@
MarketType,
have_bet_on_market_since,
)
from prediction_market_agent_tooling.markets.omen.data_models import (
ContractPrediction,
IPFSAgentResult,
)
from prediction_market_agent_tooling.markets.omen.omen import (
is_minimum_required_balance,
redeem_from_all_user_positions,
withdraw_wxdai_to_xdai_to_keep_balance,
)
from prediction_market_agent_tooling.markets.omen.omen_contracts import (
OmenAgentResultMappingContract,
)
from prediction_market_agent_tooling.monitor.monitor_app import (
MARKET_TYPE_TO_DEPLOYED_AGENT,
)
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
from prediction_market_agent_tooling.tools.is_predictable import is_predictable_binary
from prediction_market_agent_tooling.tools.langfuse_ import langfuse_context, observe
from prediction_market_agent_tooling.tools.utils import DatetimeWithTimezone, utcnow
from prediction_market_agent_tooling.tools.web3_utils import ipfscidv0_to_byte32

MAX_AVAILABLE_MARKETS = 20
TRADER_TAG = "trader"
Expand Down Expand Up @@ -291,6 +302,7 @@ def __init__(
) -> None:
super().__init__(enable_langfuse=enable_langfuse)
self.place_bet = place_bet
self.ipfs_handler = IPFSHandler(APIKeys())

def get_betting_strategy(self, market: AgentMarket) -> BettingStrategy:
user_id = market.get_user_id(api_keys=APIKeys())
Expand Down Expand Up @@ -461,7 +473,7 @@ def process_market(
placed_trades = []
if self.place_bet:
for trade in trades:
logger.info(f"Executing trade {trade}")
logger.info(f"Executing trade {trade} on market {market.id}")

match trade.trade_type:
case TradeType.BUY:
Expand All @@ -476,18 +488,59 @@ def process_market(
raise ValueError(f"Unexpected trade type {trade.trade_type}.")
placed_trades.append(PlacedTrade.from_trade(trade, id))

self.after_process_market(market_type, market)

processed_market = ProcessedMarket(answer=answer, trades=placed_trades)
self.update_langfuse_trace_by_processed_market(market_type, processed_market)

self.after_process_market(
market_type, market, processed_market=processed_market
)

logger.info(f"Processed market {market.question=} from {market.url=}.")
return processed_market

def after_process_market(
self, market_type: MarketType, market: AgentMarket
self,
market_type: MarketType,
market: AgentMarket,
processed_market: ProcessedMarket,
) -> None:
pass
if market_type != MarketType.OMEN:
logger.info(
f"Skipping after_process_market since market_type {market_type} != OMEN"
)
return
keys = APIKeys()
self.store_prediction(
market_id=market.id, processed_market=processed_market, keys=keys
)

def store_prediction(
self, market_id: str, processed_market: ProcessedMarket, keys: APIKeys
) -> None:
reasoning = (
processed_market.answer.reasoning
if processed_market.answer.reasoning
else ""
)
ipfs_hash = self.ipfs_handler.store_agent_result(
IPFSAgentResult(reasoning=reasoning)
)

tx_hashes = [HexBytes(i.id) for i in processed_market.trades]
prediction = ContractPrediction(
publisher=keys.public_key,
gabrielfior marked this conversation as resolved.
Show resolved Hide resolved
ipfs_hash=ipfscidv0_to_byte32(ipfs_hash),
tx_hashes=tx_hashes,
estimated_probability_bps=int(processed_market.answer.p_yes * 10000),
)
tx_receipt = OmenAgentResultMappingContract().add_prediction(
api_keys=keys,
market_address=Web3.to_checksum_address(market_id),
prediction=prediction,
)
logger.info(
f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].hex()}."
)
gabrielfior marked this conversation as resolved.
Show resolved Hide resolved

def before_process_markets(self, market_type: MarketType) -> None:
"""
Expand Down
23 changes: 22 additions & 1 deletion prediction_market_agent_tooling/markets/omen/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import datetime

import pytz
from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict, Field
from web3 import Web3

from prediction_market_agent_tooling.gtypes import (
Expand Down Expand Up @@ -534,3 +534,24 @@ class RealityAnswers(BaseModel):

class RealityAnswersResponse(BaseModel):
data: RealityAnswers


class ContractPrediction(BaseModel):
model_config = ConfigDict(populate_by_name=True)
publisher: ChecksumAddress = Field(..., alias="publisherAddress")
ipfs_hash: HexBytes = Field(..., alias="ipfsHash")
tx_hashes: list[HexBytes] = Field(..., alias="txHashes")
estimated_probability_bps: int = Field(..., alias="estimatedProbabilityBps")

@staticmethod
def from_tuple(values: tuple[t.Any]) -> "ContractPrediction":
data = {k: v for k, v in zip(ContractPrediction.model_fields.keys(), values)}
return ContractPrediction.model_validate(data)


class IPFSAgentResult(BaseModel):
reasoning: str

model_config = ConfigDict(
extra="forbid",
)
Loading
Loading