stakeapi-codestats 0.2.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.
stakeapi/exceptions.py ADDED
@@ -0,0 +1,43 @@
1
+ """Custom exceptions for StakeAPI."""
2
+
3
+
4
+ class StakeAPIError(Exception):
5
+ """Base exception for StakeAPI errors."""
6
+
7
+ pass
8
+
9
+
10
+ class AuthenticationError(StakeAPIError):
11
+ """Raised when authentication fails."""
12
+
13
+ pass
14
+
15
+
16
+ class RateLimitError(StakeAPIError):
17
+ """Raised when rate limit is exceeded."""
18
+
19
+ pass
20
+
21
+
22
+ class ValidationError(StakeAPIError):
23
+ """Raised when input validation fails."""
24
+
25
+ pass
26
+
27
+
28
+ class NetworkError(StakeAPIError):
29
+ """Raised when network requests fail."""
30
+
31
+ pass
32
+
33
+
34
+ class GameNotFoundError(StakeAPIError):
35
+ """Raised when a requested game is not found."""
36
+
37
+ pass
38
+
39
+
40
+ class InsufficientFundsError(StakeAPIError):
41
+ """Raised when user has insufficient funds for an operation."""
42
+
43
+ pass
stakeapi/models.py ADDED
@@ -0,0 +1,285 @@
1
+ """Data models for StakeAPI.
2
+
3
+ Models reflect the actual GraphQL API response shapes as verified
4
+ against stake.com and stake.us on 2025-05-09/10.
5
+
6
+ Pydantic v2 model_config handles camelCase (from API) <-> snake_case (Python)
7
+ conversion via alias_generator.
8
+ """
9
+
10
+ from decimal import Decimal
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ from pydantic import BaseModel as _BaseModel
14
+ from pydantic import ConfigDict, Field
15
+
16
+
17
+ def _to_camel(name: str) -> str:
18
+ first, *rest = name.split("_")
19
+ return first + "".join(r.capitalize() for r in rest)
20
+
21
+
22
+ class StakeModel(_BaseModel):
23
+ """Base model with camelCase alias support."""
24
+
25
+ model_config = ConfigDict(
26
+ populate_by_name=True,
27
+ alias_generator=_to_camel,
28
+ )
29
+
30
+ @classmethod
31
+ def from_dict(cls, data: Dict[str, Any]) -> "StakeModel":
32
+ return cls(**data)
33
+
34
+
35
+ class BalanceEntry(StakeModel):
36
+ """Available or vault balance for a single currency."""
37
+
38
+ amount: Decimal = Decimal("0")
39
+ currency: str = ""
40
+
41
+ @classmethod
42
+ def from_dict(cls, data: Dict[str, Any]) -> "BalanceEntry":
43
+ return cls(**data)
44
+
45
+
46
+ class User(StakeModel):
47
+ """User model matching the real GraphQL user shape."""
48
+
49
+ id: str = ""
50
+ name: str = ""
51
+ email: Optional[str] = None
52
+ has_email_verified: bool = False
53
+ is_muted: bool = False
54
+ is_rainproof: bool = False
55
+ is_banned: bool = False
56
+ created_at: Optional[str] = None
57
+
58
+ @classmethod
59
+ def from_dict(cls, data: Dict[str, Any]) -> "User":
60
+ return cls(**data)
61
+
62
+
63
+ class SessionInfo(StakeModel):
64
+ """Session entry from user.sessionList."""
65
+
66
+ id: str = ""
67
+ session_name: Optional[str] = None
68
+ ip: Optional[str] = None
69
+ active: bool = False
70
+ country: Optional[str] = None
71
+ city: Optional[str] = None
72
+ created_at: Optional[str] = None
73
+ updated_at: Optional[str] = None
74
+
75
+ @classmethod
76
+ def from_dict(cls, data: Dict[str, Any]) -> "SessionInfo":
77
+ return cls(**data)
78
+
79
+
80
+ class ApiKeyInfo(StakeModel):
81
+ """API key entry from user.apiKeys."""
82
+
83
+ id: str = ""
84
+ ip: Optional[str] = None
85
+ active: bool = False
86
+ session_name: Optional[str] = None
87
+ type: Optional[str] = None
88
+ created_at: Optional[str] = None
89
+ updated_at: Optional[str] = None
90
+
91
+ @classmethod
92
+ def from_dict(cls, data: Dict[str, Any]) -> "ApiKeyInfo":
93
+ return cls(**data)
94
+
95
+
96
+ class StatisticEntry(StakeModel):
97
+ """Per-currency wagering statistic from user.statistic."""
98
+
99
+ id: str = ""
100
+ bet_amount: Decimal = Decimal("0")
101
+ profit: Decimal = Decimal("0")
102
+ amount: Decimal = Decimal("0")
103
+ currency: str = ""
104
+
105
+ @classmethod
106
+ def from_dict(cls, data: Dict[str, Any]) -> "StatisticEntry":
107
+ return cls(**data)
108
+
109
+
110
+ class SeedPair(StakeModel):
111
+ """Active client/server seed pair."""
112
+
113
+ client_seed: str = ""
114
+ server_seed_hash: str = ""
115
+ nonce: int = 0
116
+ next_seed_hash: Optional[str] = None
117
+
118
+ @classmethod
119
+ def from_dict(cls, data: Dict[str, Any]) -> "SeedPair":
120
+ return cls(**data)
121
+
122
+
123
+ class FaucetInfo(StakeModel):
124
+ """Reload/faucet status."""
125
+
126
+ id: str = ""
127
+ active: bool = False
128
+ value: Decimal = Decimal("0")
129
+ claim_interval: Optional[int] = None
130
+ last_claim: Optional[str] = None
131
+ expire_at: Optional[str] = None
132
+
133
+ @classmethod
134
+ def from_dict(cls, data: Dict[str, Any]) -> "FaucetInfo":
135
+ return cls(**data)
136
+
137
+
138
+ class TransactionEntry(StakeModel):
139
+ """Single transaction from user.transaction."""
140
+
141
+ id: str = ""
142
+ amount: Decimal = Decimal("0")
143
+ currency: str = ""
144
+ type: str = ""
145
+ created_at: Optional[str] = None
146
+
147
+ @classmethod
148
+ def from_dict(cls, data: Dict[str, Any]) -> "TransactionEntry":
149
+ return cls(**data)
150
+
151
+
152
+ class BlackjackCard(StakeModel):
153
+ """Card in a blackjack hand."""
154
+
155
+ rank: str = ""
156
+ suit: str = ""
157
+
158
+ @classmethod
159
+ def from_dict(cls, data: Dict[str, Any]) -> "BlackjackCard":
160
+ return cls(**data)
161
+
162
+
163
+ class BlackjackHand(StakeModel):
164
+ """Blackjack hand (player or dealer)."""
165
+
166
+ value: int = 0
167
+ actions: List[str] = Field(default_factory=list)
168
+ cards: List[BlackjackCard] = Field(default_factory=list)
169
+
170
+ @classmethod
171
+ def from_dict(cls, data: Dict[str, Any]) -> "BlackjackHand":
172
+ return cls(**data)
173
+
174
+
175
+ class BlackjackBet(StakeModel):
176
+ """Active blackjack bet."""
177
+
178
+ id: str = ""
179
+ active: bool = False
180
+ nonce: int = 0
181
+ payout_multiplier: Decimal = Decimal("0")
182
+ amount_multiplier: Decimal = Decimal("0")
183
+ amount: Decimal = Decimal("0")
184
+ payout: Decimal = Decimal("0")
185
+ updated_at: Optional[str] = None
186
+ currency: str = ""
187
+ game: str = ""
188
+ player: Optional[BlackjackHand] = None
189
+ dealer: Optional[BlackjackHand] = None
190
+
191
+ @classmethod
192
+ def from_dict(cls, data: Dict[str, Any]) -> "BlackjackBet":
193
+ return cls(**data)
194
+
195
+
196
+ class BonusCodeInfo(StakeModel):
197
+ """Bonus code availability info."""
198
+
199
+ availability_status: str = ""
200
+ bonus_value: Optional[Decimal] = None
201
+ crypto_multiplier: Optional[Decimal] = None
202
+
203
+ @classmethod
204
+ def from_dict(cls, data: Dict[str, Any]) -> "BonusCodeInfo":
205
+ return cls(**data)
206
+
207
+
208
+ class KuratorGame(StakeModel):
209
+ """Game entry in a kurator collection/group."""
210
+
211
+ id: str = ""
212
+ name: str = ""
213
+ slug: str = ""
214
+ provider: Optional[str] = None
215
+
216
+ @classmethod
217
+ def from_dict(cls, data: Dict[str, Any]) -> "KuratorGame":
218
+ return cls(**data)
219
+
220
+
221
+ class KuratorCollection(StakeModel):
222
+ """Kurator game collection."""
223
+
224
+ id: str = ""
225
+ name: str = ""
226
+ slug: str = ""
227
+ games: List[KuratorGame] = Field(default_factory=list)
228
+
229
+ @classmethod
230
+ def from_dict(cls, data: Dict[str, Any]) -> "KuratorCollection":
231
+ return cls(**data)
232
+
233
+
234
+ class CurrencyInfo(StakeModel):
235
+ """Currency configuration entry."""
236
+
237
+ name: str = ""
238
+ rates: List[Dict[str, Any]] = Field(default_factory=list)
239
+
240
+ @classmethod
241
+ def from_dict(cls, data: Dict[str, Any]) -> "CurrencyInfo":
242
+ return cls(**data)
243
+
244
+
245
+ class SportItem(StakeModel):
246
+ """Sport menu entry."""
247
+
248
+ id: str = ""
249
+ name: str = ""
250
+ slug: str = ""
251
+ icon: Optional[str] = None
252
+ active: bool = False
253
+
254
+ @classmethod
255
+ def from_dict(cls, data: Dict[str, Any]) -> "SportItem":
256
+ return cls(**data)
257
+
258
+
259
+ class RaceInfo(StakeModel):
260
+ """Active race entry."""
261
+
262
+ id: str = ""
263
+ name: str = ""
264
+ start_date: Optional[str] = None
265
+ end_date: Optional[str] = None
266
+ prize: Optional[Decimal] = None
267
+ currency: str = ""
268
+
269
+ @classmethod
270
+ def from_dict(cls, data: Dict[str, Any]) -> "RaceInfo":
271
+ return cls(**data)
272
+
273
+
274
+ class NotificationEntry(StakeModel):
275
+ """Notification entry."""
276
+
277
+ id: str = ""
278
+ type: Optional[str] = None
279
+ message: Optional[str] = None
280
+ read: bool = False
281
+ created_at: Optional[str] = None
282
+
283
+ @classmethod
284
+ def from_dict(cls, data: Dict[str, Any]) -> "NotificationEntry":
285
+ return cls(**data)
stakeapi/utils.py ADDED
@@ -0,0 +1,141 @@
1
+ """Utility functions for StakeAPI."""
2
+
3
+ import re
4
+ from datetime import datetime, timezone
5
+ from decimal import Decimal, InvalidOperation
6
+ from typing import Any, Dict, Optional
7
+
8
+
9
+ def validate_api_key(api_key: str) -> bool:
10
+ """
11
+ Validate API key format.
12
+
13
+ Args:
14
+ api_key: The API key to validate
15
+
16
+ Returns:
17
+ True if valid format
18
+ """
19
+ if not api_key or not isinstance(api_key, str):
20
+ return False
21
+
22
+ # Basic format validation (adjust based on actual format)
23
+ pattern = r"^[a-zA-Z0-9]{32,64}$"
24
+ return bool(re.match(pattern, api_key))
25
+
26
+
27
+ def safe_decimal(value: Any) -> Optional[Decimal]:
28
+ """
29
+ Safely convert value to Decimal.
30
+
31
+ Args:
32
+ value: Value to convert
33
+
34
+ Returns:
35
+ Decimal value or None if conversion fails
36
+ """
37
+ if value is None:
38
+ return None
39
+
40
+ try:
41
+ return Decimal(str(value))
42
+ except (InvalidOperation, ValueError, TypeError):
43
+ return None
44
+
45
+
46
+ def parse_datetime(date_string: str) -> Optional[datetime]:
47
+ """
48
+ Parse datetime string to datetime object.
49
+
50
+ Args:
51
+ date_string: ISO format datetime string
52
+
53
+ Returns:
54
+ Datetime object or None if parsing fails
55
+ """
56
+ if not date_string:
57
+ return None
58
+
59
+ try:
60
+ # Try parsing ISO format with timezone
61
+ return datetime.fromisoformat(date_string.replace("Z", "+00:00"))
62
+ except ValueError:
63
+ try:
64
+ # Try parsing without timezone
65
+ dt = datetime.fromisoformat(date_string)
66
+ return dt.replace(tzinfo=timezone.utc)
67
+ except ValueError:
68
+ return None
69
+
70
+
71
+ def format_currency(amount: Decimal, currency: str = "USD") -> str:
72
+ """
73
+ Format currency amount for display.
74
+
75
+ Args:
76
+ amount: Amount to format
77
+ currency: Currency code
78
+
79
+ Returns:
80
+ Formatted currency string
81
+ """
82
+ if currency.upper() == "USD":
83
+ return f"${amount:.2f}"
84
+ elif currency.upper() == "EUR":
85
+ return f"€{amount:.2f}"
86
+ elif currency.upper() == "GBP":
87
+ return f"£{amount:.2f}"
88
+ else:
89
+ return f"{amount:.2f} {currency.upper()}"
90
+
91
+
92
+ def calculate_win_rate(wins: int, total_bets: int) -> float:
93
+ """
94
+ Calculate win rate percentage.
95
+
96
+ Args:
97
+ wins: Number of wins
98
+ total_bets: Total number of bets
99
+
100
+ Returns:
101
+ Win rate as percentage (0-100)
102
+ """
103
+ if total_bets == 0:
104
+ return 0.0
105
+
106
+ return (wins / total_bets) * 100
107
+
108
+
109
+ def validate_bet_amount(amount: Decimal, min_bet: Decimal, max_bet: Decimal) -> bool:
110
+ """
111
+ Validate bet amount is within limits.
112
+
113
+ Args:
114
+ amount: Bet amount
115
+ min_bet: Minimum bet amount
116
+ max_bet: Maximum bet amount
117
+
118
+ Returns:
119
+ True if amount is valid
120
+ """
121
+ return min_bet <= amount <= max_bet
122
+
123
+
124
+ def sanitize_game_name(name: str) -> str:
125
+ """
126
+ Sanitize game name for safe usage.
127
+
128
+ Args:
129
+ name: Game name to sanitize
130
+
131
+ Returns:
132
+ Sanitized game name
133
+ """
134
+ if not name:
135
+ return ""
136
+
137
+ # Remove special characters and normalize spaces
138
+ sanitized = re.sub(r"[^\w\s-]", "", name)
139
+ sanitized = re.sub(r"\s+", " ", sanitized).strip()
140
+
141
+ return sanitized
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: stakeapi-codestats
3
+ Version: 0.2.0
4
+ Summary: Unofficial async Python wrapper for the stake.com / stake.us GraphQL API — CodeStats.gg edition
5
+ Author: brokechubb
6
+ License: MIT
7
+ Project-URL: Homepage, https://codestats.gg
8
+ Project-URL: Documentation, https://brokechubb.github.io/StakeAPI/
9
+ Project-URL: Repository, https://github.com/brokechubb/StakeAPI
10
+ Project-URL: Bug Tracker, https://github.com/brokechubb/StakeAPI/issues
11
+ Keywords: stake,gambling,api,casino,betting,codestats,graphql,stake.us
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: aiohttp>=3.8.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Requires-Dist: python-dotenv>=0.19.0
29
+ Requires-Dist: websockets>=10.0
30
+ Requires-Dist: cryptography>=3.4.8
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
34
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
35
+ Requires-Dist: black>=22.0.0; extra == "dev"
36
+ Requires-Dist: isort>=5.10.0; extra == "dev"
37
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
38
+ Requires-Dist: mypy>=0.910; extra == "dev"
39
+ Requires-Dist: pre-commit>=2.15.0; extra == "dev"
40
+ Provides-Extra: docs
41
+ Requires-Dist: sphinx>=4.0.0; extra == "docs"
42
+ Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "docs"
43
+ Requires-Dist: myst-parser>=0.15.0; extra == "docs"
44
+ Dynamic: license-file
45
+
46
+ # StakeAPI
47
+
48
+ ### UPDATED MAY 2026
49
+
50
+ An unofficial async Python wrapper for the stake.com / stake.us GraphQL API.
51
+
52
+ ## Disclaimer
53
+
54
+ This is an unofficial wrapper, not affiliated with or endorsed by Stake.com or Stake.us. Use at your own risk and ensure compliance with all applicable laws and the platform's terms of service.
55
+
56
+ ## Features
57
+
58
+ - Async/await with `aiohttp`
59
+ - All GraphQL operations verified against live stake.com and stake.us APIs
60
+ - Pydantic v2 models with camelCase alias support
61
+ - Cloudflare bypass via `cf_clearance` cookie + matching User-Agent
62
+ - stake.us works with access token only — no Cloudflare cookie required
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ pip install stakeapi-codestats
68
+ ```
69
+
70
+ ## Quick Start
71
+
72
+ ```python
73
+ import asyncio
74
+ from stakeapi import StakeAPI
75
+
76
+ async def main():
77
+ # stake.us — access token only
78
+ async with StakeAPI(
79
+ access_token="your_token",
80
+ base_url="https://stake.us",
81
+ ) as client:
82
+ balance = await client.get_user_balance()
83
+ print(balance)
84
+
85
+ asyncio.run(main())
86
+ ```
87
+
88
+ ### stake.com (requires Cloudflare clearance)
89
+
90
+ stake.com blocks requests without a valid `cf_clearance` cookie. Get it from your browser's DevTools (Application → Cookies → `cf_clearance`) or extract it with Playwright:
91
+
92
+ ```python
93
+ async with StakeAPI(
94
+ access_token="your_token",
95
+ cf_clearance="your_cf_clearance_cookie",
96
+ user_agent="Mozilla/5.0 ... Chrome/147.0.0.0 ...", # must match cookie
97
+ base_url="https://stake.com",
98
+ ) as client:
99
+ balance = await client.get_user_balance()
100
+ ```
101
+
102
+ ## Getting Your Access Token
103
+
104
+ 1. Log in to stake.com in your browser
105
+ 2. Open DevTools (F12) → Network tab
106
+ 3. Make any action that triggers a request to `/_api/graphql`
107
+ 4. Find the `x-access-token` request header — that's your token
108
+
109
+ ## API Methods
110
+
111
+ ### User
112
+
113
+ | Method | Description |
114
+ | ------------------------------------------- | --------------------------------------- |
115
+ | `get_user_balance()` | Available + vault balances per currency |
116
+ | `get_user_profile()` | Name, email, verification status |
117
+ | `get_user_meta(name=None)` | Lightweight user info with balances |
118
+ | `get_user_meta_extended(name, signup_code)` | Extended info including self-exclude |
119
+ | `get_user_account_info()` | Name, email, createdAt |
120
+ | `get_user_kyc_status()` | KYC status (stake.com only) |
121
+ | `get_user_sessions()` | Active sessions with IP and location |
122
+ | `get_user_api_keys()` | API key list |
123
+ | `get_user_statistic()` | Per-currency wagering stats |
124
+ | `get_user_seed_pair()` | Provably fair client/server seeds |
125
+ | `is_user_tfa_enabled()` | 2FA status |
126
+ | `get_user_preferences()` | User preferences object |
127
+ | `get_user_recent_games(limit)` | Recently played games |
128
+
129
+ ### VIP / Reload / Faucet
130
+
131
+ | Method | Description |
132
+ | ----------------------- | ------------------------------------- |
133
+ | `get_vip_meta()` | Balances + reload status combined |
134
+ | `get_faucet()` | Reload/faucet status |
135
+ | `get_active_rakeback()` | Rakeback info (permission-restricted) |
136
+ | `get_tip_list(limit)` | User tip history |
137
+
138
+ ### Currency / Config
139
+
140
+ | Method | Description |
141
+ | ------------------------------------------ | ------------------------------------------------- |
142
+ | `get_currency_configuration(is_acp)` | Currency rates; `is_acp=True` for stake.us |
143
+ | `get_conversion_rates(display_currencies)` | Fiat conversion rates (lowercase enum: `["usd"]`) |
144
+
145
+ ### Bonuses / Promos
146
+
147
+ | Method | Description |
148
+ | ------------------------------------- | ----------------------- |
149
+ | `check_bonus_code(code, coupon_type)` | Check code availability |
150
+ | `get_racing_list()` | Race/campaign list |
151
+ | `get_campaign_balances()` | User campaign balances |
152
+
153
+ ### Transactions / History
154
+
155
+ | Method | Description |
156
+ | ---------------------------------------- | -------------------------------------------------- |
157
+ | `get_transactions(offset, limit, types)` | Transaction history with optional type filter |
158
+ | `get_deposits(offset, limit)` | Deposit history |
159
+ | `get_withdrawals(offset, limit)` | Withdrawal history |
160
+ | `get_my_bets(limit)` | Chat list (direct bet history unavailable via API) |
161
+
162
+ ### Casino / Games
163
+
164
+ | Method | Description |
165
+ | ----------------------------------------- | --------------------------------------------- |
166
+ | `get_blackjack_active_bet()` | Active BJ bet or null |
167
+ | `get_kurator_collection(collection_type)` | Game collection by enum type |
168
+ | `get_kurator_group(slug)` | Game group by slug (e.g. `"stake-originals"`) |
169
+
170
+ ### Sports (stake.com only)
171
+
172
+ | Method | Description |
173
+ | ----------------------- | -------------------------------------- |
174
+ | `get_sport_list_menu()` | Sport list (region-locked on stake.us) |
175
+
176
+ ### Social / Misc
177
+
178
+ | Method | Description |
179
+ | ---------------------------------- | --------------------------------------- |
180
+ | `get_active_races()` | Active races |
181
+ | `get_notifications(offset, limit)` | Notification list |
182
+ | `get_public_chats()` | Public chat entries |
183
+ | `get_banned_countries()` | Banned countries (CSV in `value` field) |
184
+ | `get_player_count()` | Player count by scope |
185
+ | `get_feature_flags()` | Feature flag list |
186
+
187
+ ### Mutations
188
+
189
+ | Method | Description |
190
+ | --------------------------------------------------- | -------------------------- |
191
+ | `claim_bonus_code(code, currency, turnstile_token)` | Claim a bonus code |
192
+ | `claim_faucet(currency, turnstile_token)` | Claim reload bonus |
193
+ | `claim_rakeback()` | Claim rakeback |
194
+ | `create_vault_deposit(currency, amount)` | Deposit to vault |
195
+ | `rotate_seed_pair(seed)` | Rotate provably fair seeds |
196
+ | `blackjack_bet(amount, currency, identifier)` | Place BJ bet |
197
+ | `blackjack_next(action, identifier)` | Hit/stand/double |
198
+
199
+ > **Note:** `claim_bonus_code` and `claim_faucet` require a Cloudflare Turnstile token (sitekey `0x4AAAAAAAGD4gMGOTFnvupz`), which must be generated in a browser context.
200
+
201
+ ## Custom GraphQL Queries
202
+
203
+ ```python
204
+ async with StakeAPI(access_token="token", base_url="https://stake.us") as client:
205
+ data = await client._graphql_request(
206
+ query="""
207
+ query UserSeedPair {
208
+ user {
209
+ id
210
+ activeClientSeed { seed }
211
+ activeServerSeed { seedHash nonce }
212
+ }
213
+ }
214
+ """,
215
+ operation_name="UserSeedPair",
216
+ )
217
+ print(data)
218
+ ```
219
+
220
+ ## Development
221
+
222
+ ```bash
223
+ make install-dev # install with dev deps
224
+ make format # black + isort
225
+ make check # lint + typecheck + tests
226
+ ```
227
+
228
+ ## License
229
+
230
+ MIT