prediction-market-agent-tooling 0.69.7.dev1114__py3-none-any.whl → 0.69.9__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/swapr_quoter.abi.json +221 -0
- prediction_market_agent_tooling/markets/seer/data_models.py +11 -0
- prediction_market_agent_tooling/markets/seer/price_manager.py +44 -17
- prediction_market_agent_tooling/markets/seer/seer.py +10 -6
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +29 -0
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +12 -31
- prediction_market_agent_tooling/tools/caches/db_cache.py +215 -113
- {prediction_market_agent_tooling-0.69.7.dev1114.dist-info → prediction_market_agent_tooling-0.69.9.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.69.7.dev1114.dist-info → prediction_market_agent_tooling-0.69.9.dist-info}/RECORD +12 -11
- {prediction_market_agent_tooling-0.69.7.dev1114.dist-info → prediction_market_agent_tooling-0.69.9.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.69.7.dev1114.dist-info → prediction_market_agent_tooling-0.69.9.dist-info}/entry_points.txt +0 -0
- {prediction_market_agent_tooling-0.69.7.dev1114.dist-info → prediction_market_agent_tooling-0.69.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,221 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"inputs": [
|
4
|
+
{
|
5
|
+
"internalType": "address",
|
6
|
+
"name": "_factory",
|
7
|
+
"type": "address"
|
8
|
+
},
|
9
|
+
{
|
10
|
+
"internalType": "address",
|
11
|
+
"name": "_WNativeToken",
|
12
|
+
"type": "address"
|
13
|
+
},
|
14
|
+
{
|
15
|
+
"internalType": "address",
|
16
|
+
"name": "_poolDeployer",
|
17
|
+
"type": "address"
|
18
|
+
}
|
19
|
+
],
|
20
|
+
"stateMutability": "nonpayable",
|
21
|
+
"type": "constructor"
|
22
|
+
},
|
23
|
+
{
|
24
|
+
"inputs": [],
|
25
|
+
"name": "WNativeToken",
|
26
|
+
"outputs": [
|
27
|
+
{
|
28
|
+
"internalType": "address",
|
29
|
+
"name": "",
|
30
|
+
"type": "address"
|
31
|
+
}
|
32
|
+
],
|
33
|
+
"stateMutability": "view",
|
34
|
+
"type": "function"
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"inputs": [
|
38
|
+
{
|
39
|
+
"internalType": "int256",
|
40
|
+
"name": "amount0Delta",
|
41
|
+
"type": "int256"
|
42
|
+
},
|
43
|
+
{
|
44
|
+
"internalType": "int256",
|
45
|
+
"name": "amount1Delta",
|
46
|
+
"type": "int256"
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"internalType": "bytes",
|
50
|
+
"name": "path",
|
51
|
+
"type": "bytes"
|
52
|
+
}
|
53
|
+
],
|
54
|
+
"name": "algebraSwapCallback",
|
55
|
+
"outputs": [],
|
56
|
+
"stateMutability": "view",
|
57
|
+
"type": "function"
|
58
|
+
},
|
59
|
+
{
|
60
|
+
"inputs": [],
|
61
|
+
"name": "factory",
|
62
|
+
"outputs": [
|
63
|
+
{
|
64
|
+
"internalType": "address",
|
65
|
+
"name": "",
|
66
|
+
"type": "address"
|
67
|
+
}
|
68
|
+
],
|
69
|
+
"stateMutability": "view",
|
70
|
+
"type": "function"
|
71
|
+
},
|
72
|
+
{
|
73
|
+
"inputs": [],
|
74
|
+
"name": "poolDeployer",
|
75
|
+
"outputs": [
|
76
|
+
{
|
77
|
+
"internalType": "address",
|
78
|
+
"name": "",
|
79
|
+
"type": "address"
|
80
|
+
}
|
81
|
+
],
|
82
|
+
"stateMutability": "view",
|
83
|
+
"type": "function"
|
84
|
+
},
|
85
|
+
{
|
86
|
+
"inputs": [
|
87
|
+
{
|
88
|
+
"internalType": "bytes",
|
89
|
+
"name": "path",
|
90
|
+
"type": "bytes"
|
91
|
+
},
|
92
|
+
{
|
93
|
+
"internalType": "uint256",
|
94
|
+
"name": "amountIn",
|
95
|
+
"type": "uint256"
|
96
|
+
}
|
97
|
+
],
|
98
|
+
"name": "quoteExactInput",
|
99
|
+
"outputs": [
|
100
|
+
{
|
101
|
+
"internalType": "uint256",
|
102
|
+
"name": "amountOut",
|
103
|
+
"type": "uint256"
|
104
|
+
},
|
105
|
+
{
|
106
|
+
"internalType": "uint16[]",
|
107
|
+
"name": "fees",
|
108
|
+
"type": "uint16[]"
|
109
|
+
}
|
110
|
+
],
|
111
|
+
"stateMutability": "nonpayable",
|
112
|
+
"type": "function"
|
113
|
+
},
|
114
|
+
{
|
115
|
+
"inputs": [
|
116
|
+
{
|
117
|
+
"internalType": "address",
|
118
|
+
"name": "tokenIn",
|
119
|
+
"type": "address"
|
120
|
+
},
|
121
|
+
{
|
122
|
+
"internalType": "address",
|
123
|
+
"name": "tokenOut",
|
124
|
+
"type": "address"
|
125
|
+
},
|
126
|
+
{
|
127
|
+
"internalType": "uint256",
|
128
|
+
"name": "amountIn",
|
129
|
+
"type": "uint256"
|
130
|
+
},
|
131
|
+
{
|
132
|
+
"internalType": "uint160",
|
133
|
+
"name": "limitSqrtPrice",
|
134
|
+
"type": "uint160"
|
135
|
+
}
|
136
|
+
],
|
137
|
+
"name": "quoteExactInputSingle",
|
138
|
+
"outputs": [
|
139
|
+
{
|
140
|
+
"internalType": "uint256",
|
141
|
+
"name": "amountOut",
|
142
|
+
"type": "uint256"
|
143
|
+
},
|
144
|
+
{
|
145
|
+
"internalType": "uint16",
|
146
|
+
"name": "fee",
|
147
|
+
"type": "uint16"
|
148
|
+
}
|
149
|
+
],
|
150
|
+
"stateMutability": "nonpayable",
|
151
|
+
"type": "function"
|
152
|
+
},
|
153
|
+
{
|
154
|
+
"inputs": [
|
155
|
+
{
|
156
|
+
"internalType": "bytes",
|
157
|
+
"name": "path",
|
158
|
+
"type": "bytes"
|
159
|
+
},
|
160
|
+
{
|
161
|
+
"internalType": "uint256",
|
162
|
+
"name": "amountOut",
|
163
|
+
"type": "uint256"
|
164
|
+
}
|
165
|
+
],
|
166
|
+
"name": "quoteExactOutput",
|
167
|
+
"outputs": [
|
168
|
+
{
|
169
|
+
"internalType": "uint256",
|
170
|
+
"name": "amountIn",
|
171
|
+
"type": "uint256"
|
172
|
+
},
|
173
|
+
{
|
174
|
+
"internalType": "uint16[]",
|
175
|
+
"name": "fees",
|
176
|
+
"type": "uint16[]"
|
177
|
+
}
|
178
|
+
],
|
179
|
+
"stateMutability": "nonpayable",
|
180
|
+
"type": "function"
|
181
|
+
},
|
182
|
+
{
|
183
|
+
"inputs": [
|
184
|
+
{
|
185
|
+
"internalType": "address",
|
186
|
+
"name": "tokenIn",
|
187
|
+
"type": "address"
|
188
|
+
},
|
189
|
+
{
|
190
|
+
"internalType": "address",
|
191
|
+
"name": "tokenOut",
|
192
|
+
"type": "address"
|
193
|
+
},
|
194
|
+
{
|
195
|
+
"internalType": "uint256",
|
196
|
+
"name": "amountOut",
|
197
|
+
"type": "uint256"
|
198
|
+
},
|
199
|
+
{
|
200
|
+
"internalType": "uint160",
|
201
|
+
"name": "limitSqrtPrice",
|
202
|
+
"type": "uint160"
|
203
|
+
}
|
204
|
+
],
|
205
|
+
"name": "quoteExactOutputSingle",
|
206
|
+
"outputs": [
|
207
|
+
{
|
208
|
+
"internalType": "uint256",
|
209
|
+
"name": "amountIn",
|
210
|
+
"type": "uint256"
|
211
|
+
},
|
212
|
+
{
|
213
|
+
"internalType": "uint16",
|
214
|
+
"name": "fee",
|
215
|
+
"type": "uint16"
|
216
|
+
}
|
217
|
+
],
|
218
|
+
"stateMutability": "nonpayable",
|
219
|
+
"type": "function"
|
220
|
+
}
|
221
|
+
]
|
@@ -179,6 +179,17 @@ class ExactInputSingleParams(BaseModel):
|
|
179
179
|
) # 0 for convenience, we also don't expect major price shifts
|
180
180
|
|
181
181
|
|
182
|
+
class QuoteExactInputSingleParams(BaseModel):
|
183
|
+
# from https://gnosisscan.io/address/0xcBaD9FDf0D2814659Eb26f600EFDeAF005Eda0F7#writeContract
|
184
|
+
model_config = ConfigDict(populate_by_name=True)
|
185
|
+
token_in: ChecksumAddress = Field(alias="tokenIn")
|
186
|
+
token_out: ChecksumAddress = Field(alias="tokenOut")
|
187
|
+
amount_in: Wei = Field(alias="amountIn")
|
188
|
+
limit_sqrt_price: Wei = Field(
|
189
|
+
alias="limitSqrtPrice", default_factory=lambda: Wei(0)
|
190
|
+
) # 0 for convenience, we also don't expect major price shifts
|
191
|
+
|
192
|
+
|
182
193
|
class SeerTransactionType(str, Enum):
|
183
194
|
SWAP = "swap"
|
184
195
|
SPLIT = "split"
|
@@ -10,12 +10,17 @@ from prediction_market_agent_tooling.gtypes import (
|
|
10
10
|
OutcomeStr,
|
11
11
|
OutcomeToken,
|
12
12
|
Probability,
|
13
|
+
Wei,
|
13
14
|
)
|
14
15
|
from prediction_market_agent_tooling.loggers import logger
|
15
16
|
from prediction_market_agent_tooling.markets.seer.data_models import SeerMarket
|
16
17
|
from prediction_market_agent_tooling.markets.seer.exceptions import (
|
17
18
|
PriceCalculationError,
|
18
19
|
)
|
20
|
+
from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
21
|
+
QuoteExactInputSingleParams,
|
22
|
+
SwaprQuoterContract,
|
23
|
+
)
|
19
24
|
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
20
25
|
SeerSubgraphHandler,
|
21
26
|
)
|
@@ -52,16 +57,16 @@ class PriceManager:
|
|
52
57
|
)
|
53
58
|
|
54
59
|
def get_price_for_token(self, token: ChecksumAddress) -> CollateralToken | None:
|
55
|
-
return self.get_amount_of_token_in_collateral(token,
|
60
|
+
return self.get_amount_of_token_in_collateral(token, OutcomeToken(1))
|
56
61
|
|
57
62
|
@cached(TTLCache(maxsize=100, ttl=5 * 60))
|
58
63
|
def get_amount_of_collateral_in_token(
|
59
64
|
self,
|
60
65
|
token: ChecksumAddress,
|
61
66
|
collateral_exchange_amount: CollateralToken,
|
62
|
-
) ->
|
67
|
+
) -> OutcomeToken | None:
|
63
68
|
if token == self.seer_market.collateral_token_contract_address_checksummed:
|
64
|
-
return collateral_exchange_amount
|
69
|
+
return OutcomeToken(collateral_exchange_amount.value)
|
65
70
|
|
66
71
|
try:
|
67
72
|
buy_token_amount = get_buy_token_amount_else_raise(
|
@@ -69,31 +74,31 @@ class PriceManager:
|
|
69
74
|
sell_token=self.seer_market.collateral_token_contract_address_checksummed,
|
70
75
|
buy_token=token,
|
71
76
|
)
|
72
|
-
return buy_token_amount.as_token
|
77
|
+
return OutcomeToken.from_token(buy_token_amount.as_token)
|
73
78
|
|
74
79
|
except Exception as e:
|
75
80
|
logger.warning(
|
76
81
|
f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
|
77
82
|
)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
else None
|
83
|
+
quote = self.get_swapr_input_quote(
|
84
|
+
input_token=self.seer_market.collateral_token_contract_address_checksummed,
|
85
|
+
output_token=token,
|
86
|
+
input_amount=collateral_exchange_amount.as_wei,
|
83
87
|
)
|
88
|
+
return OutcomeToken.from_token(quote.as_token)
|
84
89
|
|
85
90
|
@cached(TTLCache(maxsize=100, ttl=5 * 60))
|
86
91
|
def get_amount_of_token_in_collateral(
|
87
92
|
self,
|
88
93
|
token: ChecksumAddress,
|
89
|
-
token_exchange_amount:
|
94
|
+
token_exchange_amount: OutcomeToken,
|
90
95
|
) -> CollateralToken | None:
|
91
96
|
if token == self.seer_market.collateral_token_contract_address_checksummed:
|
92
|
-
return token_exchange_amount
|
97
|
+
return token_exchange_amount.as_token
|
93
98
|
|
94
99
|
try:
|
95
100
|
buy_collateral_amount = get_buy_token_amount_else_raise(
|
96
|
-
sell_amount=token_exchange_amount.as_wei,
|
101
|
+
sell_amount=token_exchange_amount.as_outcome_wei.as_wei,
|
97
102
|
sell_token=token,
|
98
103
|
buy_token=self.seer_market.collateral_token_contract_address_checksummed,
|
99
104
|
)
|
@@ -103,17 +108,21 @@ class PriceManager:
|
|
103
108
|
logger.warning(
|
104
109
|
f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
|
105
110
|
)
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
else None
|
111
|
+
quote = self.get_swapr_input_quote(
|
112
|
+
input_token=token,
|
113
|
+
output_token=self.seer_market.collateral_token_contract_address_checksummed,
|
114
|
+
input_amount=token_exchange_amount.as_outcome_wei.as_wei,
|
111
115
|
)
|
116
|
+
return quote.as_token
|
112
117
|
|
113
118
|
def get_token_price_from_pools(
|
114
119
|
self,
|
115
120
|
token: ChecksumAddress,
|
116
121
|
) -> Prices | None:
|
122
|
+
"""
|
123
|
+
Although this might come handy,
|
124
|
+
consider using `get_amount_of_collateral_in_token` or `get_amount_of_token_in_collateral` to have an exact quote.
|
125
|
+
"""
|
117
126
|
pool = SeerSubgraphHandler().get_pool_by_token(
|
118
127
|
token_address=token,
|
119
128
|
collateral_address=self.seer_market.collateral_token_contract_address_checksummed,
|
@@ -248,3 +257,21 @@ class PriceManager:
|
|
248
257
|
else:
|
249
258
|
probability_map[outcome] = Probability(0)
|
250
259
|
return probability_map, outcome_token_pool
|
260
|
+
|
261
|
+
def get_swapr_input_quote(
|
262
|
+
self,
|
263
|
+
input_token: ChecksumAddress,
|
264
|
+
output_token: ChecksumAddress,
|
265
|
+
input_amount: Wei,
|
266
|
+
web3: Web3 | None = None,
|
267
|
+
) -> Wei: # Not marked as OutcomeWei, but this works for both buying and selling.
|
268
|
+
quoter = SwaprQuoterContract()
|
269
|
+
amount_out, _ = quoter.quote_exact_input_single(
|
270
|
+
QuoteExactInputSingleParams(
|
271
|
+
token_in=input_token,
|
272
|
+
token_out=output_token,
|
273
|
+
amount_in=input_amount,
|
274
|
+
),
|
275
|
+
web3=web3,
|
276
|
+
)
|
277
|
+
return amount_out
|
@@ -159,6 +159,8 @@ class SeerAgentMarket(AgentMarket):
|
|
159
159
|
|
160
160
|
def get_token_in_usd(self, x: CollateralToken) -> USD:
|
161
161
|
p = self.get_price_manager()
|
162
|
+
# This function is meant to convert market's collateral token into usd value, however, on Seer, market's collateral can be another's market outcome token.
|
163
|
+
# That's why we need this middle step.
|
162
164
|
sdai_amount = p.get_amount_of_collateral_in_token(
|
163
165
|
# Hard-coded SDAI, because Seer is atm hard-coded it as well, and it's needed in case of fallback to pools. CoW would work with other tokens as well.
|
164
166
|
SDAI_CONTRACT_ADDRESS,
|
@@ -168,14 +170,16 @@ class SeerAgentMarket(AgentMarket):
|
|
168
170
|
raise RuntimeError(
|
169
171
|
"Both CoW and pool-fallback way of getting price failed."
|
170
172
|
)
|
171
|
-
return get_token_in_usd(sdai_amount, SDAI_CONTRACT_ADDRESS)
|
173
|
+
return get_token_in_usd(sdai_amount.as_token, SDAI_CONTRACT_ADDRESS)
|
172
174
|
|
173
175
|
def get_usd_in_token(self, x: USD) -> CollateralToken:
|
174
176
|
p = self.get_price_manager()
|
177
|
+
# This function is meant to convert market's collateral token into usd value, however, on Seer, market's collateral can be another's market outcome token.
|
178
|
+
# That's why we need this middle step.
|
175
179
|
token_amount = p.get_amount_of_token_in_collateral(
|
176
180
|
# Hard-coded SDAI, because Seer is atm hard-coded it as well, and it's needed in case of fallback to pools. CoW would work with other tokens as well.
|
177
181
|
SDAI_CONTRACT_ADDRESS,
|
178
|
-
get_usd_in_token(x, SDAI_CONTRACT_ADDRESS),
|
182
|
+
OutcomeToken.from_token(get_usd_in_token(x, SDAI_CONTRACT_ADDRESS)),
|
179
183
|
)
|
180
184
|
if token_amount is None:
|
181
185
|
raise RuntimeError(
|
@@ -205,7 +209,7 @@ class SeerAgentMarket(AgentMarket):
|
|
205
209
|
logger.info(f"Could not get price for token {outcome_token}")
|
206
210
|
return None
|
207
211
|
|
208
|
-
return
|
212
|
+
return amount_outcome_tokens
|
209
213
|
|
210
214
|
def get_sell_value_of_outcome_token(
|
211
215
|
self, outcome: OutcomeStr, amount: OutcomeToken
|
@@ -217,7 +221,7 @@ class SeerAgentMarket(AgentMarket):
|
|
217
221
|
|
218
222
|
p = self.get_price_manager()
|
219
223
|
value_outcome_token_in_collateral = p.get_amount_of_token_in_collateral(
|
220
|
-
wrapped_outcome_token, amount
|
224
|
+
wrapped_outcome_token, amount
|
221
225
|
)
|
222
226
|
|
223
227
|
if value_outcome_token_in_collateral is None:
|
@@ -583,7 +587,7 @@ class SeerAgentMarket(AgentMarket):
|
|
583
587
|
web3=web3,
|
584
588
|
)
|
585
589
|
collateral_balance = p.get_amount_of_token_in_collateral(
|
586
|
-
token_address_checksummed, token_balance
|
590
|
+
token_address_checksummed, OutcomeToken.from_token(token_balance)
|
587
591
|
)
|
588
592
|
|
589
593
|
# We ignore the liquidity in outcome tokens if price unknown.
|
@@ -688,7 +692,7 @@ class SeerAgentMarket(AgentMarket):
|
|
688
692
|
).buy_or_sell_outcome_token(
|
689
693
|
token_in=sell_token,
|
690
694
|
token_out=buy_token,
|
691
|
-
|
695
|
+
amount_in=amount_wei,
|
692
696
|
web3=web3,
|
693
697
|
)
|
694
698
|
swap_pool_tx_hash = tx_receipt["transactionHash"]
|
@@ -15,6 +15,7 @@ from prediction_market_agent_tooling.gtypes import (
|
|
15
15
|
)
|
16
16
|
from prediction_market_agent_tooling.markets.seer.data_models import (
|
17
17
|
ExactInputSingleParams,
|
18
|
+
QuoteExactInputSingleParams,
|
18
19
|
)
|
19
20
|
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
|
20
21
|
CreateCategoricalMarketsParams,
|
@@ -191,3 +192,31 @@ class SwaprRouterContract(ContractOnGnosisChain):
|
|
191
192
|
# Typical Swapr swaps use 150k-300k gas, we set conservative
|
192
193
|
default_gas=400_000,
|
193
194
|
)
|
195
|
+
|
196
|
+
|
197
|
+
class SwaprQuoterContract(ContractOnGnosisChain):
|
198
|
+
# File content taken from https://gnosisscan.io/address/0xcBaD9FDf0D2814659Eb26f600EFDeAF005Eda0F7#code.
|
199
|
+
abi: ABI = abi_field_validator(
|
200
|
+
os.path.join(
|
201
|
+
os.path.dirname(os.path.realpath(__file__)),
|
202
|
+
"../../abis/swapr_quoter.abi.json",
|
203
|
+
)
|
204
|
+
)
|
205
|
+
|
206
|
+
address: ChecksumAddress = Web3.to_checksum_address(
|
207
|
+
"0xcBaD9FDf0D2814659Eb26f600EFDeAF005Eda0F7"
|
208
|
+
)
|
209
|
+
|
210
|
+
def quote_exact_input_single(
|
211
|
+
self,
|
212
|
+
params: QuoteExactInputSingleParams,
|
213
|
+
web3: Web3 | None = None,
|
214
|
+
) -> tuple[Wei, Wei]:
|
215
|
+
# See https://docs.uniswap.org/contracts/v3/guides/swaps/single-swaps.
|
216
|
+
result: tuple[int, int] = self.call(
|
217
|
+
function_name="quoteExactInputSingle",
|
218
|
+
function_params=list(dict(params).values()),
|
219
|
+
web3=web3,
|
220
|
+
)
|
221
|
+
amount_out, fee = result
|
222
|
+
return Wei(amount_out), Wei(fee)
|
@@ -3,7 +3,6 @@ from web3 import Web3
|
|
3
3
|
from prediction_market_agent_tooling.config import APIKeys
|
4
4
|
from prediction_market_agent_tooling.gtypes import (
|
5
5
|
ChecksumAddress,
|
6
|
-
CollateralToken,
|
7
6
|
HexBytes,
|
8
7
|
HexStr,
|
9
8
|
TxReceipt,
|
@@ -36,22 +35,20 @@ class SwapPoolHandler:
|
|
36
35
|
|
37
36
|
def _calculate_amount_out_minimum(
|
38
37
|
self,
|
39
|
-
|
38
|
+
amount_in: Wei,
|
40
39
|
token_in: ChecksumAddress,
|
41
|
-
|
40
|
+
token_out: ChecksumAddress,
|
42
41
|
buffer_pct: float = 0.05,
|
43
42
|
) -> Wei:
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
value = amount_wei.value * price_outcome_token.value * (1.0 - buffer_pct)
|
50
|
-
return Wei(int(value))
|
43
|
+
price_manager = PriceManager.build(HexBytes(HexStr(self.market_id)))
|
44
|
+
value = price_manager.get_swapr_input_quote(
|
45
|
+
input_amount=amount_in, input_token=token_in, output_token=token_out
|
46
|
+
)
|
47
|
+
return value * (1 - buffer_pct)
|
51
48
|
|
52
49
|
def buy_or_sell_outcome_token(
|
53
50
|
self,
|
54
|
-
|
51
|
+
amount_in: Wei,
|
55
52
|
token_in: ChecksumAddress,
|
56
53
|
token_out: ChecksumAddress,
|
57
54
|
web3: Web3 | None = None,
|
@@ -62,38 +59,22 @@ class SwapPoolHandler:
|
|
62
59
|
f"trading outcome_token for a token different than collateral_token {self.collateral_token_address} is not supported. {token_in=} {token_out=}"
|
63
60
|
)
|
64
61
|
|
65
|
-
outcome_token = (
|
66
|
-
token_in if token_in != self.collateral_token_address else token_out
|
67
|
-
)
|
68
|
-
|
69
|
-
# We could use a quoter contract (https://github.com/SwaprHQ/swapr-sdk/blob/develop/src/entities/trades/swapr-v3/constants.ts#L7), but since there is normally 1 pool per outcome token/collateral pair, it's not necessary.
|
70
|
-
|
71
|
-
price_outcome_token = PriceManager.build(
|
72
|
-
HexBytes(HexStr(self.market_id))
|
73
|
-
).get_token_price_from_pools(token=outcome_token)
|
74
|
-
if (
|
75
|
-
not price_outcome_token
|
76
|
-
or not price_outcome_token.priceOfCollateralInAskingToken
|
77
|
-
):
|
78
|
-
raise ValueError(
|
79
|
-
f"Could not find price for {outcome_token=} and {self.collateral_token_address}"
|
80
|
-
)
|
81
|
-
|
82
62
|
amount_out_minimum = self._calculate_amount_out_minimum(
|
83
|
-
|
63
|
+
amount_in=amount_in,
|
84
64
|
token_in=token_in,
|
85
|
-
|
65
|
+
token_out=token_out,
|
86
66
|
)
|
87
67
|
|
88
68
|
p = ExactInputSingleParams(
|
89
69
|
token_in=token_in,
|
90
70
|
token_out=token_out,
|
91
71
|
recipient=self.api_keys.bet_from_address,
|
92
|
-
amount_in=
|
72
|
+
amount_in=amount_in,
|
93
73
|
amount_out_minimum=amount_out_minimum,
|
94
74
|
)
|
95
75
|
|
96
76
|
tx_receipt = SwaprRouterContract().exact_input_single(
|
97
77
|
api_keys=self.api_keys, params=p, web3=web3
|
98
78
|
)
|
79
|
+
|
99
80
|
return tx_receipt
|
@@ -1,6 +1,8 @@
|
|
1
|
+
import asyncio
|
1
2
|
import hashlib
|
2
3
|
import inspect
|
3
4
|
import json
|
5
|
+
from dataclasses import dataclass
|
4
6
|
from datetime import timedelta
|
5
7
|
from functools import wraps
|
6
8
|
from types import UnionType
|
@@ -12,6 +14,7 @@ from typing import (
|
|
12
14
|
cast,
|
13
15
|
get_args,
|
14
16
|
get_origin,
|
17
|
+
get_type_hints,
|
15
18
|
overload,
|
16
19
|
)
|
17
20
|
|
@@ -101,136 +104,235 @@ def db_cache(
|
|
101
104
|
|
102
105
|
api_keys = api_keys if api_keys is not None else APIKeys()
|
103
106
|
|
104
|
-
|
105
|
-
|
106
|
-
# If caching is disabled, just call the function and return it
|
107
|
-
if not api_keys.ENABLE_CACHE:
|
108
|
-
return func(*args, **kwargs)
|
107
|
+
# Check if the decorated function is async
|
108
|
+
if inspect.iscoroutinefunction(func):
|
109
109
|
|
110
|
-
|
111
|
-
|
112
|
-
|
110
|
+
@wraps(func)
|
111
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
112
|
+
# If caching is disabled, just call the function and return it
|
113
|
+
if not api_keys.ENABLE_CACHE:
|
114
|
+
return await func(*args, **kwargs)
|
113
115
|
|
114
|
-
|
115
|
-
signature = inspect.signature(func)
|
116
|
-
bound_arguments = signature.bind(*args, **kwargs)
|
117
|
-
bound_arguments.apply_defaults()
|
118
|
-
|
119
|
-
# Convert any argument that is Pydantic model into classic dictionary, otherwise it won't be json-serializable.
|
120
|
-
args_dict: dict[str, Any] = bound_arguments.arguments
|
121
|
-
|
122
|
-
# Remove `self` or `cls` if present (in case of class' methods)
|
123
|
-
if "self" in args_dict:
|
124
|
-
del args_dict["self"]
|
125
|
-
if "cls" in args_dict:
|
126
|
-
del args_dict["cls"]
|
127
|
-
|
128
|
-
# Remove ignored arguments
|
129
|
-
if ignore_args:
|
130
|
-
for arg in ignore_args:
|
131
|
-
if arg in args_dict:
|
132
|
-
del args_dict[arg]
|
133
|
-
|
134
|
-
# Remove arguments of ignored types
|
135
|
-
if ignore_arg_types:
|
136
|
-
args_dict = {
|
137
|
-
k: v
|
138
|
-
for k, v in args_dict.items()
|
139
|
-
if not isinstance(v, tuple(ignore_arg_types))
|
140
|
-
}
|
116
|
+
# Run blocking database operations in thread pool
|
141
117
|
|
142
|
-
|
143
|
-
|
144
|
-
args_hash = hashlib.md5(arg_string.encode()).hexdigest()
|
118
|
+
# Ensure tables in thread pool
|
119
|
+
await asyncio.to_thread(_ensure_tables, api_keys)
|
145
120
|
|
146
|
-
|
147
|
-
full_function_name = func.__module__ + "." + func.__qualname__
|
148
|
-
# But also get the standard function name to easily search for it in database
|
149
|
-
function_name = func.__name__
|
121
|
+
ctx = _build_context(func, args, kwargs, ignore_args, ignore_arg_types)
|
150
122
|
|
151
|
-
|
152
|
-
|
153
|
-
is_pydantic_model = return_type is not None and contains_pydantic_model(
|
154
|
-
return_type
|
155
|
-
)
|
123
|
+
# Fetch cached result in thread pool
|
124
|
+
lookup = await asyncio.to_thread(_fetch_cached, api_keys, ctx, max_age)
|
156
125
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
# Try to get cached result
|
161
|
-
statement = (
|
162
|
-
select(FunctionCache)
|
163
|
-
.where(
|
164
|
-
FunctionCache.function_name == function_name,
|
165
|
-
FunctionCache.full_function_name == full_function_name,
|
166
|
-
FunctionCache.args_hash == args_hash,
|
126
|
+
if lookup.hit:
|
127
|
+
logger.debug(
|
128
|
+
f"{DB_CACHE_LOG_PREFIX} [cache-hit] Cache hit for {ctx.full_function_name}"
|
167
129
|
)
|
168
|
-
.
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
cached_result = session.exec(statement).first()
|
174
|
-
|
175
|
-
if cached_result:
|
176
|
-
logger.info(
|
177
|
-
# Keep the special [case-hit] identifier so we can easily track it in GCP.
|
178
|
-
f"{DB_CACHE_LOG_PREFIX} [cache-hit] Cache hit for {full_function_name} with args {args_dict} and output {cached_result.result}"
|
130
|
+
return lookup.value
|
131
|
+
|
132
|
+
computed_result = await func(*args, **kwargs)
|
133
|
+
logger.debug(
|
134
|
+
f"{DB_CACHE_LOG_PREFIX} [cache-miss] Cache miss for {ctx.full_function_name}"
|
179
135
|
)
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
136
|
+
|
137
|
+
if cache_none or computed_result is not None:
|
138
|
+
# Save cached result in thread pool (fire-and-forget)
|
139
|
+
asyncio.create_task(
|
140
|
+
asyncio.to_thread(
|
141
|
+
_save_cached,
|
142
|
+
api_keys,
|
143
|
+
ctx,
|
144
|
+
computed_result,
|
145
|
+
log_error_on_unsavable_data,
|
190
146
|
)
|
191
|
-
|
192
|
-
|
193
|
-
|
147
|
+
)
|
148
|
+
|
149
|
+
return computed_result
|
150
|
+
|
151
|
+
return cast(FunctionT, async_wrapper)
|
152
|
+
|
153
|
+
@wraps(func)
|
154
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
155
|
+
if not api_keys.ENABLE_CACHE:
|
156
|
+
return func(*args, **kwargs)
|
157
|
+
|
158
|
+
_ensure_tables(api_keys)
|
159
|
+
|
160
|
+
ctx = _build_context(func, args, kwargs, ignore_args, ignore_arg_types)
|
161
|
+
lookup = _fetch_cached(api_keys, ctx, max_age)
|
162
|
+
|
163
|
+
if lookup.hit:
|
164
|
+
logger.debug(
|
165
|
+
f"{DB_CACHE_LOG_PREFIX} [cache-hit] Cache hit for {ctx.full_function_name}"
|
166
|
+
)
|
167
|
+
return lookup.value
|
194
168
|
|
195
|
-
# On cache miss, compute the result
|
196
169
|
computed_result = func(*args, **kwargs)
|
197
|
-
|
198
|
-
|
199
|
-
f"{DB_CACHE_LOG_PREFIX} [cache-miss] Cache miss for {full_function_name} with args {args_dict}, computed the output {computed_result}"
|
170
|
+
logger.debug(
|
171
|
+
f"{DB_CACHE_LOG_PREFIX} [cache-miss] Cache miss for {ctx.full_function_name}"
|
200
172
|
)
|
201
173
|
|
202
|
-
# If postgres access was specified, save it.
|
203
174
|
if cache_none or computed_result is not None:
|
204
|
-
|
205
|
-
function_name=function_name,
|
206
|
-
full_function_name=full_function_name,
|
207
|
-
args_hash=args_hash,
|
208
|
-
args=args_dict,
|
209
|
-
result=computed_result,
|
210
|
-
created_at=utcnow(),
|
211
|
-
)
|
212
|
-
# Do not raise an exception if saving to the database fails, just log it and let the agent continue the work.
|
213
|
-
try:
|
214
|
-
with DBManager(
|
215
|
-
api_keys.sqlalchemy_db_url.get_secret_value()
|
216
|
-
).get_session() as session:
|
217
|
-
logger.info(
|
218
|
-
f"{DB_CACHE_LOG_PREFIX} [cache-info] Saving {cache_entry} into database."
|
219
|
-
)
|
220
|
-
session.add(cache_entry)
|
221
|
-
session.commit()
|
222
|
-
except (DataError, psycopg2.errors.UntranslatableCharacter) as e:
|
223
|
-
(logger.error if log_error_on_unsavable_data else logger.warning)(
|
224
|
-
f"{DB_CACHE_LOG_PREFIX} [cache-error] Failed to save {cache_entry} into database, ignoring, because: {e}"
|
225
|
-
)
|
226
|
-
except Exception:
|
227
|
-
logger.exception(
|
228
|
-
f"{DB_CACHE_LOG_PREFIX} [cache-error] Failed to save {cache_entry} into database, ignoring."
|
229
|
-
)
|
175
|
+
_save_cached(api_keys, ctx, computed_result, log_error_on_unsavable_data)
|
230
176
|
|
231
177
|
return computed_result
|
232
178
|
|
233
|
-
return cast(FunctionT,
|
179
|
+
return cast(FunctionT, sync_wrapper)
|
180
|
+
|
181
|
+
|
182
|
+
@dataclass
|
183
|
+
class CallContext:
|
184
|
+
args_dict: dict[str, Any]
|
185
|
+
args_hash: str
|
186
|
+
function_name: str
|
187
|
+
full_function_name: str
|
188
|
+
return_type: Any
|
189
|
+
|
190
|
+
@property
|
191
|
+
def is_pydantic_model(self) -> bool:
|
192
|
+
return self.return_type is not None and contains_pydantic_model(
|
193
|
+
self.return_type
|
194
|
+
)
|
195
|
+
|
196
|
+
|
197
|
+
@dataclass
|
198
|
+
class CacheLookup:
|
199
|
+
hit: bool
|
200
|
+
value: Any | None = None
|
201
|
+
|
202
|
+
|
203
|
+
def _ensure_tables(api_keys: APIKeys) -> None:
|
204
|
+
DBManager(api_keys.sqlalchemy_db_url.get_secret_value()).create_tables(
|
205
|
+
[FunctionCache]
|
206
|
+
)
|
207
|
+
|
208
|
+
|
209
|
+
def _build_context(
|
210
|
+
func: Callable[..., Any],
|
211
|
+
args: tuple[Any, ...],
|
212
|
+
kwargs: dict[str, Any],
|
213
|
+
ignore_args: Sequence[str] | None,
|
214
|
+
ignore_arg_types: Sequence[type] | None,
|
215
|
+
) -> CallContext:
|
216
|
+
signature = inspect.signature(func)
|
217
|
+
bound_arguments = signature.bind(*args, **kwargs)
|
218
|
+
bound_arguments.apply_defaults()
|
219
|
+
|
220
|
+
args_dict: dict[str, Any] = bound_arguments.arguments
|
221
|
+
|
222
|
+
if "self" in args_dict:
|
223
|
+
del args_dict["self"]
|
224
|
+
if "cls" in args_dict:
|
225
|
+
del args_dict["cls"]
|
226
|
+
|
227
|
+
if ignore_args:
|
228
|
+
for arg in ignore_args:
|
229
|
+
if arg in args_dict:
|
230
|
+
del args_dict[arg]
|
231
|
+
|
232
|
+
if ignore_arg_types:
|
233
|
+
args_dict = {
|
234
|
+
k: v
|
235
|
+
for k, v in args_dict.items()
|
236
|
+
if not isinstance(v, tuple(ignore_arg_types))
|
237
|
+
}
|
238
|
+
|
239
|
+
arg_string = json.dumps(args_dict, sort_keys=True, default=str)
|
240
|
+
args_hash = hashlib.md5(arg_string.encode()).hexdigest()
|
241
|
+
|
242
|
+
full_function_name = func.__module__ + "." + func.__qualname__
|
243
|
+
function_name = func.__name__
|
244
|
+
|
245
|
+
# Use get_type_hints to resolve forward references instead of __annotations__
|
246
|
+
try:
|
247
|
+
type_hints = get_type_hints(func)
|
248
|
+
return_type = type_hints.get("return", None)
|
249
|
+
except (NameError, AttributeError, TypeError) as e:
|
250
|
+
# Fallback to raw annotations if get_type_hints fails
|
251
|
+
logger.debug(
|
252
|
+
f"{DB_CACHE_LOG_PREFIX} Failed to resolve type hints for {full_function_name}, falling back to raw annotations: {e}"
|
253
|
+
)
|
254
|
+
return_type = func.__annotations__.get("return", None)
|
255
|
+
|
256
|
+
return CallContext(
|
257
|
+
args_dict=args_dict,
|
258
|
+
args_hash=args_hash,
|
259
|
+
function_name=function_name,
|
260
|
+
full_function_name=full_function_name,
|
261
|
+
return_type=return_type,
|
262
|
+
)
|
263
|
+
|
264
|
+
|
265
|
+
def _fetch_cached(
|
266
|
+
api_keys: APIKeys,
|
267
|
+
ctx: CallContext,
|
268
|
+
max_age: timedelta | None,
|
269
|
+
) -> CacheLookup:
|
270
|
+
with DBManager(
|
271
|
+
api_keys.sqlalchemy_db_url.get_secret_value()
|
272
|
+
).get_session() as session:
|
273
|
+
statement = (
|
274
|
+
select(FunctionCache)
|
275
|
+
.where(
|
276
|
+
FunctionCache.function_name == ctx.function_name,
|
277
|
+
FunctionCache.full_function_name == ctx.full_function_name,
|
278
|
+
FunctionCache.args_hash == ctx.args_hash,
|
279
|
+
)
|
280
|
+
.order_by(desc(FunctionCache.created_at))
|
281
|
+
)
|
282
|
+
if max_age is not None:
|
283
|
+
cutoff_time = utcnow() - max_age
|
284
|
+
statement = statement.where(FunctionCache.created_at >= cutoff_time)
|
285
|
+
cached_result = session.exec(statement).first()
|
286
|
+
|
287
|
+
if not cached_result:
|
288
|
+
return CacheLookup(hit=False)
|
289
|
+
|
290
|
+
if ctx.is_pydantic_model:
|
291
|
+
try:
|
292
|
+
value = convert_cached_output_to_pydantic(
|
293
|
+
ctx.return_type, cached_result.result
|
294
|
+
)
|
295
|
+
return CacheLookup(hit=True, value=value)
|
296
|
+
except (ValueError, TypeError) as e:
|
297
|
+
logger.warning(
|
298
|
+
f"{DB_CACHE_LOG_PREFIX} [cache-miss] Failed to validate cached result for {ctx.full_function_name}, treating as cache miss: {e}"
|
299
|
+
)
|
300
|
+
return CacheLookup(hit=False)
|
301
|
+
|
302
|
+
return CacheLookup(hit=True, value=cached_result.result)
|
303
|
+
|
304
|
+
|
305
|
+
def _save_cached(
|
306
|
+
api_keys: APIKeys,
|
307
|
+
ctx: CallContext,
|
308
|
+
computed_result: Any,
|
309
|
+
log_error_on_unsavable_data: bool,
|
310
|
+
) -> None:
|
311
|
+
cache_entry = FunctionCache(
|
312
|
+
function_name=ctx.function_name,
|
313
|
+
full_function_name=ctx.full_function_name,
|
314
|
+
args_hash=ctx.args_hash,
|
315
|
+
args=ctx.args_dict,
|
316
|
+
result=computed_result,
|
317
|
+
created_at=utcnow(),
|
318
|
+
)
|
319
|
+
try:
|
320
|
+
with DBManager(
|
321
|
+
api_keys.sqlalchemy_db_url.get_secret_value()
|
322
|
+
).get_session() as session:
|
323
|
+
logger.debug(
|
324
|
+
f"{DB_CACHE_LOG_PREFIX} [cache-save] Saving cache entry for {ctx.full_function_name}"
|
325
|
+
)
|
326
|
+
session.add(cache_entry)
|
327
|
+
session.commit()
|
328
|
+
except (DataError, psycopg2.errors.UntranslatableCharacter) as e:
|
329
|
+
(logger.error if log_error_on_unsavable_data else logger.warning)(
|
330
|
+
f"{DB_CACHE_LOG_PREFIX} [cache-error] Failed to save cache entry for {ctx.full_function_name}: {e}"
|
331
|
+
)
|
332
|
+
except Exception:
|
333
|
+
logger.exception(
|
334
|
+
f"{DB_CACHE_LOG_PREFIX} [cache-error] Failed to save cache entry for {ctx.full_function_name}"
|
335
|
+
)
|
234
336
|
|
235
337
|
|
236
338
|
def contains_pydantic_model(return_type: Any) -> bool:
|
@@ -19,6 +19,7 @@ prediction_market_agent_tooling/abis/ownable_erc721.abi.json,sha256=9sxm588MAQmq
|
|
19
19
|
prediction_market_agent_tooling/abis/proxy.abi.json,sha256=h24GXZ6Q0bSZlwh7zOv0EiDvbqUz_PHtWfKHTyPJ1w4,644
|
20
20
|
prediction_market_agent_tooling/abis/seer_gnosis_router.abi.json,sha256=DyADzOXhy9MDS31ReVrG7ibpWbw1jVy19nExZ80xfRY,6839
|
21
21
|
prediction_market_agent_tooling/abis/seer_market_factory.abi.json,sha256=g7RVxZVUWlTXIgTV2W6kO4twQM909Qv58zAr7Dk4XIc,13553
|
22
|
+
prediction_market_agent_tooling/abis/swapr_quoter.abi.json,sha256=pm21GfVFY2yMYn3D0yKYZIYc_GQkX5GfE6lsGv6xl7c,5560
|
22
23
|
prediction_market_agent_tooling/abis/swapr_router.abi.json,sha256=Y1wK20D1-zdbdlzkzV0BHY-HXMJrog6dgSHEzKzrQOU,13346
|
23
24
|
prediction_market_agent_tooling/benchmark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
25
|
prediction_market_agent_tooling/benchmark/agents.py,sha256=zC5tUM6pPTWtqSddOOSYV_fxHYmZb5uGAb4Ru87Tah8,4221
|
@@ -73,22 +74,22 @@ prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=QKbmSflc
|
|
73
74
|
prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py,sha256=x8yvAUPl55nbJALsQAAHJtstahhpH8WcEOj8or_QloQ,1165
|
74
75
|
prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py,sha256=6_dJbp9YBz0TZW6PxWGIB1L9VE-1JAIzvf8qTBrEyYs,2905
|
75
76
|
prediction_market_agent_tooling/markets/polymarket/utils.py,sha256=i1KjkQWABUoQyfX-XBuhACm6-POFMTXWifgP1V2sz-Y,1279
|
76
|
-
prediction_market_agent_tooling/markets/seer/data_models.py,sha256=
|
77
|
+
prediction_market_agent_tooling/markets/seer/data_models.py,sha256=FjkrI761jXygGMDm9qdoIsCvNaPQc1sGaPcHqIGsjho,8148
|
77
78
|
prediction_market_agent_tooling/markets/seer/exceptions.py,sha256=cEObdjluivD94tgOLzmimR7wgQEOt6SRakrYdhsRQtk,112
|
78
|
-
prediction_market_agent_tooling/markets/seer/price_manager.py,sha256=
|
79
|
-
prediction_market_agent_tooling/markets/seer/seer.py,sha256=
|
79
|
+
prediction_market_agent_tooling/markets/seer/price_manager.py,sha256=xxApIc4L79vRcF-mmlOK39ZzuBui_H3JkVPgvqwbmj0,11085
|
80
|
+
prediction_market_agent_tooling/markets/seer/seer.py,sha256=L5QrpFqfP4jSQOAIZs96S5VEWHrG74Yx2t_Ce0rBWhs,31602
|
80
81
|
prediction_market_agent_tooling/markets/seer/seer_api.py,sha256=4iiTWskxWm__oGgUUd1GhV_ItPSrAr0OfnYQ7lKndnQ,1143
|
81
|
-
prediction_market_agent_tooling/markets/seer/seer_contracts.py,sha256=
|
82
|
+
prediction_market_agent_tooling/markets/seer/seer_contracts.py,sha256=0KHxAbUfLu-1cXBr5vBOCkavhKqskmGJBtaCqi5Y1r0,7153
|
82
83
|
prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=nmeSofxwtdB6osrP0ASmDWhL684U52Qp4bqJRbRFkCE,18794
|
83
84
|
prediction_market_agent_tooling/markets/seer/subgraph_data_models.py,sha256=96v41jdNNxBqTCp5g8SLMSrIgeeITVx5IsBjVgm-qdo,2920
|
84
|
-
prediction_market_agent_tooling/markets/seer/swap_pool_handler.py,sha256=
|
85
|
+
prediction_market_agent_tooling/markets/seer/swap_pool_handler.py,sha256=24RVIppztXXFkZp0df7GR_GUSslwR42XsPJhDZ-jphs,2612
|
85
86
|
prediction_market_agent_tooling/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
86
87
|
prediction_market_agent_tooling/tools/_generic_value.py,sha256=L9SH_-JwD6RQeKoeTxvNWbkY6CxWIz-VjZEURXoZleg,10617
|
87
88
|
prediction_market_agent_tooling/tools/balances.py,sha256=Osab21btfJDw2Y-jT_TV-KHGrseCRxcsYeW6WcOMB8E,1050
|
88
89
|
prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=o5ba633gKiDqV4t_C2d9FWwH-HkRAOZd8FcZTYvbj6g,14451
|
89
90
|
prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py,sha256=THMXwFlskvzbjnX_OiYtDSzI8XVFyULWfP2525_9UGc,429
|
90
91
|
prediction_market_agent_tooling/tools/betting_strategies/utils.py,sha256=MpS3FOMn0C7nbmbQRUT9QwSh3UzzsgGczP91iSMr9wo,261
|
91
|
-
prediction_market_agent_tooling/tools/caches/db_cache.py,sha256=
|
92
|
+
prediction_market_agent_tooling/tools/caches/db_cache.py,sha256=V6o6UdesjkKzSJMhqkUtD76cJGPaNhuwA4OL2chIYSI,13801
|
92
93
|
prediction_market_agent_tooling/tools/caches/inmemory_cache.py,sha256=ZW5iI5rmjqeAebu5T7ftRnlkxiL02IC-MxCfDB80x7w,1506
|
93
94
|
prediction_market_agent_tooling/tools/caches/serializers.py,sha256=vFDx4fsPxclXp2q0sv27j4al_M_Tj9aR2JJP-xNHQXA,2151
|
94
95
|
prediction_market_agent_tooling/tools/contract.py,sha256=BzpAFcbKl_KqwgAlaXx63Fg8jzr0EO3qEeOs1K11CPA,33905
|
@@ -136,8 +137,8 @@ prediction_market_agent_tooling/tools/tokens/usd.py,sha256=DPO-4HBTy1-TZHKL_9CnH
|
|
136
137
|
prediction_market_agent_tooling/tools/transaction_cache.py,sha256=K5YKNL2_tR10Iw2TD9fuP-CTGpBbZtNdgbd0B_R7pjg,1814
|
137
138
|
prediction_market_agent_tooling/tools/utils.py,sha256=ruq6P5TFs8CBHxeBLj1Plpx7kuNFPpDgMsJGQgDiRNs,8785
|
138
139
|
prediction_market_agent_tooling/tools/web3_utils.py,sha256=CDbaidlLeQ4VHzSg150L7QNfHfGveljSePGuDVFEYqc,13963
|
139
|
-
prediction_market_agent_tooling-0.69.
|
140
|
-
prediction_market_agent_tooling-0.69.
|
141
|
-
prediction_market_agent_tooling-0.69.
|
142
|
-
prediction_market_agent_tooling-0.69.
|
143
|
-
prediction_market_agent_tooling-0.69.
|
140
|
+
prediction_market_agent_tooling-0.69.9.dist-info/METADATA,sha256=fBezbmaBxLVzpMNtCsmCnE2Pc8ywbXJTnCRF7HMJcYw,8890
|
141
|
+
prediction_market_agent_tooling-0.69.9.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
|
142
|
+
prediction_market_agent_tooling-0.69.9.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
|
143
|
+
prediction_market_agent_tooling-0.69.9.dist-info/licenses/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
|
144
|
+
prediction_market_agent_tooling-0.69.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|