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/client.py ADDED
@@ -0,0 +1,695 @@
1
+ """Main client for StakeAPI."""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+ from urllib.parse import urljoin
5
+
6
+ import aiohttp
7
+
8
+ from .auth import AuthManager
9
+ from .endpoints import Endpoints, GraphQLQueries
10
+ from .exceptions import AuthenticationError, RateLimitError, StakeAPIError
11
+ from .models import ( # noqa: F401
12
+ ApiKeyInfo,
13
+ BalanceEntry,
14
+ BlackjackBet,
15
+ BlackjackCard,
16
+ BlackjackHand,
17
+ BonusCodeInfo,
18
+ CurrencyInfo,
19
+ FaucetInfo,
20
+ KuratorCollection,
21
+ KuratorGame,
22
+ NotificationEntry,
23
+ RaceInfo,
24
+ SeedPair,
25
+ SessionInfo,
26
+ SportItem,
27
+ StatisticEntry,
28
+ TransactionEntry,
29
+ User,
30
+ )
31
+
32
+
33
+ class StakeAPI:
34
+ """Main client for interacting with stake.com API."""
35
+
36
+ def __init__(
37
+ self,
38
+ access_token: Optional[str] = None,
39
+ session_cookie: Optional[str] = None,
40
+ cf_clearance: Optional[str] = None,
41
+ user_agent: Optional[str] = None,
42
+ base_url: str = "https://stake.com",
43
+ timeout: int = 30,
44
+ rate_limit: int = 10,
45
+ ):
46
+ """
47
+ Initialize the StakeAPI client.
48
+
49
+ Args:
50
+ access_token: Your stake.com access token (x-access-token header)
51
+ session_cookie: Session cookie for authentication
52
+ cf_clearance: Cloudflare clearance cookie (required for stake.com)
53
+ user_agent: Browser UA (must match the one that got cf_clearance)
54
+ base_url: Base URL for the API (use https://stake.us for stake.us)
55
+ timeout: Request timeout in seconds
56
+ rate_limit: Maximum requests per second
57
+ """
58
+ self.access_token = access_token
59
+ self.session_cookie = session_cookie
60
+ self.cf_clearance = cf_clearance
61
+ self.user_agent = user_agent
62
+ self.base_url = base_url
63
+ self.timeout = timeout
64
+ self.rate_limit = rate_limit
65
+
66
+ self._session: Optional[aiohttp.ClientSession] = None
67
+ self._auth_manager = AuthManager(access_token)
68
+
69
+ async def __aenter__(self):
70
+ """Async context manager entry."""
71
+ await self._create_session()
72
+ return self
73
+
74
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
75
+ """Async context manager exit."""
76
+ await self.close()
77
+
78
+ async def _create_session(self):
79
+ """Create aiohttp session with proper headers."""
80
+ ua = self.user_agent or (
81
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
82
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
83
+ "Chrome/135.0.0.0 Safari/537.36"
84
+ )
85
+ headers = {
86
+ "User-Agent": ua,
87
+ "Accept": "application/graphql+json, application/json",
88
+ "Accept-Language": "en-US,en;q=0.9",
89
+ "Content-Type": "application/json",
90
+ "Origin": self.base_url,
91
+ "Referer": f"{self.base_url}/",
92
+ "Sec-Ch-Ua-Mobile": "?0",
93
+ "Sec-Ch-Ua-Platform": '"Windows"',
94
+ "Sec-Fetch-Dest": "empty",
95
+ "Sec-Fetch-Mode": "cors",
96
+ "Sec-Fetch-Site": "same-origin",
97
+ "X-Language": "en",
98
+ }
99
+
100
+ if self.access_token:
101
+ headers["X-Access-Token"] = self.access_token
102
+
103
+ cookies = {}
104
+ if self.session_cookie:
105
+ cookies["session"] = self.session_cookie
106
+ if self.cf_clearance:
107
+ cookies["cf_clearance"] = self.cf_clearance
108
+
109
+ timeout = aiohttp.ClientTimeout(total=self.timeout)
110
+ self._session = aiohttp.ClientSession(
111
+ headers=headers,
112
+ timeout=timeout,
113
+ cookies=cookies or None,
114
+ )
115
+
116
+ async def close(self):
117
+ """Close the session."""
118
+ if self._session:
119
+ await self._session.close()
120
+
121
+ async def _request(
122
+ self,
123
+ method: str,
124
+ endpoint: str,
125
+ params: Optional[Dict] = None,
126
+ data: Optional[Dict] = None,
127
+ ) -> Dict[Any, Any]:
128
+ """Make an authenticated HTTP request to the API."""
129
+ if not self._session:
130
+ await self._create_session()
131
+
132
+ url = urljoin(self.base_url, endpoint)
133
+
134
+ try:
135
+ async with self._session.request(
136
+ method, url, params=params, json=data
137
+ ) as response:
138
+ if response.status == 403:
139
+ raise StakeAPIError(
140
+ "403 Forbidden — Cloudflare blocking the request. "
141
+ "Provide a valid 'cf_clearance' cookie. "
142
+ "Get it from: stake.com -> DevTools (F12) -> "
143
+ "Application -> Cookies -> cf_clearance. "
144
+ "Pass as: StakeAPI(access_token=..., cf_clearance='...')"
145
+ )
146
+ elif response.status == 401:
147
+ raise AuthenticationError(
148
+ "Invalid access token or unauthorized access"
149
+ )
150
+ elif response.status == 429:
151
+ raise RateLimitError("Rate limit exceeded")
152
+
153
+ response_data = await response.json()
154
+
155
+ if response.status >= 400:
156
+ raise StakeAPIError(
157
+ f"API error: {response.status} - {response_data}"
158
+ )
159
+
160
+ return response_data
161
+
162
+ except (StakeAPIError, AuthenticationError, RateLimitError):
163
+ raise
164
+ except aiohttp.ClientError as e:
165
+ raise StakeAPIError(f"Request failed: {e}")
166
+
167
+ async def _graphql_request(
168
+ self,
169
+ query: str,
170
+ variables: Optional[Dict[str, Any]] = None,
171
+ operation_name: Optional[str] = None,
172
+ ) -> Dict[Any, Any]:
173
+ """Make a GraphQL request to the stake.com API."""
174
+ payload: Dict[str, Any] = {"query": query}
175
+
176
+ if variables:
177
+ payload["variables"] = variables
178
+
179
+ if operation_name:
180
+ payload["operationName"] = operation_name
181
+
182
+ response = await self._request("POST", Endpoints.GRAPHQL, data=payload)
183
+
184
+ if "errors" in response:
185
+ error_messages = [
186
+ error.get("message", "Unknown error") for error in response["errors"]
187
+ ]
188
+ raise StakeAPIError(f"GraphQL errors: {', '.join(error_messages)}")
189
+
190
+ return response.get("data", {})
191
+
192
+ # ═══════════════════════════════════════════════════════════════════
193
+ # USER METHODS
194
+ # ═══════════════════════════════════════════════════════════════════
195
+
196
+ async def get_user_balance(self) -> Dict[str, Dict[str, float]]:
197
+ """Get user account balance.
198
+
199
+ Returns:
200
+ Balance information by currency with available and vault amounts.
201
+ """
202
+ data = await self._graphql_request(
203
+ GraphQLQueries.USER_BALANCES, operation_name="UserBalances"
204
+ )
205
+
206
+ result: Dict[str, Dict[str, float]] = {"available": {}, "vault": {}}
207
+
208
+ if "user" in data and data["user"] and "balances" in data["user"]:
209
+ for entry in data["user"]["balances"]:
210
+ if "available" in entry:
211
+ currency = entry["available"].get("currency", "").lower()
212
+ amount = float(entry["available"].get("amount", 0))
213
+ result["available"][currency] = amount
214
+ if "vault" in entry:
215
+ currency = entry["vault"].get("currency", "").lower()
216
+ amount = float(entry["vault"].get("amount", 0))
217
+ result["vault"][currency] = amount
218
+
219
+ return result
220
+
221
+ async def get_user_profile(self) -> Dict[str, Any]:
222
+ """Get current user profile (verified fields only)."""
223
+ return await self._graphql_request(
224
+ GraphQLQueries.USER_PROFILE, operation_name="UserProfile"
225
+ )
226
+
227
+ async def get_user_meta(self, name: Optional[str] = None) -> Dict[str, Any]:
228
+ """Get lightweight user info with balances.
229
+
230
+ Args:
231
+ name: Optional username to look up
232
+ """
233
+ variables = {}
234
+ if name:
235
+ variables["name"] = name
236
+ return await self._graphql_request(
237
+ GraphQLQueries.USER_META,
238
+ variables=variables or None,
239
+ operation_name="UserMeta",
240
+ )
241
+
242
+ async def get_user_meta_extended(
243
+ self, name: Optional[str] = None, signup_code: bool = False
244
+ ) -> Dict[str, Any]:
245
+ """Get extended user info including self-exclude and campaign status.
246
+
247
+ Args:
248
+ name: Optional username to look up
249
+ signup_code: Whether to include signup code info
250
+ """
251
+ return await self._graphql_request(
252
+ GraphQLQueries.USER_META_EXTENDED,
253
+ variables={"name": name, "signupCode": signup_code},
254
+ operation_name="UserMetaExtended",
255
+ )
256
+
257
+ async def get_user_account_info(self) -> Dict[str, Any]:
258
+ """Get user account info with email, country details (stake.com)."""
259
+ return await self._graphql_request(
260
+ GraphQLQueries.USER_ACCOUNT_INFO, operation_name="UserAccountInfo"
261
+ )
262
+
263
+ async def get_user_kyc_status(self) -> Dict[str, Any]:
264
+ """Get user KYC status (stake.com only; returns null on stake.us)."""
265
+ return await self._graphql_request(
266
+ GraphQLQueries.USER_KYC_STATUS, operation_name="UserKycStatus"
267
+ )
268
+
269
+ async def get_user_sessions(self) -> Dict[str, Any]:
270
+ """Get user session list with ip, location details."""
271
+ return await self._graphql_request(
272
+ GraphQLQueries.USER_SESSIONS, operation_name="UserSessions"
273
+ )
274
+
275
+ async def get_user_api_keys(self) -> Dict[str, Any]:
276
+ """Get user API keys (stake.com only; may return empty on stake.us)."""
277
+ return await self._graphql_request(
278
+ GraphQLQueries.USER_API_KEYS, operation_name="UserApiKeys"
279
+ )
280
+
281
+ async def get_user_statistic(self) -> Dict[str, Any]:
282
+ """Get per-currency wagering statistics."""
283
+ return await self._graphql_request(
284
+ GraphQLQueries.USER_STATISTIC, operation_name="UserStatistic"
285
+ )
286
+
287
+ async def get_user_seed_pair(self) -> Dict[str, Any]:
288
+ """Get active client/server seed pair and nonce."""
289
+ return await self._graphql_request(
290
+ GraphQLQueries.USER_SEED_PAIR, operation_name="UserSeedPair"
291
+ )
292
+
293
+ async def is_user_tfa_enabled(self) -> Dict[str, Any]:
294
+ """Check if user has two-factor authentication enabled."""
295
+ return await self._graphql_request(
296
+ GraphQLQueries.IS_USER_TFA_ENABLED, operation_name="IsUserTfaEnabled"
297
+ )
298
+
299
+ async def get_user_preferences(self) -> Dict[str, Any]:
300
+ """Get user preferences."""
301
+ return await self._graphql_request(
302
+ GraphQLQueries.USER_PREFERENCES, operation_name="UserPreferences"
303
+ )
304
+
305
+ async def get_user_recent_games(self, limit: int = 10) -> Dict[str, Any]:
306
+ """Get user's recently played games.
307
+
308
+ Args:
309
+ limit: Maximum number of games to return (default: 10)
310
+ """
311
+ return await self._graphql_request(
312
+ GraphQLQueries.USER_RECENT_GAME_LIST,
313
+ variables={"limit": limit},
314
+ operation_name="UserRecentGameList",
315
+ )
316
+
317
+ # ═══════════════════════════════════════════════════════════════════
318
+ # VIP / RELOAD / FAUCET METHODS
319
+ # ═══════════════════════════════════════════════════════════════════
320
+
321
+ async def get_vip_meta(self) -> Dict[str, Any]:
322
+ """Get VIP meta info: balances + reload/faucet status combined."""
323
+ return await self._graphql_request(
324
+ GraphQLQueries.VIP_META, operation_name="VipMeta"
325
+ )
326
+
327
+ async def get_faucet(self) -> Dict[str, Any]:
328
+ """Get reload/faucet status."""
329
+ return await self._graphql_request(
330
+ GraphQLQueries.FAUCET, operation_name="Faucet"
331
+ )
332
+
333
+ async def get_active_rakeback(self) -> Dict[str, Any]:
334
+ """Get active rakeback amount per currency."""
335
+ return await self._graphql_request(
336
+ GraphQLQueries.ACTIVE_RAKEBACK, operation_name="ActiveRakeback"
337
+ )
338
+
339
+ async def get_tip_list(self, limit: int = 20) -> Dict[str, Any]:
340
+ """Get user tip list.
341
+
342
+ Args:
343
+ limit: Number of tips to return (default: 20)
344
+ """
345
+ return await self._graphql_request(
346
+ GraphQLQueries.TIP_LIMIT,
347
+ variables={"limit": limit},
348
+ operation_name="TipList",
349
+ )
350
+
351
+ # ═══════════════════════════════════════════════════════════════════
352
+ # CURRENCY / CONFIG METHODS
353
+ # ═══════════════════════════════════════════════════════════════════
354
+
355
+ async def get_currency_configuration(self, is_acp: bool = False) -> Dict[str, Any]:
356
+ """Get currency configuration and rates.
357
+
358
+ Args:
359
+ is_acp: True for stake.us, False for stake.com
360
+ """
361
+ return await self._graphql_request(
362
+ GraphQLQueries.CURRENCY_CONFIGURATION,
363
+ variables={"isAcp": is_acp},
364
+ operation_name="CurrencyConfiguration",
365
+ )
366
+
367
+ async def get_conversion_rates(
368
+ self, display_currencies: List[str]
369
+ ) -> Dict[str, Any]:
370
+ """Get currency conversion rates for specified fiat display currencies.
371
+
372
+ Args:
373
+ display_currencies: List of lowercase fiat currency codes
374
+ (e.g., ["usd", "eur"]) — enum is lowercase
375
+ """
376
+ return await self._graphql_request(
377
+ GraphQLQueries.CURRENCY_NEW_CONVERSION_RATE,
378
+ variables={"displayCurrencies": display_currencies},
379
+ operation_name="CurrencyNewConversionRate",
380
+ )
381
+
382
+ # ═══════════════════════════════════════════════════════════════════
383
+ # BONUS / PROMO METHODS
384
+ # ═══════════════════════════════════════════════════════════════════
385
+
386
+ async def check_bonus_code(
387
+ self, code: str, coupon_type: str = "drop"
388
+ ) -> Dict[str, Any]:
389
+ """Check bonus code availability.
390
+
391
+ Args:
392
+ code: Bonus code to check
393
+ coupon_type: Type of coupon (default: "drop")
394
+ """
395
+ return await self._graphql_request(
396
+ GraphQLQueries.BONUS_CODE_INFORMATION,
397
+ variables={"code": code, "couponType": coupon_type},
398
+ operation_name="BonusCodeInformation",
399
+ )
400
+
401
+ async def get_racing_list(self) -> Dict[str, Any]:
402
+ """Get racing/campaign list."""
403
+ return await self._graphql_request(
404
+ GraphQLQueries.CAMPAIGN_LIST, operation_name="CampaignList"
405
+ )
406
+
407
+ async def get_campaign_balances(self) -> Dict[str, Any]:
408
+ """Get user campaign balances."""
409
+ return await self._graphql_request(
410
+ GraphQLQueries.CAMPAIGN_BALANCES, operation_name="CampaignBalances"
411
+ )
412
+
413
+ # ═══════════════════════════════════════════════════════════════════
414
+ # TRANSACTION / HISTORY METHODS
415
+ # ═══════════════════════════════════════════════════════════════════
416
+
417
+ async def get_transactions(
418
+ self,
419
+ offset: int = 0,
420
+ limit: int = 20,
421
+ types: Optional[List[str]] = None,
422
+ ) -> Dict[str, Any]:
423
+ """Get transaction history.
424
+
425
+ Args:
426
+ offset: Pagination offset
427
+ limit: Number of transactions to return
428
+ types: Optional list of transaction types to filter
429
+ (e.g., ["bonusDrop", "rakeback", "chatTip"])
430
+ """
431
+ variables: Dict[str, Any] = {"offset": offset, "limit": limit}
432
+ if types:
433
+ variables["types"] = types
434
+ return await self._graphql_request(
435
+ GraphQLQueries.TRANSACTION,
436
+ variables=variables,
437
+ operation_name="Transaction",
438
+ )
439
+
440
+ async def get_deposits(self, offset: int = 0, limit: int = 20) -> Dict[str, Any]:
441
+ """Get deposit history.
442
+
443
+ Args:
444
+ offset: Pagination offset
445
+ limit: Number of deposits to return
446
+ """
447
+ return await self._graphql_request(
448
+ GraphQLQueries.DEPOSIT_LIST,
449
+ variables={"offset": offset, "limit": limit},
450
+ operation_name="DepositList",
451
+ )
452
+
453
+ async def get_withdrawals(self, offset: int = 0, limit: int = 20) -> Dict[str, Any]:
454
+ """Get withdrawal history.
455
+
456
+ Args:
457
+ offset: Pagination offset
458
+ limit: Number of withdrawals to return
459
+ """
460
+ return await self._graphql_request(
461
+ GraphQLQueries.WITHDRAWAL_LIST,
462
+ variables={"offset": offset, "limit": limit},
463
+ operation_name="WithdrawalList",
464
+ )
465
+
466
+ async def get_my_bets(self, limit: int = 20) -> Dict[str, Any]:
467
+ """Get user chat list (bet history not available via this field).
468
+
469
+ Args:
470
+ limit: Number of entries to return (default: 20)
471
+ """
472
+ return await self._graphql_request(
473
+ GraphQLQueries.MY_BET_LIST,
474
+ variables={"limit": limit},
475
+ operation_name="MyBetList",
476
+ )
477
+
478
+ # ═══════════════════════════════════════════════════════════════════
479
+ # CASINO / GAME METHODS
480
+ # ═══════════════════════════════════════════════════════════════════
481
+
482
+ async def get_blackjack_active_bet(self) -> Dict[str, Any]:
483
+ """Get current active blackjack bet (returns null if none)."""
484
+ return await self._graphql_request(
485
+ GraphQLQueries.BLACKJACK_ACTIVE_BET,
486
+ operation_name="BlackjackActiveBet",
487
+ )
488
+
489
+ async def get_kurator_collection(self, collection_type: str) -> Dict[str, Any]:
490
+ """Get a kurator collection by type.
491
+
492
+ Args:
493
+ collection_type: GameKuratorCollectionEnum value
494
+ (must match the server enum exactly)
495
+ """
496
+ return await self._graphql_request(
497
+ GraphQLQueries.KURATOR_COLLECTION,
498
+ variables={"type": collection_type},
499
+ operation_name="KuratorCollection",
500
+ )
501
+
502
+ async def get_kurator_group(self, slug: str) -> Dict[str, Any]:
503
+ """Get a kurator group (game category) by slug.
504
+
505
+ Args:
506
+ slug: Group slug identifier (e.g., "stake-originals")
507
+ """
508
+ return await self._graphql_request(
509
+ GraphQLQueries.SLUG_KURATOR_GROUP,
510
+ variables={"slug": slug},
511
+ operation_name="SlugKuratorGroup",
512
+ )
513
+
514
+ # ═══════════════════════════════════════════════════════════════════
515
+ # SPORTS METHODS
516
+ # ═══════════════════════════════════════════════════════════════════
517
+
518
+ async def get_sport_list_menu(self) -> Dict[str, Any]:
519
+ """Get sports menu list (stake.com only; region-locked on stake.us)."""
520
+ return await self._graphql_request(
521
+ GraphQLQueries.SPORT_LIST_MENU, operation_name="SportListMenu"
522
+ )
523
+
524
+ # ═══════════════════════════════════════════════════════════════════
525
+ # SOCIAL / RACE / MISC METHODS
526
+ # ═══════════════════════════════════════════════════════════════════
527
+
528
+ async def get_active_races(self) -> Dict[str, Any]:
529
+ """Get active race list."""
530
+ return await self._graphql_request(
531
+ GraphQLQueries.ACTIVE_RACES,
532
+ operation_name="ActiveRaces",
533
+ )
534
+
535
+ async def get_notifications(
536
+ self, offset: int = 0, limit: int = 20
537
+ ) -> Dict[str, Any]:
538
+ """Get user notification list.
539
+
540
+ Args:
541
+ offset: Pagination offset
542
+ limit: Number of notifications to return
543
+ """
544
+ return await self._graphql_request(
545
+ GraphQLQueries.NOTIFICATION_LIST,
546
+ variables={"offset": offset, "limit": limit},
547
+ operation_name="NotificationList",
548
+ )
549
+
550
+ async def get_public_chats(self) -> Dict[str, Any]:
551
+ """Get public chat entries."""
552
+ return await self._graphql_request(
553
+ GraphQLQueries.PUBLIC_CHATS,
554
+ operation_name="PublicChats",
555
+ )
556
+
557
+ async def get_banned_countries(self) -> Dict[str, Any]:
558
+ """Get list of banned countries (returns CSV string in value)."""
559
+ return await self._graphql_request(
560
+ GraphQLQueries.BANNED_COUNTRIES, operation_name="BannedCountries"
561
+ )
562
+
563
+ async def get_player_count(self) -> Dict[str, Any]:
564
+ """Get player count by scope (no parameters)."""
565
+ return await self._graphql_request(
566
+ GraphQLQueries.PLAYER_COUNT_BY_SCOPE,
567
+ operation_name="PlayerCountByScope",
568
+ )
569
+
570
+ async def get_feature_flags(self) -> Dict[str, Any]:
571
+ """Get feature flag list (all flags with names)."""
572
+ return await self._graphql_request(
573
+ GraphQLQueries.FEATURE_FLAG_DETAILS,
574
+ operation_name="FeatureFlagDetails",
575
+ )
576
+
577
+ # ═══════════════════════════════════════════════════════════════════
578
+ # MUTATIONS — BONUS / FAUCET / RAKEBACK
579
+ # ═══════════════════════════════════════════════════════════════════
580
+
581
+ async def claim_bonus_code(
582
+ self, code: str, currency: str, turnstile_token: str
583
+ ) -> Dict[str, Any]:
584
+ """Claim a condition bonus code.
585
+
586
+ Args:
587
+ code: Bonus code to claim
588
+ currency: Currency enum (e.g., "btc", "usd")
589
+ turnstile_token: Cloudflare Turnstile CAPTCHA token
590
+ (sitekey: 0x4AAAAAAAGD4gMGOTFnvupz)
591
+ """
592
+ return await self._graphql_request(
593
+ GraphQLQueries.CLAIM_CONDITION_BONUS_CODE,
594
+ variables={
595
+ "code": code,
596
+ "currency": currency,
597
+ "turnstileToken": turnstile_token,
598
+ },
599
+ operation_name="ClaimConditionBonusCode",
600
+ )
601
+
602
+ async def claim_faucet(self, currency: str, turnstile_token: str) -> Dict[str, Any]:
603
+ """Claim faucet/reload bonus.
604
+
605
+ Args:
606
+ currency: Currency enum (e.g., "btc", "usd")
607
+ turnstile_token: Cloudflare Turnstile CAPTCHA token
608
+ (sitekey: 0x4AAAAAAAGD4gMGOTFnvupz)
609
+ """
610
+ return await self._graphql_request(
611
+ GraphQLQueries.CLAIM_FAUCET,
612
+ variables={"currency": currency, "turnstileToken": turnstile_token},
613
+ operation_name="ClaimFaucet",
614
+ )
615
+
616
+ async def claim_rakeback(self) -> Dict[str, Any]:
617
+ """Claim rakeback (no parameters required)."""
618
+ return await self._graphql_request(
619
+ GraphQLQueries.CLAIM_RAKEBACK, operation_name="ClaimRakeback"
620
+ )
621
+
622
+ # ═══════════════════════════════════════════════════════════════════
623
+ # MUTATIONS — VAULT
624
+ # ═══════════════════════════════════════════════════════════════════
625
+
626
+ async def create_vault_deposit(
627
+ self, currency: str, amount: float
628
+ ) -> Dict[str, Any]:
629
+ """Deposit funds into vault.
630
+
631
+ Args:
632
+ currency: Currency enum (e.g., "btc", "usd")
633
+ amount: Amount to deposit
634
+ """
635
+ return await self._graphql_request(
636
+ GraphQLQueries.CREATE_VAULT_DEPOSIT,
637
+ variables={"currency": currency, "amount": amount},
638
+ operation_name="CreateVaultDeposit",
639
+ )
640
+
641
+ # ═══════════════════════════════════════════════════════════════════
642
+ # MUTATIONS — SEED
643
+ # ═══════════════════════════════════════════════════════════════════
644
+
645
+ async def rotate_seed_pair(self, seed: str) -> Dict[str, Any]:
646
+ """Rotate the client/server seed pair.
647
+
648
+ Args:
649
+ seed: New client seed string
650
+ """
651
+ return await self._graphql_request(
652
+ GraphQLQueries.ROTATE_SEED_PAIR,
653
+ variables={"seed": seed},
654
+ operation_name="RotateSeedPair",
655
+ )
656
+
657
+ # ═══════════════════════════════════════════════════════════════════
658
+ # MUTATIONS — BLACKJACK
659
+ # ═══════════════════════════════════════════════════════════════════
660
+
661
+ async def blackjack_bet(
662
+ self, amount: float, currency: str, identifier: str
663
+ ) -> Dict[str, Any]:
664
+ """Place a blackjack bet.
665
+
666
+ Args:
667
+ amount: Bet amount
668
+ currency: Currency enum (e.g., "btc", "usd")
669
+ identifier: Unique bet identifier string
670
+ """
671
+ return await self._graphql_request(
672
+ GraphQLQueries.BLACKJACK_BET,
673
+ variables={
674
+ "amount": amount,
675
+ "currency": currency,
676
+ "identifier": identifier,
677
+ },
678
+ operation_name="BlackjackBet",
679
+ )
680
+
681
+ async def blackjack_next(
682
+ self, action: Dict[str, Any], identifier: str
683
+ ) -> Dict[str, Any]:
684
+ """Take the next action in a blackjack hand.
685
+
686
+ Args:
687
+ action: BlackjackNextActionInput dict, e.g. {"action": "hit"}
688
+ or {"action": "stand"} or {"action": "double"}
689
+ identifier: The bet identifier from blackjack_bet response
690
+ """
691
+ return await self._graphql_request(
692
+ GraphQLQueries.BLACKJACK_NEXT,
693
+ variables={"action": action, "identifier": identifier},
694
+ operation_name="BlackjackNext",
695
+ )