polymarket-apis 0.2.2__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.

Potentially problematic release.


This version of polymarket-apis might be problematic. Click here for more details.

Files changed (40) hide show
  1. polymarket_apis/__init__.py +2 -0
  2. polymarket_apis/clients/__init__.py +0 -0
  3. polymarket_apis/clients/clob_client.py +730 -0
  4. polymarket_apis/clients/data_client.py +234 -0
  5. polymarket_apis/clients/gamma_client.py +311 -0
  6. polymarket_apis/clients/web3_client.py +261 -0
  7. polymarket_apis/clients/websockets_client.py +131 -0
  8. polymarket_apis/types/__init__.py +0 -0
  9. polymarket_apis/types/clob_types.py +494 -0
  10. polymarket_apis/types/common.py +49 -0
  11. polymarket_apis/types/data_types.py +161 -0
  12. polymarket_apis/types/gamma_types.py +313 -0
  13. polymarket_apis/types/websockets_types.py +191 -0
  14. polymarket_apis/utilities/__init__.py +0 -0
  15. polymarket_apis/utilities/config.py +36 -0
  16. polymarket_apis/utilities/constants.py +26 -0
  17. polymarket_apis/utilities/endpoints.py +37 -0
  18. polymarket_apis/utilities/exceptions.py +11 -0
  19. polymarket_apis/utilities/headers.py +54 -0
  20. polymarket_apis/utilities/order_builder/__init__.py +0 -0
  21. polymarket_apis/utilities/order_builder/builder.py +240 -0
  22. polymarket_apis/utilities/order_builder/helpers.py +61 -0
  23. polymarket_apis/utilities/signing/__init__.py +0 -0
  24. polymarket_apis/utilities/signing/eip712.py +28 -0
  25. polymarket_apis/utilities/signing/hmac.py +20 -0
  26. polymarket_apis/utilities/signing/model.py +8 -0
  27. polymarket_apis/utilities/signing/signer.py +25 -0
  28. polymarket_apis/utilities/web3/__init__.py +0 -0
  29. polymarket_apis/utilities/web3/abis/CTFExchange.json +1851 -0
  30. polymarket_apis/utilities/web3/abis/ConditionalTokens.json +705 -0
  31. polymarket_apis/utilities/web3/abis/NegRiskAdapter.json +999 -0
  32. polymarket_apis/utilities/web3/abis/NegRiskCtfExchange.json +1856 -0
  33. polymarket_apis/utilities/web3/abis/ProxyWalletFactory.json +319 -0
  34. polymarket_apis/utilities/web3/abis/UChildERC20Proxy.json +1438 -0
  35. polymarket_apis/utilities/web3/abis/__init__.py +0 -0
  36. polymarket_apis/utilities/web3/abis/custom_contract_errors.py +31 -0
  37. polymarket_apis/utilities/web3/helpers.py +8 -0
  38. polymarket_apis-0.2.2.dist-info/METADATA +18 -0
  39. polymarket_apis-0.2.2.dist-info/RECORD +40 -0
  40. polymarket_apis-0.2.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,494 @@
