poly-web3 0.0.4__py3-none-any.whl → 0.0.6__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 +67 -63
- poly_web3/__init__.py +33 -33
- poly_web3/const.py +171 -171
- poly_web3/log.py +39 -18
- poly_web3/schema.py +22 -22
- poly_web3/signature/__init__.py +6 -6
- poly_web3/signature/build.py +113 -113
- poly_web3/signature/hash_message.py +48 -48
- poly_web3/signature/secp256k1.py +57 -57
- poly_web3/web3_service/__init__.py +9 -9
- poly_web3/web3_service/base.py +332 -182
- poly_web3/web3_service/eoa_service.py +19 -17
- poly_web3/web3_service/proxy_service.py +125 -164
- poly_web3/web3_service/safe_service.py +24 -17
- {poly_web3-0.0.4.dist-info → poly_web3-0.0.6.dist-info}/METADATA +298 -302
- poly_web3-0.0.6.dist-info/RECORD +18 -0
- poly_web3-0.0.4.dist-info/RECORD +0 -18
- {poly_web3-0.0.4.dist-info → poly_web3-0.0.6.dist-info}/WHEEL +0 -0
- {poly_web3-0.0.4.dist-info → poly_web3-0.0.6.dist-info}/top_level.txt +0 -0
poly_web3/web3_service/base.py
CHANGED
|
@@ -1,182 +1,332 @@
|
|
|
1
|
-
# -*- coding = utf-8 -*-
|
|
2
|
-
# @Time: 2025-12-27 15:57:07
|
|
3
|
-
# @Author: PinBar
|
|
4
|
-
# @Site:
|
|
5
|
-
# @File: base.py
|
|
6
|
-
# @Software: PyCharm
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
1
|
+
# -*- coding = utf-8 -*-
|
|
2
|
+
# @Time: 2025-12-27 15:57:07
|
|
3
|
+
# @Author: PinBar
|
|
4
|
+
# @Site:
|
|
5
|
+
# @File: base.py
|
|
6
|
+
# @Software: PyCharm
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from py_builder_relayer_client.client import RelayClient
|
|
10
|
+
from py_clob_client.client import ClobClient
|
|
11
|
+
from web3 import Web3
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
from poly_web3.const import (
|
|
15
|
+
RPC_URL,
|
|
16
|
+
CTF_ADDRESS,
|
|
17
|
+
CTF_ABI_PAYOUT,
|
|
18
|
+
ZERO_BYTES32,
|
|
19
|
+
USDC_POLYGON,
|
|
20
|
+
CTF_ABI_REDEEM,
|
|
21
|
+
NEG_RISK_ADAPTER_ADDRESS,
|
|
22
|
+
RELAYER_URL,
|
|
23
|
+
POL,
|
|
24
|
+
AMOY,
|
|
25
|
+
GET_RELAY_PAYLOAD,
|
|
26
|
+
NEG_RISK_ADAPTER_ABI_REDEEM,
|
|
27
|
+
)
|
|
28
|
+
from poly_web3.schema import WalletType
|
|
29
|
+
from poly_web3.log import logger
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BaseWeb3Service:
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
clob_client: ClobClient = None,
|
|
36
|
+
relayer_client: RelayClient = None,
|
|
37
|
+
rpc_url: str | None = None,
|
|
38
|
+
):
|
|
39
|
+
self.relayer_client = relayer_client
|
|
40
|
+
self.clob_client: ClobClient = clob_client
|
|
41
|
+
if self.clob_client:
|
|
42
|
+
self.wallet_type: WalletType = WalletType.get_with_code(
|
|
43
|
+
self.clob_client.builder.sig_type
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
self.wallet_type = WalletType.PROXY
|
|
47
|
+
self.rpc_url = rpc_url or RPC_URL
|
|
48
|
+
self.w3: Web3 = Web3(Web3.HTTPProvider(self.rpc_url))
|
|
49
|
+
if self.wallet_type == WalletType.PROXY and relayer_client is None:
|
|
50
|
+
raise Exception("relayer_client must be provided")
|
|
51
|
+
|
|
52
|
+
def _resolve_user_address(self):
|
|
53
|
+
funder = getattr(getattr(self.clob_client, "builder", None), "funder", None)
|
|
54
|
+
if funder:
|
|
55
|
+
return funder
|
|
56
|
+
return self.clob_client.get_address()
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def fetch_positions(cls, user_address: str) -> list[dict]:
|
|
60
|
+
"""
|
|
61
|
+
Fetches current positions for a user from the official Polymarket API.
|
|
62
|
+
|
|
63
|
+
:param user_address: User wallet address (0x-prefixed, 40 hex chars)
|
|
64
|
+
:return: List of position dictionaries from the API
|
|
65
|
+
"""
|
|
66
|
+
url = "https://data-api.polymarket.com/positions"
|
|
67
|
+
params = {
|
|
68
|
+
"user": user_address,
|
|
69
|
+
"sizeThreshold": 1,
|
|
70
|
+
"limit": 100,
|
|
71
|
+
"redeemable": True,
|
|
72
|
+
"sortBy": "RESOLVING",
|
|
73
|
+
"sortDirection": "DESC",
|
|
74
|
+
}
|
|
75
|
+
try:
|
|
76
|
+
response = requests.get(url, params=params)
|
|
77
|
+
response.raise_for_status()
|
|
78
|
+
positions = response.json()
|
|
79
|
+
return [i for i in positions if i.get("percentPnl") > 0]
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.error(f"Failed to fetch positions from API: {e}")
|
|
82
|
+
return []
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def fetch_positions_by_condition_ids(
|
|
86
|
+
cls, user_address: str, condition_ids: list[str]
|
|
87
|
+
) -> list[dict]:
|
|
88
|
+
"""
|
|
89
|
+
Fetches positions for a user filtered by condition IDs.
|
|
90
|
+
|
|
91
|
+
:param user_address: User wallet address (0x-prefixed, 40 hex chars)
|
|
92
|
+
:param condition_ids: List of condition IDs to filter by
|
|
93
|
+
:return: List of position dictionaries from the API
|
|
94
|
+
"""
|
|
95
|
+
if not condition_ids:
|
|
96
|
+
return []
|
|
97
|
+
url = "https://data-api.polymarket.com/positions"
|
|
98
|
+
params = {
|
|
99
|
+
"user": user_address,
|
|
100
|
+
"market": ",".join(condition_ids),
|
|
101
|
+
"sizeThreshold": 1
|
|
102
|
+
}
|
|
103
|
+
try:
|
|
104
|
+
response = requests.get(url, params=params)
|
|
105
|
+
response.raise_for_status()
|
|
106
|
+
positions = response.json()
|
|
107
|
+
return [i for i in positions if i.get("percentPnl") > 0]
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"Failed to fetch positions from API: {e}")
|
|
110
|
+
return []
|
|
111
|
+
|
|
112
|
+
def is_condition_resolved(self, condition_id: str) -> bool:
|
|
113
|
+
ctf = self.w3.eth.contract(address=CTF_ADDRESS, abi=CTF_ABI_PAYOUT)
|
|
114
|
+
return ctf.functions.payoutDenominator(condition_id).call() > 0
|
|
115
|
+
|
|
116
|
+
def get_winning_indexes(self, condition_id: str) -> list[int]:
|
|
117
|
+
ctf = self.w3.eth.contract(address=CTF_ADDRESS, abi=CTF_ABI_PAYOUT)
|
|
118
|
+
if not self.is_condition_resolved(condition_id):
|
|
119
|
+
return []
|
|
120
|
+
outcome_count = ctf.functions.getOutcomeSlotCount(condition_id).call()
|
|
121
|
+
winners: list[int] = []
|
|
122
|
+
for i in range(outcome_count):
|
|
123
|
+
if ctf.functions.payoutNumerators(condition_id, i).call() > 0:
|
|
124
|
+
winners.append(i)
|
|
125
|
+
return winners
|
|
126
|
+
|
|
127
|
+
def get_redeemable_index_and_balance(
|
|
128
|
+
self, condition_id: str
|
|
129
|
+
) -> list[tuple]:
|
|
130
|
+
owner = self._resolve_user_address()
|
|
131
|
+
winners = self.get_winning_indexes(condition_id)
|
|
132
|
+
if not winners:
|
|
133
|
+
return []
|
|
134
|
+
ctf = self.w3.eth.contract(address=CTF_ADDRESS, abi=CTF_ABI_PAYOUT)
|
|
135
|
+
owner_checksum = Web3.to_checksum_address(owner)
|
|
136
|
+
redeemable: list[tuple] = []
|
|
137
|
+
for index in winners:
|
|
138
|
+
index_set = 1 << index
|
|
139
|
+
collection_id = ctf.functions.getCollectionId(
|
|
140
|
+
ZERO_BYTES32, condition_id, index_set
|
|
141
|
+
).call()
|
|
142
|
+
position_id = ctf.functions.getPositionId(
|
|
143
|
+
USDC_POLYGON, collection_id
|
|
144
|
+
).call()
|
|
145
|
+
balance = ctf.functions.balanceOf(owner_checksum, position_id).call()
|
|
146
|
+
if balance > 0:
|
|
147
|
+
redeemable.append((index, balance / 1000000))
|
|
148
|
+
return redeemable
|
|
149
|
+
|
|
150
|
+
def build_ctf_redeem_tx_data(self, condition_id: str) -> str:
|
|
151
|
+
ctf = self.w3.eth.contract(address=CTF_ADDRESS, abi=CTF_ABI_REDEEM)
|
|
152
|
+
# 只需要 calldata:encodeABI 即可
|
|
153
|
+
return ctf.functions.redeemPositions(
|
|
154
|
+
USDC_POLYGON,
|
|
155
|
+
ZERO_BYTES32,
|
|
156
|
+
condition_id,
|
|
157
|
+
[1, 2],
|
|
158
|
+
)._encode_transaction_data()
|
|
159
|
+
|
|
160
|
+
def build_neg_risk_redeem_tx_data(
|
|
161
|
+
self, condition_id: str, redeem_amounts: list[int]
|
|
162
|
+
) -> str:
|
|
163
|
+
nr_adapter = self.w3.eth.contract(
|
|
164
|
+
address=NEG_RISK_ADAPTER_ADDRESS, abi=NEG_RISK_ADAPTER_ABI_REDEEM
|
|
165
|
+
)
|
|
166
|
+
return nr_adapter.functions.redeemPositions(
|
|
167
|
+
condition_id,
|
|
168
|
+
redeem_amounts,
|
|
169
|
+
)._encode_transaction_data()
|
|
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
|
+
|
|
272
|
+
@classmethod
|
|
273
|
+
def _get_relay_payload(cls, address: str, wallet_type: WalletType):
|
|
274
|
+
return requests.get(
|
|
275
|
+
RELAYER_URL + GET_RELAY_PAYLOAD,
|
|
276
|
+
params={"address": address, "type": wallet_type},
|
|
277
|
+
).json()
|
|
278
|
+
|
|
279
|
+
def get_contract_config(self) -> dict:
|
|
280
|
+
if self.clob_client.chain_id == 137:
|
|
281
|
+
return POL
|
|
282
|
+
elif self.clob_client.chain_id == 80002:
|
|
283
|
+
return AMOY
|
|
284
|
+
raise Exception("Invalid network")
|
|
285
|
+
|
|
286
|
+
def estimate_gas(self, tx):
|
|
287
|
+
payload = {
|
|
288
|
+
"jsonrpc": "2.0",
|
|
289
|
+
"method": "eth_estimateGas",
|
|
290
|
+
"params": [tx],
|
|
291
|
+
"id": 1,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
response = requests.post(self.rpc_url, json=payload)
|
|
295
|
+
result = response.json()
|
|
296
|
+
|
|
297
|
+
if "result" in result:
|
|
298
|
+
# 返回的是16进制 gas 数量
|
|
299
|
+
gas_hex = result["result"]
|
|
300
|
+
return str(int(gas_hex, 16))
|
|
301
|
+
else:
|
|
302
|
+
raise Exception("Estimate gas error: " + str(result))
|
|
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
|
+
|
|
315
|
+
def redeem(
|
|
316
|
+
self,
|
|
317
|
+
condition_ids: str | list[str],
|
|
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)
|
|
326
|
+
|
|
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)
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
# -*- coding = utf-8 -*-
|
|
2
|
-
# @Time: 2025-12-27 16:01:09
|
|
3
|
-
# @Author: PinBar
|
|
4
|
-
# @Site:
|
|
5
|
-
# @File: eoa_service.py
|
|
6
|
-
# @Software: PyCharm
|
|
7
|
-
from poly_web3.web3_service.base import BaseWeb3Service
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class EOAWeb3Service(BaseWeb3Service):
|
|
11
|
-
def redeem(
|
|
12
|
-
self,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
# -*- coding = utf-8 -*-
|
|
2
|
+
# @Time: 2025-12-27 16:01:09
|
|
3
|
+
# @Author: PinBar
|
|
4
|
+
# @Site:
|
|
5
|
+
# @File: eoa_service.py
|
|
6
|
+
# @Software: PyCharm
|
|
7
|
+
from poly_web3.web3_service.base import BaseWeb3Service
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EOAWeb3Service(BaseWeb3Service):
|
|
11
|
+
def redeem(
|
|
12
|
+
self,
|
|
13
|
+
condition_ids: str | list[str],
|
|
14
|
+
batch_size: int = 10,
|
|
15
|
+
):
|
|
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")
|