@dubsdotapp/expo 0.3.0 → 0.3.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dubsdotapp/expo",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "React Native SDK for the Dubs betting platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/src/client.ts CHANGED
@@ -37,8 +37,12 @@ import type {
37
37
  UiConfig,
38
38
  UFCEvent,
39
39
  UFCFighterDetail,
40
- DeveloperCommissionsResult,
41
- DeveloperCommissionsSummary,
40
+ ArcadePool,
41
+ ArcadeEntry,
42
+ ArcadeLeaderboardEntry,
43
+ ArcadePoolStats,
44
+ StartAttemptResult,
45
+ SubmitScoreResult,
42
46
  } from './types';
43
47
 
44
48
  export interface DubsClientConfig {
@@ -473,43 +477,85 @@ export class DubsClient {
473
477
  return { ...SOLANA_PROGRAM_ERRORS };
474
478
  }
475
479
 
476
- // ── App Config ──
480
+ // ── Arcade Pools ──
477
481
 
478
- /** Fetch the app's UI customization config (accent color, icon, tagline, environment) */
479
- async getAppConfig(): Promise<UiConfig> {
480
- const res = await this.request<{ data: { environment?: string; uiConfig: UiConfig } }>('GET', '/apps/config');
481
- const config = res.data?.uiConfig || {};
482
- if (res.data?.environment) {
483
- config.environment = res.data.environment as UiConfig['environment'];
484
- }
485
- return config;
482
+ async getArcadePools(params?: { gameSlug?: string; status?: string }): Promise<ArcadePool[]> {
483
+ const qs = new URLSearchParams();
484
+ if (params?.gameSlug) qs.set('gameSlug', params.gameSlug);
485
+ if (params?.status) qs.set('status', params.status);
486
+ const query = qs.toString();
487
+ const res = await this.request<{ success: true; pools: ArcadePool[] }>(
488
+ 'GET',
489
+ `/arcade/pools${query ? `?${query}` : ''}`,
490
+ );
491
+ return res.pools;
492
+ }
493
+
494
+ async getArcadePool(poolId: number): Promise<{ pool: ArcadePool; stats: ArcadePoolStats }> {
495
+ const res = await this.request<{ success: true; pool: ArcadePool; stats: ArcadePoolStats }>(
496
+ 'GET',
497
+ `/arcade/pools/${poolId}`,
498
+ );
499
+ return { pool: res.pool, stats: res.stats };
486
500
  }
487
501
 
488
- // ── Developer Commissions ──
502
+ async enterArcadePool(poolId: number, params: { walletAddress: string; txSignature: string }): Promise<ArcadeEntry> {
503
+ const res = await this.request<{ success: true; entry: ArcadeEntry }>(
504
+ 'POST',
505
+ `/arcade/pools/${poolId}/enter`,
506
+ params,
507
+ );
508
+ return res.entry;
509
+ }
489
510
 
490
- /** Fetch paginated list of commission earnings for this app */
491
- async getCommissions(params?: { limit?: number; offset?: number }): Promise<DeveloperCommissionsResult> {
511
+ async startArcadeAttempt(poolId: number, walletAddress: string): Promise<StartAttemptResult> {
512
+ const res = await this.request<{ success: true } & StartAttemptResult>(
513
+ 'POST',
514
+ `/arcade/pools/${poolId}/start-attempt`,
515
+ { walletAddress },
516
+ );
517
+ return { sessionToken: res.sessionToken, attemptNumber: res.attemptNumber, livesRemaining: res.livesRemaining };
518
+ }
519
+
520
+ async submitArcadeScore(poolId: number, params: { walletAddress: string; sessionToken: string; score: number; durationMs?: number }): Promise<SubmitScoreResult> {
521
+ const res = await this.request<{ success: true } & SubmitScoreResult>(
522
+ 'POST',
523
+ `/arcade/pools/${poolId}/submit-score`,
524
+ params,
525
+ );
526
+ return { score: res.score, bestScore: res.bestScore, livesUsed: res.livesUsed, isNewBest: res.isNewBest };
527
+ }
528
+
529
+ async getArcadeLeaderboard(poolId: number, params?: { limit?: number; offset?: number }): Promise<ArcadeLeaderboardEntry[]> {
492
530
  const qs = new URLSearchParams();
493
531
  if (params?.limit != null) qs.set('limit', String(params.limit));
494
532
  if (params?.offset != null) qs.set('offset', String(params.offset));
495
533
  const query = qs.toString();
496
- const res = await this.request<{ success: true } & DeveloperCommissionsResult>(
534
+ const res = await this.request<{ success: true; leaderboard: ArcadeLeaderboardEntry[] }>(
497
535
  'GET',
498
- `/commissions${query ? `?${query}` : ''}`,
536
+ `/arcade/pools/${poolId}/leaderboard${query ? `?${query}` : ''}`,
499
537
  );
500
- return {
501
- commissions: res.commissions,
502
- summary: res.summary,
503
- pagination: res.pagination,
504
- };
538
+ return res.leaderboard;
505
539
  }
506
540
 
507
- /** Fetch a quick summary of commission earnings for this app */
508
- async getCommissionsSummary(): Promise<DeveloperCommissionsSummary> {
509
- const res = await this.request<{ success: true; summary: DeveloperCommissionsSummary }>(
541
+ async getArcadeEntry(poolId: number, walletAddress: string): Promise<ArcadeEntry> {
542
+ const res = await this.request<{ success: true; entry: ArcadeEntry }>(
510
543
  'GET',
511
- '/commissions/summary',
544
+ `/arcade/pools/${poolId}/my-entry?walletAddress=${encodeURIComponent(walletAddress)}`,
512
545
  );
513
- return res.summary;
546
+ return res.entry;
514
547
  }
548
+
549
+ // ── App Config ──
550
+
551
+ /** Fetch the app's UI customization config (accent color, icon, tagline, environment) */
552
+ async getAppConfig(): Promise<UiConfig> {
553
+ const res = await this.request<{ data: { environment?: string; uiConfig: UiConfig } }>('GET', '/apps/config');
554
+ const config = res.data?.uiConfig || {};
555
+ if (res.data?.environment) {
556
+ config.environment = res.data.environment as UiConfig['environment'];
557
+ }
558
+ return config;
559
+ }
560
+
515
561
  }
@@ -18,3 +18,9 @@ export { useUFCFightCard } from './useUFCFightCard';
18
18
  export { useUFCFighterDetail } from './useUFCFighterDetail';
19
19
  export { usePushNotifications } from './usePushNotifications';
20
20
  export type { PushNotificationStatus } from './usePushNotifications';
21
+ export { useArcadePools } from './useArcadePools';
22
+ export type { UseArcadePoolsResult } from './useArcadePools';
23
+ export { useArcadePool } from './useArcadePool';
24
+ export type { UseArcadePoolResult } from './useArcadePool';
25
+ export { useArcadeGame } from './useArcadeGame';
26
+ export type { UseArcadeGameResult } from './useArcadeGame';
@@ -0,0 +1,102 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { useDubs } from '../provider';
3
+ import { useAuth } from './useAuth';
4
+ import type { ArcadeEntry, StartAttemptResult, SubmitScoreResult } from '../types';
5
+
6
+ export interface UseArcadeGameResult {
7
+ entry: ArcadeEntry | null;
8
+ livesRemaining: number;
9
+ bestScore: number;
10
+ loading: boolean;
11
+ error: Error | null;
12
+ /** Fetch/refresh the user's entry for this pool */
13
+ refreshEntry: () => Promise<void>;
14
+ /** Start a new life — returns session token to pass to the game */
15
+ startAttempt: () => Promise<StartAttemptResult>;
16
+ /** Submit score after game over */
17
+ submitScore: (sessionToken: string, score: number, durationMs?: number) => Promise<SubmitScoreResult>;
18
+ }
19
+
20
+ export function useArcadeGame(poolId: number | null, maxLives: number = 3): UseArcadeGameResult {
21
+ const { client } = useDubs();
22
+ const { user } = useAuth();
23
+ const [entry, setEntry] = useState<ArcadeEntry | null>(null);
24
+ const [loading, setLoading] = useState(false);
25
+ const [error, setError] = useState<Error | null>(null);
26
+
27
+ const walletAddress = user?.walletAddress || '';
28
+
29
+ const refreshEntry = useCallback(async () => {
30
+ if (!poolId || !walletAddress) return;
31
+ setLoading(true);
32
+ setError(null);
33
+ try {
34
+ const e = await client.getArcadeEntry(poolId, walletAddress);
35
+ setEntry(e);
36
+ } catch (err) {
37
+ // 404 means no entry yet — that's fine
38
+ if (err instanceof Error && err.message.includes('No entry found')) {
39
+ setEntry(null);
40
+ } else {
41
+ setError(err instanceof Error ? err : new Error(String(err)));
42
+ }
43
+ } finally {
44
+ setLoading(false);
45
+ }
46
+ }, [client, poolId, walletAddress]);
47
+
48
+ const startAttempt = useCallback(async (): Promise<StartAttemptResult> => {
49
+ if (!poolId || !walletAddress) throw new Error('Not ready');
50
+ setError(null);
51
+ try {
52
+ const result = await client.startArcadeAttempt(poolId, walletAddress);
53
+ return result;
54
+ } catch (err) {
55
+ const e = err instanceof Error ? err : new Error(String(err));
56
+ setError(e);
57
+ throw e;
58
+ }
59
+ }, [client, poolId, walletAddress]);
60
+
61
+ const submitScore = useCallback(async (
62
+ sessionToken: string,
63
+ score: number,
64
+ durationMs?: number,
65
+ ): Promise<SubmitScoreResult> => {
66
+ if (!poolId || !walletAddress) throw new Error('Not ready');
67
+ setError(null);
68
+ try {
69
+ const result = await client.submitArcadeScore(poolId, {
70
+ walletAddress,
71
+ sessionToken,
72
+ score,
73
+ durationMs,
74
+ });
75
+ // Update local entry state
76
+ setEntry(prev => prev ? {
77
+ ...prev,
78
+ best_score: result.bestScore,
79
+ lives_used: result.livesUsed,
80
+ } : prev);
81
+ return result;
82
+ } catch (err) {
83
+ const e = err instanceof Error ? err : new Error(String(err));
84
+ setError(e);
85
+ throw e;
86
+ }
87
+ }, [client, poolId, walletAddress]);
88
+
89
+ const livesRemaining = entry ? maxLives - entry.lives_used : maxLives;
90
+ const bestScore = entry?.best_score || 0;
91
+
92
+ return {
93
+ entry,
94
+ livesRemaining,
95
+ bestScore,
96
+ loading,
97
+ error,
98
+ refreshEntry,
99
+ startAttempt,
100
+ submitScore,
101
+ };
102
+ }
@@ -0,0 +1,44 @@
1
+ import { useState, useEffect, useCallback, useMemo } from 'react';
2
+ import { useDubs } from '../provider';
3
+ import type { ArcadePool, ArcadePoolStats, ArcadeLeaderboardEntry } from '../types';
4
+
5
+ export interface UseArcadePoolResult {
6
+ pool: ArcadePool | null;
7
+ stats: ArcadePoolStats | null;
8
+ leaderboard: ArcadeLeaderboardEntry[];
9
+ loading: boolean;
10
+ error: Error | null;
11
+ refetch: () => void;
12
+ }
13
+
14
+ export function useArcadePool(poolId: number | null): UseArcadePoolResult {
15
+ const { client } = useDubs();
16
+ const [pool, setPool] = useState<ArcadePool | null>(null);
17
+ const [stats, setStats] = useState<ArcadePoolStats | null>(null);
18
+ const [leaderboard, setLeaderboard] = useState<ArcadeLeaderboardEntry[]>([]);
19
+ const [loading, setLoading] = useState(false);
20
+ const [error, setError] = useState<Error | null>(null);
21
+
22
+ const fetch = useCallback(async () => {
23
+ if (!poolId) return;
24
+ setLoading(true);
25
+ setError(null);
26
+ try {
27
+ const [poolRes, lb] = await Promise.all([
28
+ client.getArcadePool(poolId),
29
+ client.getArcadeLeaderboard(poolId),
30
+ ]);
31
+ setPool(poolRes.pool);
32
+ setStats(poolRes.stats);
33
+ setLeaderboard(lb);
34
+ } catch (err) {
35
+ setError(err instanceof Error ? err : new Error(String(err)));
36
+ } finally {
37
+ setLoading(false);
38
+ }
39
+ }, [client, poolId]);
40
+
41
+ useEffect(() => { fetch(); }, [fetch]);
42
+
43
+ return { pool, stats, leaderboard, loading, error, refetch: fetch };
44
+ }
@@ -0,0 +1,34 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { useDubs } from '../provider';
3
+ import type { ArcadePool } from '../types';
4
+
5
+ export interface UseArcadePoolsResult {
6
+ pools: ArcadePool[];
7
+ loading: boolean;
8
+ error: Error | null;
9
+ refetch: () => void;
10
+ }
11
+
12
+ export function useArcadePools(gameSlug?: string): UseArcadePoolsResult {
13
+ const { client } = useDubs();
14
+ const [pools, setPools] = useState<ArcadePool[]>([]);
15
+ const [loading, setLoading] = useState(false);
16
+ const [error, setError] = useState<Error | null>(null);
17
+
18
+ const fetch = useCallback(async () => {
19
+ setLoading(true);
20
+ setError(null);
21
+ try {
22
+ const result = await client.getArcadePools({ gameSlug });
23
+ setPools(result);
24
+ } catch (err) {
25
+ setError(err instanceof Error ? err : new Error(String(err)));
26
+ } finally {
27
+ setLoading(false);
28
+ }
29
+ }, [client, gameSlug]);
30
+
31
+ useEffect(() => { fetch(); }, [fetch]);
32
+
33
+ return { pools, loading, error, refetch: fetch };
34
+ }
package/src/index.ts CHANGED
@@ -67,9 +67,13 @@ export type {
67
67
  UFCData,
68
68
  UFCFight,
69
69
  UFCEvent,
70
- DeveloperCommission,
71
- DeveloperCommissionsSummary,
72
- DeveloperCommissionsResult,
70
+ ArcadePool,
71
+ ArcadeEntry,
72
+ ArcadeAttempt,
73
+ ArcadeLeaderboardEntry,
74
+ ArcadePoolStats,
75
+ StartAttemptResult,
76
+ SubmitScoreResult,
73
77
  } from './types';
74
78
 
75
79
  // Provider
@@ -98,6 +102,9 @@ export {
98
102
  useUFCFightCard,
99
103
  useUFCFighterDetail,
100
104
  usePushNotifications,
105
+ useArcadePools,
106
+ useArcadePool,
107
+ useArcadeGame,
101
108
  } from './hooks';
102
109
  export type {
103
110
  CreateGameMutationResult,
@@ -107,11 +114,14 @@ export type {
107
114
  ClaimStatus,
108
115
  UseAuthResult,
109
116
  PushNotificationStatus,
117
+ UseArcadePoolsResult,
118
+ UseArcadePoolResult,
119
+ UseArcadeGameResult,
110
120
  } from './hooks';
111
121
 
112
122
  // UI
113
- export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, UserProfileSheet, DeveloperDashboardSheet, useDubsTheme, mergeTheme } from './ui';
114
- export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, UserProfileCardProps, SettingsSheetProps, UserProfileSheetProps, DeveloperDashboardSheetProps, DubsTheme } from './ui';
123
+ export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, UserProfileSheet, useDubsTheme, mergeTheme } from './ui';
124
+ export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, UserProfileCardProps, SettingsSheetProps, UserProfileSheetProps, DubsTheme } from './ui';
115
125
 
116
126
  // Game widgets
117
127
  export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet, JoinGameSheet, ClaimPrizeSheet, ClaimButton } from './ui';
package/src/types.ts CHANGED
@@ -481,57 +481,74 @@ export interface UFCEvent {
481
481
  fights: UFCFight[];
482
482
  }
483
483
 
484
- // ── Developer Commissions ──
484
+ // ── Arcade Pools ──
485
485
 
486
- export interface DeveloperCommission {
486
+ export interface ArcadePool {
487
487
  id: number;
488
- gameId: string;
489
- /** Commission amount in lamports */
490
- amount: number;
491
- /** Commission amount in SOL */
492
- amountSol: number;
493
- status: 'pending' | 'paid' | 'cancelled';
494
- createdAt: string;
495
- paidAt: string | null;
496
- txSignature: string | null;
497
- gameType: string;
498
- /** Total pot size in lamports */
499
- potSize: number;
500
- /** Total pot size in SOL */
501
- potSizeSol: number;
502
- appId?: number;
503
- appName?: string;
488
+ app_id: number;
489
+ game_slug: string;
490
+ name: string;
491
+ buy_in_lamports: number;
492
+ max_lives: number;
493
+ period_start: string;
494
+ period_end: string;
495
+ schedule: 'weekly' | 'daily' | 'manual';
496
+ status: 'open' | 'active' | 'resolving' | 'complete' | 'cancelled';
497
+ solana_game_id: string | null;
498
+ solana_game_address: string | null;
499
+ total_entries: number;
500
+ created_at: string;
501
+ }
502
+
503
+ export interface ArcadeEntry {
504
+ id: number;
505
+ pool_id: number;
506
+ wallet_address: string;
507
+ best_score: number;
508
+ lives_used: number;
509
+ rank: number | null;
510
+ created_at: string;
511
+ attempts: ArcadeAttempt[] | null;
504
512
  }
505
513
 
506
- export interface DeveloperCommissionsSummary {
507
- totalEarned: number;
508
- totalEarnedSol: number;
509
- totalPaid: number;
510
- totalPaidSol: number;
511
- totalPending: number;
512
- totalPendingSol: number;
513
- totalGames: number;
514
- last7Days?: {
515
- games: number;
516
- earned: number;
517
- earnedSol: number;
518
- };
519
- last30Days?: {
520
- games: number;
521
- earned: number;
522
- earnedSol: number;
523
- };
524
- commissionWallet?: string;
514
+ export interface ArcadeAttempt {
515
+ id: number;
516
+ attempt_number: number;
517
+ score: number | null;
518
+ status: 'active' | 'submitted' | 'expired';
519
+ started_at: string;
520
+ submitted_at: string | null;
521
+ duration_ms: number | null;
525
522
  }
526
523
 
527
- export interface DeveloperCommissionsResult {
528
- commissions: DeveloperCommission[];
529
- summary: DeveloperCommissionsSummary;
530
- pagination: {
531
- total: number;
532
- limit: number;
533
- offset: number;
534
- };
524
+ export interface ArcadeLeaderboardEntry {
525
+ id: number;
526
+ wallet_address: string;
527
+ best_score: number;
528
+ lives_used: number;
529
+ username: string;
530
+ avatar: string | null;
531
+ rank: number;
532
+ }
533
+
534
+ export interface ArcadePoolStats {
535
+ total_entries: number;
536
+ top_score: number;
537
+ avg_score: number;
538
+ active_players: number;
539
+ }
540
+
541
+ export interface StartAttemptResult {
542
+ sessionToken: string;
543
+ attemptNumber: number;
544
+ livesRemaining: number;
545
+ }
546
+
547
+ export interface SubmitScoreResult {
548
+ score: number;
549
+ bestScore: number;
550
+ livesUsed: number;
551
+ isNewBest: boolean;
535
552
  }
536
553
 
537
554
  // ── UI Config (developer branding) ──
package/src/ui/index.ts CHANGED
@@ -8,8 +8,6 @@ export { SettingsSheet } from './SettingsSheet';
8
8
  export type { SettingsSheetProps } from './SettingsSheet';
9
9
  export { UserProfileSheet } from './UserProfileSheet';
10
10
  export type { UserProfileSheetProps } from './UserProfileSheet';
11
- export { DeveloperDashboardSheet } from './DeveloperDashboardSheet';
12
- export type { DeveloperDashboardSheetProps } from './DeveloperDashboardSheet';
13
11
  export { useDubsTheme, mergeTheme } from './theme';
14
12
  export type { DubsTheme } from './theme';
15
13