poly-web3 0.0.1__py3-none-any.whl → 0.0.3__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.
- examples/example_redeem.py +11 -2
- poly_web3/__init__.py +4 -2
- poly_web3/const.py +1 -1
- poly_web3/log.py +18 -0
- poly_web3/web3_service/base.py +50 -11
- poly_web3/web3_service/proxy_service.py +35 -4
- {poly_web3-0.0.1.dist-info → poly_web3-0.0.3.dist-info}/METADATA +30 -3
- {poly_web3-0.0.1.dist-info → poly_web3-0.0.3.dist-info}/RECORD +10 -9
- {poly_web3-0.0.1.dist-info → poly_web3-0.0.3.dist-info}/WHEEL +0 -0
- {poly_web3-0.0.1.dist-info → poly_web3-0.0.3.dist-info}/top_level.txt +0 -0
examples/example_redeem.py
CHANGED
|
@@ -41,11 +41,20 @@ if __name__ == "__main__":
|
|
|
41
41
|
)
|
|
42
42
|
),
|
|
43
43
|
)
|
|
44
|
-
condition_id = "
|
|
45
|
-
service = PolyWeb3Service(
|
|
44
|
+
condition_id = "0x38124532c68bf16aa5800433118463acdbf09152237b45bb9e11bd4b73e0d1c4"
|
|
45
|
+
service = PolyWeb3Service(
|
|
46
|
+
clob_client=client,
|
|
47
|
+
relayer_client=relayer_client,
|
|
48
|
+
rpc_url="https://polygon-bor.publicnode.com",
|
|
49
|
+
)
|
|
50
|
+
# Redeem by condition_id
|
|
46
51
|
redeem = service.redeem(condition_id=condition_id)
|
|
47
52
|
print(redeem)
|
|
48
53
|
|
|
54
|
+
# Redeem all positions that are currently redeemable (returns list or None)
|
|
55
|
+
redeem_all = service.redeem_all()
|
|
56
|
+
print(redeem_all)
|
|
57
|
+
|
|
49
58
|
# Optional - Query operations (可选操作,用于查询)
|
|
50
59
|
# can_redeem = service.is_condition_resolved(condition_id)
|
|
51
60
|
# redeem_balance = service.get_redeemable_index_and_balance(
|
poly_web3/__init__.py
CHANGED
|
@@ -16,7 +16,9 @@ from poly_web3.web3_service import SafeWeb3Service, EOAWeb3Service, ProxyWeb3Ser
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def PolyWeb3Service(
|
|
19
|
-
clob_client: ClobClient,
|
|
19
|
+
clob_client: ClobClient,
|
|
20
|
+
relayer_client: RelayClient = None,
|
|
21
|
+
rpc_url: str | None = None,
|
|
20
22
|
) -> Union[SafeWeb3Service, EOAWeb3Service, ProxyWeb3Service]: # noqa
|
|
21
23
|
services = {
|
|
22
24
|
WalletType.EOA: EOAWeb3Service,
|
|
@@ -26,6 +28,6 @@ def PolyWeb3Service(
|
|
|
26
28
|
|
|
27
29
|
wallet_type = WalletType.get_with_code(clob_client.builder.sig_type)
|
|
28
30
|
if service := services.get(wallet_type):
|
|
29
|
-
return service(clob_client, relayer_client)
|
|
31
|
+
return service(clob_client, relayer_client, rpc_url=rpc_url)
|
|
30
32
|
else:
|
|
31
33
|
raise Exception(f"Unknown wallet type: {wallet_type}")
|
poly_web3/const.py
CHANGED
|
@@ -12,7 +12,7 @@ GET_TRANSACTION = "/transaction"
|
|
|
12
12
|
GET_TRANSACTIONS = "/transactions"
|
|
13
13
|
SUBMIT_TRANSACTION = "/submit"
|
|
14
14
|
GET_DEPLOYED = "/deployed"
|
|
15
|
-
RPC_URL = "https://polygon-rpc.com"
|
|
15
|
+
RPC_URL = "https://polygon-bor.publicnode.com" # "https://polygon-rpc.com"
|
|
16
16
|
RELAYER_URL = "https://relayer-v2.polymarket.com"
|
|
17
17
|
|
|
18
18
|
STATE_NEW = ("STATE_NEW",)
|
poly_web3/log.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- coding = utf-8 -*-
|
|
2
|
+
# @Time: 2026-01-13 14:40:20
|
|
3
|
+
# @Author: PinBar
|
|
4
|
+
# @Site:
|
|
5
|
+
# @File: log.py
|
|
6
|
+
# @Software: PyCharm
|
|
7
|
+
import sys
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("poly_web3")
|
|
11
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
12
|
+
formatter = logging.Formatter(
|
|
13
|
+
'%(asctime)s | %(levelname)s | %(name)s | %(filename)s:%(lineno)d %(message)s',
|
|
14
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
15
|
+
)
|
|
16
|
+
handler.setFormatter(formatter)
|
|
17
|
+
logger.addHandler(handler)
|
|
18
|
+
logger.setLevel(logging.INFO)
|
poly_web3/web3_service/base.py
CHANGED
|
@@ -24,13 +24,15 @@ from poly_web3.const import (
|
|
|
24
24
|
NEG_RISK_ADAPTER_ABI_REDEEM,
|
|
25
25
|
)
|
|
26
26
|
from poly_web3.schema import WalletType
|
|
27
|
+
from poly_web3.log import logger
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class BaseWeb3Service:
|
|
30
31
|
def __init__(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
self,
|
|
33
|
+
clob_client: ClobClient = None,
|
|
34
|
+
relayer_client: RelayClient = None,
|
|
35
|
+
rpc_url: str | None = None,
|
|
34
36
|
):
|
|
35
37
|
self.relayer_client = relayer_client
|
|
36
38
|
self.clob_client: ClobClient = clob_client
|
|
@@ -40,10 +42,43 @@ class BaseWeb3Service:
|
|
|
40
42
|
)
|
|
41
43
|
else:
|
|
42
44
|
self.wallet_type = WalletType.PROXY
|
|
43
|
-
self.
|
|
45
|
+
self.rpc_url = rpc_url or RPC_URL
|
|
46
|
+
self.w3: Web3 = Web3(Web3.HTTPProvider(self.rpc_url))
|
|
44
47
|
if self.wallet_type == WalletType.PROXY and relayer_client is None:
|
|
45
48
|
raise Exception("relayer_client must be provided")
|
|
46
49
|
|
|
50
|
+
def _resolve_user_address(self):
|
|
51
|
+
funder = getattr(getattr(self.clob_client, "builder", None), "funder", None)
|
|
52
|
+
if funder:
|
|
53
|
+
return funder
|
|
54
|
+
return self.clob_client.get_address()
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def fetch_positions(cls, user_address: str) -> list[dict]:
|
|
58
|
+
"""
|
|
59
|
+
Fetches current positions for a user from the official Polymarket API.
|
|
60
|
+
|
|
61
|
+
:param user_address: User wallet address (0x-prefixed, 40 hex chars)
|
|
62
|
+
:return: List of position dictionaries from the API
|
|
63
|
+
"""
|
|
64
|
+
url = "https://data-api.polymarket.com/positions"
|
|
65
|
+
params = {
|
|
66
|
+
"user": user_address,
|
|
67
|
+
"sizeThreshold": 1,
|
|
68
|
+
"limit": 100,
|
|
69
|
+
"redeemable": True,
|
|
70
|
+
"sortBy": "RESOLVING",
|
|
71
|
+
"sortDirection": "DESC",
|
|
72
|
+
}
|
|
73
|
+
try:
|
|
74
|
+
response = requests.get(url, params=params)
|
|
75
|
+
response.raise_for_status()
|
|
76
|
+
positions = response.json()
|
|
77
|
+
return [i for i in positions if i.get("percentPnl") > 0]
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(f"Failed to fetch positions from API: {e}")
|
|
80
|
+
return []
|
|
81
|
+
|
|
47
82
|
def is_condition_resolved(self, condition_id: str) -> bool:
|
|
48
83
|
ctf = self.w3.eth.contract(address=CTF_ADDRESS, abi=CTF_ABI_PAYOUT)
|
|
49
84
|
return ctf.functions.payoutDenominator(condition_id).call() > 0
|
|
@@ -60,8 +95,9 @@ class BaseWeb3Service:
|
|
|
60
95
|
return winners
|
|
61
96
|
|
|
62
97
|
def get_redeemable_index_and_balance(
|
|
63
|
-
|
|
98
|
+
self, condition_id: str
|
|
64
99
|
) -> list[tuple]:
|
|
100
|
+
owner = self._resolve_user_address()
|
|
65
101
|
winners = self.get_winning_indexes(condition_id)
|
|
66
102
|
if not winners:
|
|
67
103
|
return []
|
|
@@ -92,7 +128,7 @@ class BaseWeb3Service:
|
|
|
92
128
|
)._encode_transaction_data()
|
|
93
129
|
|
|
94
130
|
def build_neg_risk_redeem_tx_data(
|
|
95
|
-
|
|
131
|
+
self, condition_id: str, redeem_amounts: list[int]
|
|
96
132
|
) -> str:
|
|
97
133
|
nr_adapter = self.w3.eth.contract(
|
|
98
134
|
address=NEG_RISK_ADAPTER_ADDRESS, abi=NEG_RISK_ADAPTER_ABI_REDEEM
|
|
@@ -124,7 +160,7 @@ class BaseWeb3Service:
|
|
|
124
160
|
"id": 1,
|
|
125
161
|
}
|
|
126
162
|
|
|
127
|
-
response = requests.post(
|
|
163
|
+
response = requests.post(self.rpc_url, json=payload)
|
|
128
164
|
result = response.json()
|
|
129
165
|
|
|
130
166
|
if "result" in result:
|
|
@@ -135,9 +171,12 @@ class BaseWeb3Service:
|
|
|
135
171
|
raise Exception("Estimate gas error: " + str(result))
|
|
136
172
|
|
|
137
173
|
def redeem(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
174
|
+
self,
|
|
175
|
+
condition_id: str,
|
|
176
|
+
neg_risk: bool = False,
|
|
177
|
+
redeem_amounts: list[int] | None = None,
|
|
142
178
|
): # noqa:
|
|
143
179
|
raise ImportError()
|
|
180
|
+
|
|
181
|
+
def redeem_all(self) -> list[dict] | None:
|
|
182
|
+
raise ImportError()
|
|
@@ -24,6 +24,7 @@ from poly_web3.web3_service.base import BaseWeb3Service
|
|
|
24
24
|
from poly_web3.signature.build import derive_proxy_wallet, create_struct_hash
|
|
25
25
|
from poly_web3.signature.hash_message import hash_message
|
|
26
26
|
from poly_web3.signature import secp256k1
|
|
27
|
+
from poly_web3.log import logger
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class ProxyWeb3Service(BaseWeb3Service):
|
|
@@ -94,10 +95,10 @@ class ProxyWeb3Service(BaseWeb3Service):
|
|
|
94
95
|
return function_data
|
|
95
96
|
|
|
96
97
|
def redeem(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
self,
|
|
99
|
+
condition_id: str,
|
|
100
|
+
neg_risk: bool = False,
|
|
101
|
+
redeem_amounts: list[int] | None = None,
|
|
101
102
|
):
|
|
102
103
|
if neg_risk:
|
|
103
104
|
if redeem_amounts is None or len(redeem_amounts) != 2:
|
|
@@ -133,3 +134,33 @@ class ProxyWeb3Service(BaseWeb3Service):
|
|
|
133
134
|
max_polls=100,
|
|
134
135
|
)
|
|
135
136
|
return redeem_res
|
|
137
|
+
|
|
138
|
+
def redeem_all(self) -> list[dict]:
|
|
139
|
+
logger.info("test")
|
|
140
|
+
return
|
|
141
|
+
positions = self.fetch_positions(user_address=self._resolve_user_address())
|
|
142
|
+
if not positions:
|
|
143
|
+
return []
|
|
144
|
+
redeem_list = []
|
|
145
|
+
for pos in positions:
|
|
146
|
+
condition_id = pos.get("conditionId")
|
|
147
|
+
try:
|
|
148
|
+
can_redeem = self.get_redeemable_index_and_balance(condition_id)
|
|
149
|
+
if not can_redeem:
|
|
150
|
+
continue
|
|
151
|
+
if pos.get("negativeRisk"):
|
|
152
|
+
amounts = [0, 0]
|
|
153
|
+
amounts[pos.get("outcomeIndex")] = pos.get("size")
|
|
154
|
+
int_amounts = [int(amount * 1e6) for amount in amounts]
|
|
155
|
+
redeem_res = self.redeem(condition_id=condition_id, redeem_amounts=int_amounts, neg_risk=True)
|
|
156
|
+
else:
|
|
157
|
+
redeem_res = self.redeem(condition_id=condition_id)
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error(f"redeem error, {condition_id=}, error={e}")
|
|
160
|
+
else:
|
|
161
|
+
redeem_list.append(redeem_res)
|
|
162
|
+
buy_price = pos.get("avgPrice")
|
|
163
|
+
size = pos.get("size")
|
|
164
|
+
volume = 1 / buy_price * (buy_price * size)
|
|
165
|
+
logger.info(f"slug={pos.get('slug')} redeem success, volume={volume:.4f} usdc")
|
|
166
|
+
return redeem_list
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: poly-web3
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
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
|
|
@@ -27,7 +27,7 @@ Dynamic: requires-python
|
|
|
27
27
|
|
|
28
28
|
# poly-web3
|
|
29
29
|
|
|
30
|
-
Python SDK for Polymarket Proxy wallet redeem operations. Supports executing Conditional Token Fund (CTF) redeem operations on Polymarket through proxy wallets.
|
|
30
|
+
Python SDK for Polymarket Proxy wallet redeem operations. Supports executing Conditional Token Fund (CTF) redeem operations on Polymarket through proxy wallets, Free gas.
|
|
31
31
|
|
|
32
32
|
[English](README.md) | [中文](README.zh.md)
|
|
33
33
|
|
|
@@ -39,6 +39,11 @@ This project is a Python rewrite of Polymarket's official TypeScript implementat
|
|
|
39
39
|
- This project **only implements the official redeem functionality**, focusing on Conditional Token Fund (CTF) redeem operations
|
|
40
40
|
- Other features (such as trading, order placement, etc.) are not within the scope of this project
|
|
41
41
|
|
|
42
|
+
**Some Polymarket-related redeem or write operations implemented in this project depend on access granted through Polymarket's Builder program. To perform real redeem operations against Polymarket, you must apply for and obtain a Builder key/credentials via Polymarket's official Builder application process. After approval you will receive the credentials required to use the Builder API—only then will the redeem flows in this repository work against the live service. For local development or automated tests, use mocks or testnet setups instead of real keys to avoid exposing production credentials.**
|
|
43
|
+
|
|
44
|
+
Reference:
|
|
45
|
+
- Polymarket Builders — Introduction: https://docs.polymarket.com/developers/builders/builder-intro
|
|
46
|
+
|
|
42
47
|
**Current Status:**
|
|
43
48
|
- ✅ **Proxy Wallet** - Fully supported for redeem functionality
|
|
44
49
|
- 🚧 **Safe Wallet** - Under development
|
|
@@ -120,12 +125,20 @@ relayer_client = RelayClient(
|
|
|
120
125
|
)
|
|
121
126
|
|
|
122
127
|
# Create service instance
|
|
123
|
-
service = PolyWeb3Service(
|
|
128
|
+
service = PolyWeb3Service(
|
|
129
|
+
clob_client=client,
|
|
130
|
+
relayer_client=relayer_client,
|
|
131
|
+
rpc_url="https://polygon-bor.publicnode.com", # optional
|
|
132
|
+
)
|
|
124
133
|
|
|
125
134
|
# Execute redeem operation
|
|
126
135
|
condition_id = "0xc3df016175463c44f9c9f98bddaa3bf3daaabb14b069fb7869621cffe73ddd1c"
|
|
127
136
|
redeem_result = service.redeem(condition_id=condition_id)
|
|
128
137
|
print(f"Redeem result: {redeem_result}")
|
|
138
|
+
|
|
139
|
+
# Redeem all positions that are currently redeemable
|
|
140
|
+
redeem_all_result = service.redeem_all()
|
|
141
|
+
print(f"Redeem all result: {redeem_all_result}")
|
|
129
142
|
```
|
|
130
143
|
|
|
131
144
|
### Optional - Query Operations
|
|
@@ -211,6 +224,20 @@ result = service.redeem(
|
|
|
211
224
|
)
|
|
212
225
|
```
|
|
213
226
|
|
|
227
|
+
##### `redeem_all() -> list[dict] | None`
|
|
228
|
+
|
|
229
|
+
Redeem all positions that are currently redeemable for the authenticated account.
|
|
230
|
+
|
|
231
|
+
**Returns:**
|
|
232
|
+
- `list[dict] | None`: List of redeem results, or `None` if no redeemable positions
|
|
233
|
+
|
|
234
|
+
**Examples:**
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
# Redeem all positions that can be redeemed
|
|
238
|
+
service.redeem_all()
|
|
239
|
+
```
|
|
240
|
+
|
|
214
241
|
## Project Structure
|
|
215
242
|
|
|
216
243
|
```
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
examples/example_redeem.py,sha256=
|
|
2
|
-
poly_web3/__init__.py,sha256=
|
|
3
|
-
poly_web3/const.py,sha256=
|
|
1
|
+
examples/example_redeem.py,sha256=jBTHM_qHbTMDwBfeD_M2oc9sDXbd-8GseBUHxpQ0M3Q,2063
|
|
2
|
+
poly_web3/__init__.py,sha256=9SfcOZtFSMErtmGG7rTtd5L2xb3pg9I7e9ctto3u3qE,1111
|
|
3
|
+
poly_web3/const.py,sha256=mmNbN4rUMHjH_65aIGoVIB1c7My_8dB1uL0eiH36f_I,5130
|
|
4
|
+
poly_web3/log.py,sha256=UaMJkBntAuWFO4yLFVQRUQHqFTiRLXnexdaBrWIGkOI,487
|
|
4
5
|
poly_web3/schema.py,sha256=sxZOzYglLLyQLwbSlQGpQzQ3IB7R059XQzJAO9T5frg,457
|
|
5
6
|
poly_web3/signature/__init__.py,sha256=g5z1cPO_o-LPdvKb3VQu4aEcpJMMjJW-a9h9Mo6qczo,129
|
|
6
7
|
poly_web3/signature/build.py,sha256=iSOzrMQOte6NWA981gQYPkCtRSoXhlFEcmV0bI7XMrA,3727
|
|
7
8
|
poly_web3/signature/hash_message.py,sha256=IYCtqdV9HKE6bG6DZXt3wedrrXUn8KQ4RBgcqZq45aI,1367
|
|
8
9
|
poly_web3/signature/secp256k1.py,sha256=AGpItsDFTAJR9ZpAfyC26llKaNmIfpD8v6HM3qqMAIA,1557
|
|
9
10
|
poly_web3/web3_service/__init__.py,sha256=pxW7XqlSDZeoYc3ohwCcdtd_9aK3gJBIQ2ANXwHXUcE,324
|
|
10
|
-
poly_web3/web3_service/base.py,sha256=
|
|
11
|
+
poly_web3/web3_service/base.py,sha256=7GIDYseLFuGfpfxmrfePNwywlMOUc9DGoj1U1XNpLIA,6377
|
|
11
12
|
poly_web3/web3_service/eoa_service.py,sha256=MmwaAmpD_WLk4M-2w99_F7iHTZ-pKtAnPw4m5x2h6CA,446
|
|
12
|
-
poly_web3/web3_service/proxy_service.py,sha256=
|
|
13
|
+
poly_web3/web3_service/proxy_service.py,sha256=L_bNWzKAKv1nQ3cBn5AxbuP6Hv3D3SbqMZgp19DtNsw,6204
|
|
13
14
|
poly_web3/web3_service/safe_service.py,sha256=KPmToVjqxFqFhFrDbY0hfR3Z00Alu9w31QKMyyluzX8,449
|
|
14
|
-
poly_web3-0.0.
|
|
15
|
-
poly_web3-0.0.
|
|
16
|
-
poly_web3-0.0.
|
|
17
|
-
poly_web3-0.0.
|
|
15
|
+
poly_web3-0.0.3.dist-info/METADATA,sha256=wiZzR2j8aDtjj3meYr1kXmZKaRtlojHEe6LSrRptd0Y,10212
|
|
16
|
+
poly_web3-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
+
poly_web3-0.0.3.dist-info/top_level.txt,sha256=wW40wsocHfhFgqSWWvYWrhwKGQU5IWkhyaz5J90vmHo,19
|
|
18
|
+
poly_web3-0.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|