1
+ import logging
2
+ from datetime import datetime
3
+ from enum import Enum
4
+ from typing import Any, Literal, Optional, TypeVar, Union
5
+
6
+ from py_order_utils.model import SignedOrder
7
+ from pydantic import (
8
+ BaseModel,
9
+ ConfigDict,
10
+ Field,
11
+ RootModel,
12
+ ValidationError,
13
+ ValidationInfo,
14
+ ValidatorFunctionWrapHandler,
15
+ field_serializer,
16
+ field_validator,
17
+ )
18
+
19
+ from ..types.common import EthAddress, Keccak256, TimeseriesPoint
20
+ from ..utilities.constants import ZERO_ADDRESS
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ class ApiCreds(BaseModel):
25
+ api_key: str = Field(alias="apiKey")
26
+ secret: str
27
+ passphrase: str
28
+
29
+
30
+ class RequestArgs(BaseModel):
31
+ method: Literal["GET", "POST", "DELETE"]
32
+ request_path: str
33
+ body: Any = None
34
+
35
+
36
+ class TokenValue(BaseModel):
37
+ token_id: str
38
+ value: float
39
+
40
+
41
+ class Midpoint(TokenValue):
42
+ pass
43
+
44
+
45
+ class Spread(TokenValue):
46
+ pass
47
+
48
+
49
+ class TokenValueDict(RootModel):
50
+ root: dict[str, float]
51
+
52
+
53
+ class BookParams(BaseModel):
54
+ token_id: str
55
+ side: Literal["BUY", "SELL"]
56
+
57
+
58
+ class Price(BookParams):
59
+ price: float
60
+
61
+
62
+ class BidAsk(BaseModel):
63
+ BUY: Optional[float] = None # Price buyers are willing to pay
64
+ SELL: Optional[float] = None # Price sellers are willing to accept
65
+
66
+
67
+ class TokenBidAsk(BidAsk):
68
+ token_id: str
69
+
70
+
71
+ class TokenBidAskDict(RootModel):
72
+ root: dict[str, BidAsk]
73
+
74
+
75
+ class Token(BaseModel):
76
+ token_id: str
77
+ outcome: str
78
+ price: float
79
+ winner: Optional[bool] = None
80
+
81
+
82
+ class PriceHistory(BaseModel):
83
+ token_id: str
84
+ history: list[TimeseriesPoint]
85
+
86
+
87
+ T = TypeVar("T")
88
+
89
+
90
+ class PaginatedResponse[T](BaseModel):
91
+ data: list[T]
92
+ next_cursor: str
93
+ limit: int
94
+ count: int
95
+
96
+
97
+ class RewardRate(BaseModel):
98
+ asset_address: str
99
+ rewards_daily_rate: float
100
+
101
+
102
+ class RewardConfig(BaseModel):
103
+ asset_address: str
104
+ rewards_daily_rate: float = Field(alias="rate_per_day")
105
+
106
+ start_date: datetime
107
+ end_date: datetime
108
+
109
+ reward_id: Optional[str] = Field(None, alias="id")
110
+ total_rewards: float
111
+ total_days: Optional[int] = None
112
+
113
+ @field_validator("reward_id", mode="before")
114
+ def convert_id_to_str(cls, v):
115
+ if isinstance(v, int):
116
+ return str(v)
117
+ return v
118
+
119
+
120
+ class Rewards(BaseModel):
121
+ rates: Optional[list[RewardRate]]
122
+ rewards_min_size: int = Field(alias="min_size")
123
+ rewards_max_spread: float = Field(alias="max_spread")
124
+
125
+ class EarnedReward(BaseModel):
126
+ asset_address: EthAddress
127
+ earnings: float
128
+ asset_rate: float
129
+
130
+ class DailyEarnedReward(BaseModel):
131
+ date: datetime
132
+ asset_address: EthAddress
133
+ maker_address: EthAddress
134
+ earnings: float
135
+ asset_rate: float
136
+
137
+ class PolymarketRewardItem(BaseModel):
138
+ market_id: str
139
+ condition_id: Keccak256
140
+ question: str
141
+ market_slug: str
142
+ market_description: str
143
+ event_slug: str
144
+ image: str
145
+ maker_address: EthAddress
146
+ tokens: list[Token]
147
+ rewards_config: list[RewardConfig]
148
+ earnings: list[EarnedReward]
149
+ rewards_max_spread: float
150
+ rewards_min_size: float
151
+ earning_percentage: float
152
+ spread: float
153
+ market_competitiveness: float
154
+
155
+
156
+ class RewardsMarket(BaseModel):
157
+ condition_id: Keccak256
158
+ question: str
159
+ market_slug: str
160
+ event_slug: str
161
+ image: str
162
+ tokens: list[Token]
163
+ rewards_config: list[RewardConfig]
164
+ rewards_max_spread: float
165
+ rewards_min_size: int
166
+ market_competitiveness: float
167
+
168
+ class ClobMarket(BaseModel):
169
+ # Core market information
170
+ token_ids: list[Token] = Field(alias="tokens")
171
+ condition_id: Keccak256
172
+ question_id: Keccak256
173
+ question: str
174
+ description: str
175
+ market_slug: str
176
+ end_date_iso: Optional[datetime]
177
+ game_start_time: Optional[datetime] = None
178
+ seconds_delay: int
179
+
180
+ # Order book settings
181
+ enable_order_book: bool
182
+ accepting_orders: bool
183
+ accepting_order_timestamp: Optional[datetime]
184
+ minimum_order_size: float
185
+ minimum_tick_size: float
186
+
187
+ # Status flags
188
+ active: bool
189
+ closed: bool
190
+ archived: bool
191
+
192
+ # Negative risk settings
193
+ neg_risk: bool
194
+ neg_risk_market_id: Keccak256
195
+ neg_risk_request_id: Keccak256
196
+
197
+ # Fee structure
198
+ fpmm: str
199
+ maker_base_fee: float
200
+ taker_base_fee: float
201
+
202
+ # Features
203
+ notifications_enabled: bool
204
+ is_50_50_outcome: bool
205
+
206
+ # Visual representation
207
+ icon: str
208
+ image: str
209
+
210
+ # Rewards configuration
211
+ rewards: Optional[Rewards]
212
+
213
+ # Tags
214
+ tags: Optional[list[str]]
215
+
216
+ @field_validator("neg_risk_market_id", "neg_risk_request_id", mode="wrap")
217
+ @classmethod
218
+ def validate_neg_risk_fields(cls, value: str, handler: ValidatorFunctionWrapHandler, info: ValidationInfo) -> Optional[str]:
219
+ try:
220
+ return handler(value)
221
+ except ValidationError as e:
222
+ neg_risk = info.data.get("neg_risk", False)
223
+ active = info.data.get("active", False)
224
+ if not neg_risk and value == "":
225
+ return value
226
+ if not active:
227
+ return value
228
+ if neg_risk and value == "":
229
+ for _ in e.errors():
230
+ msg = ("Poorly setup market: negative risk is True, but either neg_risk_market_id or neg_risk_request_id is missing. "
231
+ f" Question: {info.data.get("question")}; Market slug: {info.data.get('market_slug')} \n")
232
+ logger.warning(msg)
233
+ @field_validator("condition_id", "question_id", mode="wrap")
234
+ @classmethod
235
+ def validate_condition_fields(cls, value: str, handler: ValidatorFunctionWrapHandler, info: ValidationInfo) -> str:
236
+ try:
237
+ return handler(value)
238
+ except ValueError:
239
+ active = info.data.get("active", False)
240
+ if not active:
241
+ return value
242
+ raise
243
+
244
+ class OpenOrder(BaseModel):
245
+ order_id: Keccak256 = Field(alias="id")
246
+ status: str
247
+ owner: str
248
+ maker_address: str
249
+ condition_id: str = Field(alias="market")
250
+ token_id: str = Field(alias="asset_id")
251
+ side: Literal["BUY", "SELL"]
252
+ original_size: float
253
+ size_matched: float
254
+ price: float
255
+ outcome: str
256
+ expiration: datetime
257
+ order_type: Literal["GTC", "FOK", "GTD"]
258
+ associate_trades: list[str]
259
+ created_at: datetime
260
+
261
+ class MakerOrder(BaseModel):
262
+ token_id: str = Field(alias="asset_id")
263
+ order_id: Keccak256
264
+ maker_address: EthAddress
265
+ owner: str
266
+ matched_amount: float
267
+ price: float
268
+ outcome: str
269
+ fee_rate_bps: float
270
+
271
+ class PolygonTrade(BaseModel):
272
+ trade_id: str = Field(alias="id")
273
+ taker_order_id: Keccak256
274
+ condition_id: Keccak256 = Field(alias="market")
275
+ id: str
276
+ side: Literal["BUY", "SELL"]
277
+ size: float
278
+ fee_rate_bps: float
279
+ price: float
280
+ status: str # change to literals MINED, CONFIRMED
281
+ match_time: datetime
282
+ last_update: datetime
283
+ outcome: str
284
+ bucket_index: int
285
+ owner: str
286
+ maker_address: EthAddress
287
+ transaction_hash: Keccak256
288
+ maker_orders: list[MakerOrder]
289
+ trader_side: Literal["TAKER", "MAKER"]
290
+
291
+ class TradeParams(BaseModel):
292
+ id: Optional[str] = None
293
+ maker_address: Optional[str] = None
294
+ market: Optional[str] = None
295
+ asset_id: Optional[str] = None
296
+ before: Optional[int] = None
297
+ after: Optional[int] = None
298
+
299
+
300
+ class OpenOrderParams(BaseModel):
301
+ order_id: Optional[str] = None
302
+ condition_id: Optional[str] = None
303
+ token_id: Optional[str] = None
304
+
305
+
306
+ class DropNotificationParams(BaseModel):
307
+ ids: Optional[list[str]]= None
308
+
309
+
310
+ class OrderSummary(BaseModel):
311
+ price: Optional[float] = None
312
+ size: Optional[float] = None
313
+
314
+ class PriceLevel(OrderSummary):
315
+ side: Literal["BUY", "SELL"]
316
+
317
+
318
+ class OrderBookSummary(BaseModel):
319
+ condition_id: Optional[Keccak256] = Field(None, alias="market")
320
+ token_id: Optional[str] = Field(None, alias="asset_id")
321
+ timestamp: Optional[datetime] = None
322
+ hash: Optional[str] = None
323
+ bids: Optional[list[OrderSummary]] = None
324
+ asks: Optional[list[OrderSummary]] = None
325
+
326
+ @field_serializer("bids", "asks")
327
+ def serialize_sizes(self, orders: list[OrderSummary]) -> list[dict]:
328
+ return [
329
+ {
330
+ "price": f"{order.price:.3f}".rstrip("0").rstrip("."),
331
+ "size": f"{order.size:.2f}".rstrip("0").rstrip("."),
332
+ }
333
+ for order in orders
334
+ ]
335
+
336
+ @field_serializer("timestamp")
337
+ def serialize_timestamp(self, ts: datetime) -> str:
338
+ # Convert to millisecond timestamp string without decimal places
339
+ return str(int(ts.timestamp() * 1000))
340
+
341
+
342
+ class AssetType(str, Enum):
343
+ COLLATERAL = "COLLATERAL"
344
+ CONDITIONAL = "CONDITIONAL"
345
+
346
+
347
+ class BalanceAllowanceParams(BaseModel):
348
+ asset_type: Optional[AssetType] = None
349
+ token_id: Optional[str] = None
350
+ signature_type: int = -1
351
+
352
+
353
+ class OrderType(str, Enum):
354
+ GTC = "GTC" # Good Till Cancelled
355
+ GTD = "GTD" # Good Till Date
356
+ FOK = "FOK" # Fill or Kill
357
+ FAK = "FAK" # Fill and Kill
358
+
359
+
360
+ TickSize = Literal["0.1", "0.01", "0.001", "0.0001"]
361
+
362
+
363
+ class CreateOrderOptions(BaseModel):
364
+ tick_size: TickSize
365
+ neg_risk: bool
366
+
367
+
368
+ class PartialCreateOrderOptions(BaseModel):
369
+ tick_size: Optional[TickSize] = None
370
+ neg_risk: Optional[bool] = None
371
+
372
+
373
+ class RoundConfig(BaseModel):
374
+ price: int
375
+ size: int
376
+ amount: int
377
+
378
+ class OrderArgs(BaseModel):
379
+ token_id: str
380
+ """
381
+ TokenID of the Conditional token asset being traded
382
+ """
383
+
384
+ price: float
385
+ """
386
+ Price used to create the order
387
+ """
388
+
389
+ size: float
390
+ """
391
+ Size in terms of the ConditionalToken
392
+ """
393
+
394
+ side: str
395
+ """
396
+ Side of the order
397
+ """
398
+
399
+ fee_rate_bps: int = 0
400
+ """
401
+ Fee rate, in basis points, charged to the order maker, charged on proceeds
402
+ """
403
+
404
+ nonce: int = 0
405
+ """
406
+ Nonce used for onchain cancellations
407
+ """
408
+
409
+ expiration: int = 0
410
+ """
411
+ Timestamp after which the order is expired.
412
+ """
413
+
414
+ taker: str = ZERO_ADDRESS
415
+ """
416
+ Address of the order taker. The zero address is used to indicate a public order.
417
+ """
418
+
419
+
420
+ class MarketOrderArgs(BaseModel):
421
+ token_id: str
422
+ """
423
+ TokenID of the Conditional token asset being traded
424
+ """
425
+
426
+ amount: float
427
+ """
428
+ BUY orders: $$$ Amount to buy
429
+ SELL orders: Shares to sell
430
+ """
431
+
432
+ side: str
433
+ """
434
+ Side of the order
435
+ """
436
+
437
+ price: float = 0
438
+ """
439
+ Price used to create the order
440
+ """
441
+
442
+ fee_rate_bps: int = 0
443
+ """
444
+ Fee rate, in basis points, charged to the order maker, charged on proceeds.
445
+ """
446
+
447
+ nonce: int = 0
448
+ """
449
+ Nonce used for onchain cancellations.
450
+ """
451
+
452
+ taker: str = ZERO_ADDRESS
453
+ """
454
+ Address of the order taker. The zero address is used to indicate a public order.
455
+ """
456
+
457
+ order_type: OrderType = OrderType.FOK
458
+
459
+ class PostOrdersArgs(BaseModel):
460
+ order: SignedOrder
461
+ order_type: OrderType = OrderType.GTC
462
+
463
+ model_config = ConfigDict(arbitrary_types_allowed=True)
464
+
465
+ class ContractConfig(BaseModel):
466
+ """Contract Configuration."""
467
+
468
+ exchange: EthAddress
469
+ """
470
+ The exchange contract responsible for matching orders.
471
+ """
472
+
473
+ collateral: EthAddress
474
+ """
475
+ The ERC20 token used as collateral for the exchange's markets.
476
+ """
477
+
478
+ conditional_tokens: EthAddress
479
+ """
480
+ The ERC1155 conditional tokens contract.
481
+ """
482
+
483
+
484
+ class OrderPostResponse(BaseModel):
485
+ error_msg: str = Field(alias="errorMsg")
486
+ order_id: Union[Keccak256, Literal[""]] = Field(alias="orderID")
487
+ taking_amount: str = Field(alias="takingAmount")
488
+ making_amount: str = Field(alias="makingAmount")
489
+ status: str = Literal["live", "matched", "delayed"]
490
+ success: bool
491
+
492
+ class OrderCancelResponse(BaseModel):
493
+ not_canceled: Optional[dict[Keccak256, str]]
494
+ canceled: Optional[list[Keccak256]]
@@ -0,0 +1,49 @@
1
+ import re
2
+ from datetime import datetime
3
+ from typing import Annotated
4
+
5
+ from pydantic import AfterValidator, BaseModel, BeforeValidator, Field
6
+
7
+
8
+ def validate_keccak256(v: str) -> str:
9
+ if not re.match(r"^0x[a-fA-F0-9]{64}$", v):
10
+ msg = "Invalid Keccak256 hash format"
11
+ raise ValueError(msg)
12
+ return v
13
+
14
+ def parse_timestamp(v: str) -> datetime:
15
+ # Normalize '+00' to '+0000' for timezone
16
+ if v.endswith("+00"):
17
+ v = v[:-3] + "+0000"
18
+
19
+ # Pad fractional seconds to 6 digits if present
20
+ if "." in v:
21
+ dot_pos = v.find(".")
22
+ tz_pos = v.find("+", dot_pos) # Find timezone start after '.'
23
+ if tz_pos == -1:
24
+ tz_pos = v.find("-", dot_pos)
25
+
26
+ if tz_pos != -1:
27
+ frac = v[dot_pos+1:tz_pos]
28
+ if len(frac) < 6:
29
+ frac = frac.ljust(6, "0")
30
+ v = f"{v[:dot_pos+1]}{frac}{v[tz_pos:]}"
31
+
32
+ # Try parsing with and without microseconds
33
+ for fmt in ("%Y-%m-%d %H:%M:%S.%f%z", "%Y-%m-%d %H:%M:%S%z"):
34
+ try:
35
+ return datetime.strptime(v, fmt) # noqa: DTZ007
36
+ except ValueError:
37
+ continue
38
+ msg = f"Time data '{v}' does not match expected formats."
39
+ raise ValueError(msg)
40
+
41
+
42
+ TimestampWithTZ = Annotated[datetime, BeforeValidator(parse_timestamp)]
43
+ EthAddress = Annotated[str, Field(pattern=r"^0x[A-Fa-f0-9]{40}$")]
44
+ Keccak256 = Annotated[str, AfterValidator(validate_keccak256)]
45
+ EmptyString = Annotated[str, Field(pattern=r"^$", description="An empty string")]
46
+
47
+ class TimeseriesPoint(BaseModel):
48
+ t: datetime
49
+ p: float
@@ -0,0 +1,161 @@
1
+ from datetime import UTC, datetime
2
+ from typing import Literal
3
+
4
+ from pydantic import BaseModel, Field, field_validator
5
+
6
+ from .common import EmptyString, EthAddress, Keccak256
7
+
8
+
9
+ class Position(BaseModel):
10
+ # User identification
11
+ proxy_wallet: EthAddress = Field(alias="proxyWallet")
12
+
13
+ # Asset information
14
+ token_id: str = Field(alias="asset")
15
+ condition_id: Keccak256 = Field(alias="conditionId")
16
+ outcome: str
17
+ outcome_index: int = Field(alias="outcomeIndex")
18
+ opposite_outcome: str = Field(alias="oppositeOutcome")
19
+ opposite_asset: str = Field(alias="oppositeAsset")
20
+
21
+ # Position details
22
+ size: float
23
+ avg_price: float = Field(alias="avgPrice")
24
+ current_price: float = Field(alias="curPrice")
25
+ redeemable: bool
26
+
27
+ # Financial metrics
28
+ initial_value: float = Field(alias="initialValue")
29
+ current_value: float = Field(alias="currentValue")
30
+ cash_pnl: float = Field(alias="cashPnl")
31
+ percent_pnl: float = Field(alias="percentPnl")
32
+ total_bought: float = Field(alias="totalBought")
33
+ realized_pnl: float = Field(alias="realizedPnl")
34
+ percent_realized_pnl: float = Field(alias="percentRealizedPnl")
35
+
36
+ # Event information
37
+ title: str
38
+ slug: str
39
+ icon: str
40
+ event_slug: str = Field(alias="eventSlug")
41
+ end_date: datetime = Field(alias="endDate")
42
+ negative_risk: bool = Field(alias="negativeRisk")
43
+
44
+ @field_validator("end_date", mode="before")
45
+ def handle_empty_end_date(cls, v):
46
+ if v == "":
47
+ return datetime(2099,12,31, tzinfo=UTC)
48
+ return v
49
+
50
+
51
+ class Trade(BaseModel):
52
+ # User identification
53
+ proxy_wallet: EthAddress = Field(alias="proxyWallet")
54
+
55
+ # Trade details
56
+ side: Literal["BUY", "SELL"]
57
+ token_id: str = Field(alias="asset")
58
+ condition_id: Keccak256 = Field(alias="conditionId")
59
+ size: float
60
+ price: float
61
+ timestamp: datetime
62
+
63
+ # Event information
64
+ title: str
65
+ slug: str
66
+ icon: str
67
+ event_slug: str = Field(alias="eventSlug")
68
+ outcome: str
69
+ outcome_index: int = Field(alias="outcomeIndex")
70
+
71
+ # User profile
72
+ name: str
73
+ pseudonym: str
74
+ bio: str
75
+ profile_image: str = Field(alias="profileImage")
76
+ profile_image_optimized: str = Field(alias="profileImageOptimized")
77
+
78
+ # Transaction information
79
+ transaction_hash: Keccak256 = Field(alias="transactionHash")
80
+
81
+
82
+ class Activity(BaseModel):
83
+ # User identification
84
+ proxy_wallet: EthAddress = Field(alias="proxyWallet")
85
+
86
+ # Activity details
87
+ timestamp: datetime
88
+ condition_id: Keccak256 | EmptyString = Field(alias="conditionId")
89
+ type: Literal["TRADE", "SPLIT", "MERGE", "REDEEM", "REWARD", "CONVERSION"]
90
+ size: float
91
+ usdc_size: float = Field(alias="usdcSize")
92
+ price: float
93
+ asset: str
94
+ side: str | None
95
+ outcome_index: int = Field(alias="outcomeIndex")
96
+
97
+ # Event information
98
+ title: str
99
+ slug: str
100
+ icon: str
101
+ event_slug: str = Field(alias="eventSlug")
102
+ outcome: str
103
+
104
+ # User profile
105
+ name: str
106
+ pseudonym: str
107
+ bio: str
108
+ profile_image: str = Field(alias="profileImage")
109
+ profile_image_optimized: str = Field(alias="profileImageOptimized")
110
+
111
+ # Transaction information
112
+ transaction_hash: Keccak256 = Field(alias="transactionHash")
113
+
114
+
115
+ class Holder(BaseModel):
116
+ # User identification
117
+ proxy_wallet: EthAddress = Field(alias="proxyWallet")
118
+
119
+ # Holder details
120
+ token_id: str = Field(alias="asset")
121
+ amount: float
122
+ outcome_index: int = Field(alias="outcomeIndex")
123
+
124
+ # User profile
125
+ name: str
126
+ pseudonym: str
127
+ bio: str
128
+ profile_image: str = Field(alias="profileImage")
129
+ profile_image_optimized: str = Field(alias="profileImageOptimized")
130
+ display_username_public: bool = Field(alias="displayUsernamePublic")
131
+
132
+
133
+ class HolderResponse(BaseModel):
134
+ # Asset information
135
+ token_id: str = Field(alias="token")
136
+
137
+ # Holders list
138
+ holders: list[Holder]
139
+
140
+
141
+ class ValueResponse(BaseModel):
142
+ # User identification
143
+ proxy_wallet: EthAddress = Field(alias="proxyWallet")
144
+
145
+ # Value information
146
+ value: float
147
+
148
+ class User(BaseModel):
149
+ proxy_wallet: EthAddress = Field(alias="proxyWallet")
150
+ name: str
151
+ bio: str
152
+ profile_image: str = Field(alias="profileImage")
153
+ profile_image_optimized: str = Field(alias="profileImageOptimized")
154
+
155
+ class UserMetric(User):
156
+ amount: float
157
+ pseudonym: str
158
+
159
+ class UserRank(User):
160
+ amount: float
161
+ rank: int