@dubsdotapp/expo 0.1.0

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/src/index.ts ADDED
@@ -0,0 +1,83 @@
1
+ // Core
2
+ export { DubsClient } from './client';
3
+ export type { DubsClientConfig } from './client';
4
+ export { DubsApiError, parseSolanaError, SOLANA_PROGRAM_ERRORS } from './errors';
5
+ export { DEFAULT_BASE_URL, DEFAULT_RPC_URL } from './constants';
6
+
7
+ // Types
8
+ export type {
9
+ UnifiedEvent,
10
+ Opponent,
11
+ EventMedia,
12
+ EventStream,
13
+ EventMeta,
14
+ Pagination,
15
+ EsportsMatchDetail,
16
+ EsportsMatchOpponent,
17
+ EsportsMatchResult,
18
+ ValidateEventResult,
19
+ CreateGameParams,
20
+ CreateGameResult,
21
+ JoinGameParams,
22
+ JoinGameResult,
23
+ ConfirmGameParams,
24
+ ConfirmGameResult,
25
+ BuildClaimParams,
26
+ BuildClaimResult,
27
+ GameDetail,
28
+ GameMedia,
29
+ GameListItem,
30
+ GameListOpponent,
31
+ GetGamesParams,
32
+ GetNetworkGamesParams,
33
+ GetUpcomingEventsParams,
34
+ ParsedError,
35
+ SolanaErrorCode,
36
+ MutationStatus,
37
+ QueryResult,
38
+ MutationResult,
39
+ DubsUser,
40
+ DubsPublicUser,
41
+ DubsAppUser,
42
+ NonceResult,
43
+ AuthenticateParams,
44
+ AuthenticateResult,
45
+ RegisterParams,
46
+ RegisterResult,
47
+ CheckUsernameResult,
48
+ AuthStatus,
49
+ } from './types';
50
+
51
+ // Provider
52
+ export { DubsProvider, useDubs } from './provider';
53
+ export type { DubsProviderProps, DubsContextValue } from './provider';
54
+
55
+ // Wallet
56
+ export type { WalletAdapter } from './wallet';
57
+ export { MwaWalletAdapter } from './wallet';
58
+ export type { MwaAdapterConfig, MwaTransactFn } from './wallet';
59
+
60
+ // Hooks
61
+ export {
62
+ useEvents,
63
+ useGame,
64
+ useGames,
65
+ useNetworkGames,
66
+ useCreateGame,
67
+ useJoinGame,
68
+ useClaim,
69
+ useAuth,
70
+ } from './hooks';
71
+ export type {
72
+ CreateGameMutationResult,
73
+ JoinGameMutationResult,
74
+ ClaimMutationResult,
75
+ UseAuthResult,
76
+ } from './hooks';
77
+
78
+ // UI
79
+ export { ConnectWalletScreen, UserProfileCard, SettingsSheet, useDubsTheme } from './ui';
80
+ export type { ConnectWalletScreenProps, UserProfileCardProps, SettingsSheetProps, DubsTheme } from './ui';
81
+
82
+ // Utils
83
+ export { signAndSendBase64Transaction, pollTransactionConfirmation } from './utils/transaction';
@@ -0,0 +1,39 @@
1
+ import React, { createContext, useContext, useMemo } from 'react';
2
+ import { Connection } from '@solana/web3.js';
3
+ import { DubsClient } from './client';
4
+ import { DEFAULT_RPC_URL } from './constants';
5
+ import type { WalletAdapter } from './wallet/types';
6
+
7
+ export interface DubsContextValue {
8
+ client: DubsClient;
9
+ wallet: WalletAdapter;
10
+ connection: Connection;
11
+ }
12
+
13
+ const DubsContext = createContext<DubsContextValue | null>(null);
14
+
15
+ export interface DubsProviderProps {
16
+ apiKey: string;
17
+ baseUrl?: string;
18
+ rpcUrl?: string;
19
+ wallet: WalletAdapter;
20
+ children: React.ReactNode;
21
+ }
22
+
23
+ export function DubsProvider({ apiKey, baseUrl, rpcUrl, wallet, children }: DubsProviderProps) {
24
+ const value = useMemo<DubsContextValue>(() => {
25
+ const client = new DubsClient({ apiKey, baseUrl });
26
+ const connection = new Connection(rpcUrl || DEFAULT_RPC_URL, { commitment: 'confirmed' });
27
+ return { client, wallet, connection };
28
+ }, [apiKey, baseUrl, rpcUrl, wallet]);
29
+
30
+ return <DubsContext.Provider value={value}>{children}</DubsContext.Provider>;
31
+ }
32
+
33
+ export function useDubs(): DubsContextValue {
34
+ const ctx = useContext(DubsContext);
35
+ if (!ctx) {
36
+ throw new Error('useDubs must be used within a <DubsProvider>');
37
+ }
38
+ return ctx;
39
+ }
package/src/types.ts ADDED
@@ -0,0 +1,340 @@
1
+ // ── Unified Event (returned by /events/upcoming, /sports/events/:league, /esports/matches/upcoming) ──
2
+
3
+ export interface Opponent {
4
+ name: string | null;
5
+ imageUrl: string | null;
6
+ score: number | null;
7
+ }
8
+
9
+ export interface EventMedia {
10
+ poster: string | null;
11
+ thumbnail: string | null;
12
+ streams: EventStream[];
13
+ }
14
+
15
+ export interface EventStream {
16
+ url: string | null;
17
+ language: string | null;
18
+ }
19
+
20
+ export interface EventMeta {
21
+ matchType: string | null;
22
+ numberOfGames: number | null;
23
+ tournament: string | null;
24
+ country: string | null;
25
+ }
26
+
27
+ export interface UnifiedEvent {
28
+ id: string;
29
+ type: 'sports' | 'esports';
30
+ title: string;
31
+ league: string | null;
32
+ game: string | null;
33
+ startTime: string | null;
34
+ status: 'upcoming' | 'live' | 'finished' | 'canceled';
35
+ tier: string | null;
36
+ venue: string | null;
37
+ opponents: Opponent[];
38
+ media: EventMedia;
39
+ meta: EventMeta;
40
+ }
41
+
42
+ export interface Pagination {
43
+ page: number;
44
+ perPage: number;
45
+ total: number;
46
+ totalPages: number;
47
+ }
48
+
49
+ // ── Esports Match Detail (returned by /esports/matches/:matchId) ──
50
+
51
+ export interface EsportsMatchOpponent {
52
+ id: number;
53
+ name: string;
54
+ acronym: string | null;
55
+ imageUrl: string | null;
56
+ }
57
+
58
+ export interface EsportsMatchResult {
59
+ teamId: number;
60
+ score: number;
61
+ }
62
+
63
+ export interface EsportsMatchDetail {
64
+ matchId: number;
65
+ title: string;
66
+ status: string;
67
+ videogame: string | null;
68
+ league: string | null;
69
+ serie: string | null;
70
+ tournament: string | null;
71
+ tier: string | null;
72
+ startTime: string | null;
73
+ endTime: string | null;
74
+ matchType: string | null;
75
+ numberOfGames: number | null;
76
+ opponents: EsportsMatchOpponent[];
77
+ results: EsportsMatchResult[];
78
+ winnerId: number | null;
79
+ }
80
+
81
+ // ── Validate Event ──
82
+
83
+ export interface ValidateEventResult {
84
+ bettable: boolean;
85
+ gameMode: number;
86
+ lockTimestamp: number;
87
+ startTime: string | null;
88
+ status: string;
89
+ }
90
+
91
+ // ── Create Game ──
92
+
93
+ export interface CreateGameParams {
94
+ id: string;
95
+ playerWallet: string;
96
+ teamChoice: 'home' | 'away' | 'draw';
97
+ wagerAmount: number;
98
+ }
99
+
100
+ export interface CreateGameResult {
101
+ gameId: string;
102
+ gameAddress: string;
103
+ transaction: string;
104
+ lockTimestamp: number;
105
+ event: UnifiedEvent;
106
+ }
107
+
108
+ // ── Join Game ──
109
+
110
+ export interface JoinGameParams {
111
+ playerWallet: string;
112
+ gameId: string;
113
+ teamChoice: 'home' | 'away' | 'draw';
114
+ amount: number;
115
+ }
116
+
117
+ export interface JoinGameResult {
118
+ gameId: string;
119
+ transaction: string;
120
+ gameAddress: string;
121
+ }
122
+
123
+ // ── Confirm Game ──
124
+
125
+ export interface ConfirmGameParams {
126
+ gameId: string;
127
+ playerWallet: string;
128
+ signature: string;
129
+ teamChoice?: 'home' | 'away' | 'draw';
130
+ wagerAmount?: number;
131
+ role?: 'creator' | 'joiner';
132
+ gameAddress?: string;
133
+ }
134
+
135
+ export interface ConfirmGameResult {
136
+ gameId: string;
137
+ signature: string;
138
+ explorerUrl: string;
139
+ message: string;
140
+ }
141
+
142
+ // ── Build Claim Transaction ──
143
+
144
+ export interface BuildClaimParams {
145
+ playerWallet: string;
146
+ gameId: string;
147
+ }
148
+
149
+ export interface BuildClaimResult {
150
+ transaction: string;
151
+ gameAddress: string;
152
+ message: string;
153
+ }
154
+
155
+ // ── Game Detail (returned by /games/:gameId) ──
156
+
157
+ export interface GameMedia {
158
+ poster: string | null;
159
+ thumbnail: string | null;
160
+ }
161
+
162
+ export interface GameDetail {
163
+ gameId: string;
164
+ gameAddress: string;
165
+ title: string;
166
+ buyIn: number;
167
+ gameMode: number;
168
+ isLocked: boolean;
169
+ isResolved: boolean;
170
+ status: string;
171
+ lockTimestamp: number | null;
172
+ homePlayers: string[];
173
+ awayPlayers: string[];
174
+ drawPlayers: string[];
175
+ homePool: number;
176
+ awayPool: number;
177
+ drawPool: number;
178
+ totalPool: number;
179
+ sportsEvent: Record<string, unknown> | null;
180
+ media: GameMedia;
181
+ createdAt: string;
182
+ updatedAt: string;
183
+ }
184
+
185
+ // ── Game List Item (returned by /games) ──
186
+
187
+ export interface GameListOpponent {
188
+ name: string | null;
189
+ imageUrl: string | null;
190
+ }
191
+
192
+ export interface GameListItem {
193
+ gameId: string;
194
+ title: string;
195
+ buyIn: number;
196
+ gameMode: number;
197
+ isLocked: boolean;
198
+ isResolved: boolean;
199
+ status: string;
200
+ totalPool: number;
201
+ league: string | null;
202
+ lockTimestamp: number | null;
203
+ createdAt: string;
204
+ opponents: GameListOpponent[];
205
+ media: GameMedia;
206
+ }
207
+
208
+ export interface GetGamesParams {
209
+ wallet?: string;
210
+ status?: 'open' | 'locked' | 'resolved';
211
+ limit?: number;
212
+ offset?: number;
213
+ }
214
+
215
+ // ── Network Games Query ──
216
+
217
+ export interface GetNetworkGamesParams {
218
+ league?: string;
219
+ limit?: number;
220
+ offset?: number;
221
+ }
222
+
223
+ // ── Upcoming Events Query ──
224
+
225
+ export interface GetUpcomingEventsParams {
226
+ type?: 'sports' | 'esports';
227
+ game?: string;
228
+ page?: number;
229
+ per_page?: number;
230
+ }
231
+
232
+ // ── Error Parsing ──
233
+
234
+ export interface ParsedError {
235
+ code: string;
236
+ message: string;
237
+ }
238
+
239
+ export interface SolanaErrorCode {
240
+ code: string;
241
+ message: string;
242
+ }
243
+
244
+ // ── User Auth ──
245
+
246
+ export interface DubsUser {
247
+ walletAddress: string;
248
+ username: string;
249
+ avatar: string | null;
250
+ myReferralCode: string | null;
251
+ onboardingComplete: boolean;
252
+ createdAt: string;
253
+ }
254
+
255
+ export interface DubsPublicUser {
256
+ walletAddress: string;
257
+ username: string;
258
+ avatar: string | null;
259
+ createdAt: string;
260
+ }
261
+
262
+ export interface DubsAppUser extends DubsPublicUser {
263
+ firstSeenAt: string;
264
+ lastSeenAt: string;
265
+ }
266
+
267
+ export interface NonceResult {
268
+ nonce: string;
269
+ message: string;
270
+ }
271
+
272
+ export interface AuthenticateParams {
273
+ walletAddress: string;
274
+ signature: string;
275
+ nonce: string;
276
+ }
277
+
278
+ export interface AuthenticateResult {
279
+ needsRegistration: boolean;
280
+ user?: DubsUser;
281
+ token?: string;
282
+ }
283
+
284
+ export interface RegisterParams {
285
+ walletAddress: string;
286
+ signature: string;
287
+ nonce: string;
288
+ username: string;
289
+ referralCode?: string;
290
+ }
291
+
292
+ export interface RegisterResult {
293
+ user: DubsUser;
294
+ token: string;
295
+ }
296
+
297
+ export interface CheckUsernameResult {
298
+ available: boolean;
299
+ reason?: string;
300
+ }
301
+
302
+ export type AuthStatus =
303
+ | 'idle'
304
+ | 'authenticating'
305
+ | 'signing'
306
+ | 'verifying'
307
+ | 'needsRegistration'
308
+ | 'registering'
309
+ | 'authenticated'
310
+ | 'error';
311
+
312
+ // ── Mutation Hook Status ──
313
+
314
+ export type MutationStatus =
315
+ | 'idle'
316
+ | 'building'
317
+ | 'signing'
318
+ | 'confirming'
319
+ | 'saving'
320
+ | 'success'
321
+ | 'error';
322
+
323
+ // ── Query Hook Result ──
324
+
325
+ export interface QueryResult<T> {
326
+ data: T | null;
327
+ loading: boolean;
328
+ error: Error | null;
329
+ refetch: () => void;
330
+ }
331
+
332
+ // ── Mutation Hook Result ──
333
+
334
+ export interface MutationResult<TParams, TResult> {
335
+ execute: (params: TParams) => Promise<TResult>;
336
+ status: MutationStatus;
337
+ error: Error | null;
338
+ data: TResult | null;
339
+ reset: () => void;
340
+ }
@@ -0,0 +1,145 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ ActivityIndicator,
7
+ StyleSheet,
8
+ } from 'react-native';
9
+ import { useDubsTheme } from './theme';
10
+
11
+ export interface ConnectWalletScreenProps {
12
+ /** Called when the user taps Connect Wallet */
13
+ onConnect: () => void | Promise<void>;
14
+ /** Show a loading spinner on the button while connecting */
15
+ connecting?: boolean;
16
+ /** Error message to display (e.g. "User rejected the request") */
17
+ error?: string | null;
18
+ /** App name shown in the header. Defaults to "Dubs" */
19
+ appName?: string;
20
+ }
21
+
22
+ export function ConnectWalletScreen({
23
+ onConnect,
24
+ connecting = false,
25
+ error = null,
26
+ appName = 'Dubs',
27
+ }: ConnectWalletScreenProps) {
28
+ const t = useDubsTheme();
29
+
30
+ return (
31
+ <View style={[styles.container, { backgroundColor: t.background }]}>
32
+ <View style={styles.content}>
33
+ {/* Branding */}
34
+ <View style={styles.brandingSection}>
35
+ <View style={[styles.logoCircle, { backgroundColor: t.accent }]}>
36
+ <Text style={styles.logoText}>D</Text>
37
+ </View>
38
+ <Text style={[styles.appName, { color: t.text }]}>{appName}</Text>
39
+ <Text style={[styles.subtitle, { color: t.textMuted }]}>
40
+ Connect your Solana wallet to get started
41
+ </Text>
42
+ </View>
43
+
44
+ {/* Action */}
45
+ <View style={styles.actionSection}>
46
+ {error ? (
47
+ <View
48
+ style={[
49
+ styles.errorBox,
50
+ { backgroundColor: t.errorBg, borderColor: t.errorBorder },
51
+ ]}
52
+ >
53
+ <Text style={[styles.errorText, { color: t.errorText }]}>{error}</Text>
54
+ </View>
55
+ ) : null}
56
+
57
+ <TouchableOpacity
58
+ style={[styles.connectButton, { backgroundColor: t.accent }]}
59
+ onPress={onConnect}
60
+ disabled={connecting}
61
+ activeOpacity={0.8}
62
+ >
63
+ {connecting ? (
64
+ <ActivityIndicator color="#FFFFFF" size="small" />
65
+ ) : (
66
+ <Text style={styles.connectButtonText}>Connect Wallet</Text>
67
+ )}
68
+ </TouchableOpacity>
69
+
70
+ <Text style={[styles.hint, { color: t.textDim }]}>
71
+ Phantom, Solflare, or any Solana wallet
72
+ </Text>
73
+ </View>
74
+ </View>
75
+ </View>
76
+ );
77
+ }
78
+
79
+ const styles = StyleSheet.create({
80
+ container: {
81
+ flex: 1,
82
+ justifyContent: 'center',
83
+ },
84
+ content: {
85
+ flex: 1,
86
+ justifyContent: 'space-between',
87
+ paddingHorizontal: 32,
88
+ paddingTop: 120,
89
+ paddingBottom: 80,
90
+ },
91
+ brandingSection: {
92
+ alignItems: 'center',
93
+ gap: 12,
94
+ },
95
+ logoCircle: {
96
+ width: 80,
97
+ height: 80,
98
+ borderRadius: 40,
99
+ justifyContent: 'center',
100
+ alignItems: 'center',
101
+ marginBottom: 8,
102
+ },
103
+ logoText: {
104
+ fontSize: 36,
105
+ fontWeight: '800',
106
+ color: '#FFFFFF',
107
+ },
108
+ appName: {
109
+ fontSize: 32,
110
+ fontWeight: '800',
111
+ },
112
+ subtitle: {
113
+ fontSize: 16,
114
+ textAlign: 'center',
115
+ lineHeight: 22,
116
+ },
117
+ actionSection: {
118
+ gap: 16,
119
+ },
120
+ errorBox: {
121
+ borderWidth: 1,
122
+ borderRadius: 12,
123
+ paddingHorizontal: 16,
124
+ paddingVertical: 12,
125
+ },
126
+ errorText: {
127
+ fontSize: 14,
128
+ textAlign: 'center',
129
+ },
130
+ connectButton: {
131
+ height: 56,
132
+ borderRadius: 16,
133
+ justifyContent: 'center',
134
+ alignItems: 'center',
135
+ },
136
+ connectButtonText: {
137
+ color: '#FFFFFF',
138
+ fontSize: 18,
139
+ fontWeight: '700',
140
+ },
141
+ hint: {
142
+ fontSize: 13,
143
+ textAlign: 'center',
144
+ },
145
+ });