poly-web3 0.0.5__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
poly_web3/log.py CHANGED
@@ -9,15 +9,17 @@ import sys
9
9
 
10
10
  LOGGER_NAME = "poly_web3"
11
11
 
12
- logger = logging.getLogger(LOGGER_NAME)
13
- logger.addHandler(logging.NullHandler())
14
-
15
12
 
16
13
  def configure_logging(
17
14
  level: int = logging.INFO,
18
15
  stream=sys.stdout,
19
16
  formatter: logging.Formatter | None = None,
20
17
  ):
18
+ try:
19
+ from loguru import logger
20
+ return logger
21
+ except:
22
+ pass
21
23
  if formatter is None:
22
24
  formatter = logging.Formatter(
23
25
  "%(asctime)s | %(levelname)s | %(name)s | %(filename)s:%(lineno)d %(message)s",
@@ -34,15 +36,4 @@ def configure_logging(
34
36
  return target
35
37
 
36
38
 
37
- def _ensure_default_logging():
38
- target = logging.getLogger(LOGGER_NAME)
39
- if target.handlers and not all(
40
- isinstance(h, logging.NullHandler) for h in target.handlers
41
- ):
42
- return
43
- if logging.getLogger().handlers:
44
- return
45
- configure_logging()
46
-
47
-
48
- _ensure_default_logging()
39
+ logger = configure_logging()
@@ -4,10 +4,12 @@
4
4
  # @Site:
5
5
  # @File: base.py
6
6
  # @Software: PyCharm
7
- import requests
7
+ from typing import Any
8
+
8
9
  from py_builder_relayer_client.client import RelayClient
9
10
  from py_clob_client.client import ClobClient
10
11
  from web3 import Web3
12
+ import requests
11
13
 
12
14
  from poly_web3.const import (
13
15
  RPC_URL,
@@ -166,6 +168,107 @@ class BaseWeb3Service:
166
168
  redeem_amounts,
167
169
  )._encode_transaction_data()
168
170
 
171
+ def _build_redeem_tx(self, to: str, data: str) -> Any:
172
+ raise NotImplementedError("redeem tx builder not implemented")
173
+
174
+ def _build_redeem_txs_from_positions(self, positions: list[dict]) -> list[Any]:
175
+ neg_amounts_by_condition: dict[str, list[float]] = {}
176
+ normal_conditions: set[str] = set()
177
+ for pos in positions:
178
+ condition_id = pos.get("conditionId")
179
+ if not condition_id:
180
+ continue
181
+ if pos.get("negativeRisk"):
182
+ idx = pos.get("outcomeIndex")
183
+ size = pos.get("size")
184
+ if idx is None or size is None:
185
+ continue
186
+ amounts = neg_amounts_by_condition.setdefault(condition_id, [0.0, 0.0])
187
+ if idx not in (0, 1):
188
+ raise Exception(f"negRisk outcomeIndex out of range: {idx}")
189
+ amounts[idx] += size
190
+ else:
191
+ normal_conditions.add(condition_id)
192
+ txs: list[Any] = []
193
+ for condition_id, amounts in neg_amounts_by_condition.items():
194
+ int_amounts = [int(amount * 1e6) for amount in amounts]
195
+ txs.append(
196
+ self._build_redeem_tx(
197
+ NEG_RISK_ADAPTER_ADDRESS,
198
+ self.build_neg_risk_redeem_tx_data(condition_id, int_amounts),
199
+ )
200
+ )
201
+ for condition_id in normal_conditions:
202
+ txs.append(
203
+ self._build_redeem_tx(
204
+ CTF_ADDRESS,
205
+ self.build_ctf_redeem_tx_data(condition_id),
206
+ )
207
+ )
208
+ return txs
209
+
210
+ def _submit_redeem(self, txs: list[Any]) -> dict | None:
211
+ raise NotImplementedError("redeem submit not implemented")
212
+
213
+ def _redeem_batch(self, condition_ids: list[str], batch_size: int) -> list[dict]:
214
+ """
215
+ Fetch positions by condition IDs in batches, then redeem each batch.
216
+ """
217
+ if not condition_ids:
218
+ return []
219
+ user_address = self._resolve_user_address()
220
+ redeem_list = []
221
+ for batch in self._chunk_condition_ids(condition_ids, batch_size):
222
+ positions = self.fetch_positions_by_condition_ids(
223
+ user_address=user_address, condition_ids=batch
224
+ )
225
+ redeem_list.extend(self._redeem_from_positions(positions, len(batch)))
226
+ return redeem_list
227
+
228
+ def _redeem_from_positions(
229
+ self, positions: list[dict], batch_size: int
230
+ ) -> list[dict]:
231
+ """
232
+ Build and submit redeem transactions from a list of positions.
233
+ """
234
+ if not positions:
235
+ return []
236
+ positions_by_condition: dict[str, list[dict]] = {}
237
+ for pos in positions:
238
+ condition_id = pos.get("conditionId")
239
+ if not condition_id:
240
+ continue
241
+ positions_by_condition.setdefault(condition_id, []).append(pos)
242
+
243
+ redeem_list = []
244
+ error_list: list[str] = []
245
+ condition_ids = list(positions_by_condition.keys())
246
+ for batch in self._chunk_condition_ids(condition_ids, batch_size):
247
+ batch_positions = []
248
+ for condition_id in batch:
249
+ batch_positions.extend(positions_by_condition.get(condition_id, []))
250
+ try:
251
+ txs = self._build_redeem_txs_from_positions(batch_positions)
252
+ if not txs:
253
+ continue
254
+ redeem_res = self._submit_redeem(txs)
255
+ redeem_list.append(redeem_res)
256
+ for pos in batch_positions:
257
+ buy_price = pos.get("avgPrice")
258
+ size = pos.get("size")
259
+ if not buy_price or not size:
260
+ continue
261
+ volume = 1 / buy_price * (buy_price * size)
262
+ logger.info(
263
+ f"{pos.get('slug')} redeem success, volume={volume:.4f} usdc"
264
+ )
265
+ except Exception as e:
266
+ error_list.extend(batch)
267
+ logger.info(f"redeem batch error, {batch=}, error={e}")
268
+ if error_list:
269
+ logger.warning(f"error redeem condition list, {error_list}")
270
+ return redeem_list
271
+
169
272
  @classmethod
170
273
  def _get_relay_payload(cls, address: str, wallet_type: WalletType):
171
274
  return requests.get(
@@ -198,11 +301,32 @@ class BaseWeb3Service:
198
301
  else:
199
302
  raise Exception("Estimate gas error: " + str(result))
200
303
 
304
+ @classmethod
305
+ def _chunk_condition_ids(
306
+ cls, condition_ids: list[str], batch_size: int
307
+ ) -> list[list[str]]:
308
+ if batch_size <= 0:
309
+ raise Exception("batch_size must be greater than 0")
310
+ return [
311
+ condition_ids[i: i + batch_size]
312
+ for i in range(0, len(condition_ids), batch_size)
313
+ ]
314
+
201
315
  def redeem(
202
316
  self,
203
317
  condition_ids: str | list[str],
204
- ): # noqa:
205
- raise ImportError()
318
+ batch_size: int = 10,
319
+ ):
320
+ """
321
+ Redeem positions for the given condition IDs.
322
+ """
323
+ if isinstance(condition_ids, str):
324
+ condition_ids = [condition_ids]
325
+ return self._redeem_batch(condition_ids, batch_size)
206
326
 
207
- def redeem_all(self) -> list[dict] | None:
208
- raise ImportError()
327
+ def redeem_all(self, batch_size: int = 10) -> list[dict]:
328
+ """
329
+ Redeem all currently redeemable positions for the user.
330
+ """
331
+ positions = self.fetch_positions(user_address=self._resolve_user_address())
332
+ return self._redeem_from_positions(positions, batch_size)
@@ -10,8 +10,10 @@ from poly_web3.web3_service.base import BaseWeb3Service
10
10
  class EOAWeb3Service(BaseWeb3Service):
11
11
  def redeem(
12
12
  self,
13
- condition_id: str,
14
- neg_risk: bool = False,
15
- redeem_amounts: list[int] | None = None,
13
+ condition_ids: str | list[str],
14
+ batch_size: int = 10,
16
15
  ):
17
16
  raise ImportError("EOA wallet redeem not supported")
17
+
18
+ def redeem_all(self, batch_size: int = 10) -> list[dict]:
19
+ raise ImportError("EOA wallet redeem not supported")
@@ -11,23 +11,26 @@ from eth_utils import to_bytes
11
11
 
12
12
  from poly_web3.const import (
13
13
  proxy_wallet_factory_abi,
14
- CTF_ADDRESS,
15
14
  RELAYER_URL,
16
15
  PROXY_INIT_CODE_HASH,
17
16
  SUBMIT_TRANSACTION,
18
17
  STATE_MINED,
19
18
  STATE_CONFIRMED,
20
19
  STATE_FAILED,
21
- NEG_RISK_ADAPTER_ADDRESS,
22
20
  )
23
21
  from poly_web3.web3_service.base import BaseWeb3Service
24
22
  from poly_web3.signature.build import derive_proxy_wallet, create_struct_hash
25
23
  from poly_web3.signature.hash_message import hash_message
26
24
  from poly_web3.signature import secp256k1
27
- from poly_web3.log import logger
28
-
29
-
30
25
  class ProxyWeb3Service(BaseWeb3Service):
26
+ def _build_redeem_tx(self, to: str, data: str) -> dict:
27
+ return {
28
+ "to": to,
29
+ "data": data,
30
+ "value": 0,
31
+ "typeCode": 1,
32
+ }
33
+
31
34
  def build_proxy_transaction_request(self, args: dict) -> dict:
32
35
  proxy_contract_config = self.get_contract_config()["ProxyContracts"]
33
36
  to = proxy_contract_config["ProxyFactory"]
@@ -94,70 +97,7 @@ class ProxyWeb3Service(BaseWeb3Service):
94
97
 
95
98
  return function_data
96
99
 
97
- def _build_redeem_txs_from_positions(self, positions: list[dict]) -> list[dict]:
98
- neg_amounts_by_condition: dict[str, list[float]] = {}
99
- normal_conditions: set[str] = set()
100
- for pos in positions:
101
- condition_id = pos.get("conditionId")
102
- if not condition_id:
103
- continue
104
- if pos.get("negativeRisk"):
105
- idx = pos.get("outcomeIndex")
106
- size = pos.get("size")
107
- if idx is None or size is None:
108
- continue
109
- amounts = neg_amounts_by_condition.setdefault(condition_id, [0.0, 0.0])
110
- if idx not in (0, 1):
111
- raise Exception(f"negRisk outcomeIndex out of range: {idx}")
112
- amounts[idx] += size
113
- else:
114
- normal_conditions.add(condition_id)
115
- txs: list[dict] = []
116
- for condition_id, amounts in neg_amounts_by_condition.items():
117
- int_amounts = [int(amount * 1e6) for amount in amounts]
118
- txs.append(
119
- {
120
- "to": NEG_RISK_ADAPTER_ADDRESS,
121
- "data": self.build_neg_risk_redeem_tx_data(condition_id, int_amounts),
122
- "value": 0,
123
- "typeCode": 1,
124
- }
125
- )
126
- for condition_id in normal_conditions:
127
- txs.append(
128
- {
129
- "to": CTF_ADDRESS,
130
- "data": self.build_ctf_redeem_tx_data(condition_id),
131
- "value": 0,
132
- "typeCode": 1,
133
- }
134
- )
135
- return txs
136
-
137
- @classmethod
138
- def _chunk_condition_ids(
139
- cls, condition_ids: list[str], batch_size: int
140
- ) -> list[list[str]]:
141
- if batch_size <= 0:
142
- raise Exception("batch_size must be greater than 0")
143
- return [
144
- condition_ids[i: i + batch_size]
145
- for i in range(0, len(condition_ids), batch_size)
146
- ]
147
-
148
- def _redeem_batch(self, condition_ids: list[str], batch_size: int) -> list[dict]:
149
- if not condition_ids:
150
- return []
151
- user_address = self._resolve_user_address()
152
- redeem_list = []
153
- for batch in self._chunk_condition_ids(condition_ids, batch_size):
154
- positions = self.fetch_positions_by_condition_ids(
155
- user_address=user_address, condition_ids=batch
156
- )
157
- redeem_list.extend(self._redeem_from_positions(positions, len(batch)))
158
- return redeem_list
159
-
160
- def _submit_proxy_redeem(self, txs: list[dict]) -> dict:
100
+ def _submit_redeem(self, txs: list[dict]) -> dict:
161
101
  if self.clob_client is None:
162
102
  raise Exception("signer not found")
163
103
  _from = Web3.to_checksum_address(self.clob_client.get_address())
@@ -183,53 +123,3 @@ class ProxyWeb3Service(BaseWeb3Service):
183
123
  max_polls=100,
184
124
  )
185
125
  return redeem_res
186
-
187
- def _redeem_from_positions(
188
- self, positions: list[dict], batch_size: int
189
- ) -> list[dict]:
190
- if not positions:
191
- return []
192
- positions_by_condition: dict[str, list[dict]] = {}
193
- for pos in positions:
194
- condition_id = pos.get("conditionId")
195
- if not condition_id:
196
- continue
197
- positions_by_condition.setdefault(condition_id, []).append(pos)
198
-
199
- redeem_list = []
200
- condition_ids = list(positions_by_condition.keys())
201
- for batch in self._chunk_condition_ids(condition_ids, batch_size):
202
- batch_positions = []
203
- for condition_id in batch:
204
- batch_positions.extend(positions_by_condition.get(condition_id, []))
205
- try:
206
- txs = self._build_redeem_txs_from_positions(batch_positions)
207
- if not txs:
208
- continue
209
- redeem_res = self._submit_proxy_redeem(txs)
210
- redeem_list.append(redeem_res)
211
- for pos in batch_positions:
212
- buy_price = pos.get("avgPrice")
213
- size = pos.get("size")
214
- if not buy_price or not size:
215
- continue
216
- volume = 1 / buy_price * (buy_price * size)
217
- logger.info(
218
- f"{pos.get('slug')} redeem success, volume={volume:.4f} usdc"
219
- )
220
- except Exception as e:
221
- logger.info(f"redeem batch error, {batch=}, error={e}")
222
- return redeem_list
223
-
224
- def redeem(
225
- self,
226
- condition_ids: str | list[str],
227
- batch_size: int = 10,
228
- ):
229
- if isinstance(condition_ids, str):
230
- condition_ids = [condition_ids]
231
- return self._redeem_batch(condition_ids, batch_size)
232
-
233
- def redeem_all(self, batch_size: int = 10) -> list[dict]:
234
- positions = self.fetch_positions(user_address=self._resolve_user_address())
235
- return self._redeem_from_positions(positions, batch_size)
@@ -4,14 +4,21 @@
4
4
  # @Site:
5
5
  # @File: safe_service.py
6
6
  # @Software: PyCharm
7
+ from py_builder_relayer_client.models import OperationType, SafeTransaction
7
8
  from poly_web3.web3_service.base import BaseWeb3Service
8
9
 
9
10
 
10
11
  class SafeWeb3Service(BaseWeb3Service):
11
- def redeem(
12
- self,
13
- condition_id: str,
14
- neg_risk: bool = False,
15
- redeem_amounts: list[int] | None = None,
16
- ):
17
- raise ImportError("Safe wallet redeem not supported")
12
+ def _build_redeem_tx(self, to: str, data: str) -> SafeTransaction:
13
+ return SafeTransaction(
14
+ to=to,
15
+ data=data,
16
+ value="0",
17
+ operation=OperationType.Call,
18
+ )
19
+
20
+ def _submit_redeem(self, txs: list[SafeTransaction]) -> dict | None:
21
+ if self.relayer_client is None:
22
+ raise Exception("relayer_client not found")
23
+ resp = self.relayer_client.execute(txs, "redeem")
24
+ return resp.wait()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: poly-web3
3
- Version: 0.0.5
3
+ Version: 1.0.0
4
4
  Summary: Polymarket Proxy wallet redeem SDK - Execute redeem operations on Polymarket using proxy wallets
5
5
  Home-page: https://github.com/tosmart01/poly-web3
6
6
  Author: PinBar
@@ -1,18 +1,18 @@
1
1
  examples/example_redeem.py,sha256=grSaAsmjgQDKd1zpd17-cYkAv7fkqFUqDEvCr3gXJtA,2149
2
2
  poly_web3/__init__.py,sha256=O4xBBGCQoO453RdGZ9eWpjpOEOz_kFoU_TTnigbBaPs,1078
3
3
  poly_web3/const.py,sha256=UYBs1b9P2YJw1isbUzlGOxMWIqWyRiyGt9D2WmZCYfU,4959
4
- poly_web3/log.py,sha256=63XRfNTyZoQCgS5DRnZCdrMyQM1C-4bXhQ-izyvYWNI,1254
4
+ poly_web3/log.py,sha256=NPY6X7Ysw-5ds3JkA26vhfge2Dc39ptezmCWYbwT-FM,986
5
5
  poly_web3/schema.py,sha256=GTzpEy8-mqw2fCqoQKQFn6ieyhXdSnW6I9YQMKGnnhM,435
6
6
  poly_web3/signature/__init__.py,sha256=p4EwohjUtwEsFqrc7eeqY5sQa14GWc8BdR6km8c7SH8,123
7
7
  poly_web3/signature/build.py,sha256=PEyMFbE2PM2Xvz3wvmA6T6FVnU3mB7uanoEutbneFQ8,3614
8
8
  poly_web3/signature/hash_message.py,sha256=hREtjuOdLcm_IlBz1YuzyrObVdxs7Yn-ULfh09PeNmk,1319
9
9
  poly_web3/signature/secp256k1.py,sha256=IBgp0vNJyskU7ZUcWCdDttrzmJgEWH6lakB74whur_w,1500
10
10
  poly_web3/web3_service/__init__.py,sha256=HNRh06yZNYXDmGS69ISXG7_PPG9foGXLyHOdnbgYXk0,315
11
- poly_web3/web3_service/base.py,sha256=cchUlqcLiOfThDFolWHudrEX6NM5sPUYpVcAkX3xung,7136
12
- poly_web3/web3_service/eoa_service.py,sha256=_5nmTX7aG4u5QMECwqVOdaYK4bgE5yI1BsW4aT_UiFU,429
13
- poly_web3/web3_service/proxy_service.py,sha256=TX9SVboIG7X_MeIN1diSqv8MEz9k7YrR2M4Ks8-n7Og,8650
14
- poly_web3/web3_service/safe_service.py,sha256=emT7h5JmvgFKX_Rl4YP8Zj5MOLNf_4QHx8Ts1lb3WJo,432
15
- poly_web3-0.0.5.dist-info/METADATA,sha256=2dsBnvsJr3QjXO47F6FZhJUIsoiJkUNPQdpk8_bq-xY,9765
16
- poly_web3-0.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- poly_web3-0.0.5.dist-info/top_level.txt,sha256=wW40wsocHfhFgqSWWvYWrhwKGQU5IWkhyaz5J90vmHo,19
18
- poly_web3-0.0.5.dist-info/RECORD,,
11
+ poly_web3/web3_service/base.py,sha256=SwiJ3nTPHUTYH5gDwjDPRoxjZNICfYKsy6w4pWiG73U,12195
12
+ poly_web3/web3_service/eoa_service.py,sha256=mTJ0OHJvpzkrYvom1wDlA7EyV_tJFwxekpgPSXMXz0Y,515
13
+ poly_web3/web3_service/proxy_service.py,sha256=-Odgpq24dKnBmtuwQ3RRINS88zzTFEQCbtaTZ4WNLs8,4181
14
+ poly_web3/web3_service/safe_service.py,sha256=D9rHcxbU9rjvDd5k__qu8MCZ8srdQITS8g4JNkoE7Uo,776
15
+ poly_web3-1.0.0.dist-info/METADATA,sha256=7r6FXHqXFPdPz8IpQHSEoI-Qm0-ZdrgVL1k_UiZf0fc,9765
16
+ poly_web3-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ poly_web3-1.0.0.dist-info/top_level.txt,sha256=wW40wsocHfhFgqSWWvYWrhwKGQU5IWkhyaz5J90vmHo,19
18
+ poly_web3-1.0.0.dist-info/RECORD,,