@dubsdotapp/expo 0.1.3 → 0.2.1

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.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "React Native SDK for the Dubs betting platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -28,7 +28,13 @@
28
28
  "peerDependencies": {
29
29
  "@solana/web3.js": "^1.90.0",
30
30
  "react": ">=18.0.0",
31
- "react-native": ">=0.72.0"
31
+ "react-native": ">=0.72.0",
32
+ "expo-secure-store": ">=13.0.0"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "expo-secure-store": {
36
+ "optional": true
37
+ }
32
38
  },
33
39
  "optionalDependencies": {
34
40
  "@solana-mobile/mobile-wallet-adapter-protocol-web3js": "^2.0.0"
@@ -0,0 +1,9 @@
1
+ import { createContext } from 'react';
2
+ import type { UseAuthResult } from './hooks/useAuth';
3
+
4
+ /**
5
+ * Shared auth context provided by AuthGate.
6
+ * When useAuth() is called inside an AuthGate, it reads from this context
7
+ * so all components share the same auth state.
8
+ */
9
+ export const AuthContext = createContext<UseAuthResult | null>(null);
package/src/client.ts CHANGED
@@ -29,6 +29,8 @@ import type {
29
29
  DubsPublicUser,
30
30
  DubsAppUser,
31
31
  CheckUsernameResult,
32
+ LiveScore,
33
+ UiConfig,
32
34
  } from './types';
33
35
 
34
36
  export interface DubsClientConfig {
@@ -69,13 +71,23 @@ export class DubsClient {
69
71
  headers['Authorization'] = `Bearer ${this._token}`;
70
72
  }
71
73
 
74
+ console.log(`[DubsClient] ${method} ${url}`, body ? JSON.stringify(body).slice(0, 200) : '');
75
+
72
76
  const res = await fetch(url, {
73
77
  method,
74
78
  headers,
75
79
  body: body ? JSON.stringify(body) : undefined,
76
80
  });
77
81
 
78
- const json = await res.json();
82
+ const text = await res.text();
83
+ console.log(`[DubsClient] ${method} ${path} → ${res.status}`, text.slice(0, 300));
84
+
85
+ let json: any;
86
+ try {
87
+ json = JSON.parse(text);
88
+ } catch {
89
+ throw new DubsApiError('parse_error', `Invalid JSON response: ${text.slice(0, 100)}`, res.status);
90
+ }
79
91
 
80
92
  if (!json.success) {
81
93
  const err = json.error;
@@ -211,6 +223,14 @@ export class DubsClient {
211
223
  return res.game;
212
224
  }
213
225
 
226
+ async getLiveScore(gameId: string): Promise<LiveScore | null> {
227
+ const res = await this.request<{ success: true; liveScore: LiveScore | null }>(
228
+ 'GET',
229
+ `/games/${encodeURIComponent(gameId)}/live-score`,
230
+ );
231
+ return res.liveScore;
232
+ }
233
+
214
234
  async getGames(params?: GetGamesParams): Promise<GameListItem[]> {
215
235
  const qs = new URLSearchParams();
216
236
  if (params?.wallet) qs.set('wallet', params.wallet);
@@ -230,6 +250,7 @@ export class DubsClient {
230
250
  async getNetworkGames(params?: GetNetworkGamesParams): Promise<{ games: GameListItem[]; pagination: Pagination }> {
231
251
  const qs = new URLSearchParams();
232
252
  if (params?.league) qs.set('league', params.league);
253
+ if (params?.exclude_wallet) qs.set('exclude_wallet', params.exclude_wallet);
233
254
  if (params?.limit != null) qs.set('limit', String(params.limit));
234
255
  if (params?.offset != null) qs.set('offset', String(params.offset));
235
256
  const query = qs.toString();
@@ -352,4 +373,12 @@ export class DubsClient {
352
373
  getErrorCodesLocal(): Record<number, SolanaErrorCode> {
353
374
  return { ...SOLANA_PROGRAM_ERRORS };
354
375
  }
376
+
377
+ // ── App Config ──
378
+
379
+ /** Fetch the app's UI customization config (accent color, icon, tagline) */
380
+ async getAppConfig(): Promise<UiConfig> {
381
+ const res = await this.request<{ uiConfig: UiConfig }>('GET', '/apps/config');
382
+ return res.uiConfig || {};
383
+ }
355
384
  }
package/src/constants.ts CHANGED
@@ -1,3 +1,18 @@
1
1
  export const DEFAULT_BASE_URL = 'https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1';
2
2
 
3
3
  export const DEFAULT_RPC_URL = 'https://api.mainnet-beta.solana.com';
4
+
5
+ export type DubsNetwork = 'devnet' | 'mainnet-beta';
6
+
7
+ export const NETWORK_CONFIG: Record<DubsNetwork, { baseUrl: string; rpcUrl: string; cluster: string }> = {
8
+ 'mainnet-beta': {
9
+ baseUrl: DEFAULT_BASE_URL,
10
+ rpcUrl: DEFAULT_RPC_URL,
11
+ cluster: 'mainnet-beta',
12
+ },
13
+ devnet: {
14
+ baseUrl: 'https://dubs-server-dev-55d1fba09a97.herokuapp.com/api/developer/v1',
15
+ rpcUrl: 'https://api.devnet.solana.com',
16
+ cluster: 'devnet',
17
+ },
18
+ };
@@ -1,6 +1,7 @@
1
- import { useState, useCallback, useRef } from 'react';
1
+ import { useState, useCallback, useRef, useContext } from 'react';
2
2
  import bs58 from 'bs58';
3
3
  import { useDubs } from '../provider';
4
+ import { AuthContext } from '../auth-context';
4
5
  import type { AuthStatus, DubsUser } from '../types';
5
6
 
6
7
  export interface UseAuthResult {
@@ -42,6 +43,9 @@ export interface UseAuthResult {
42
43
  }
43
44
 
44
45
  export function useAuth(): UseAuthResult {
46
+ // If inside AuthGate, return the shared auth state
47
+ const sharedAuth = useContext(AuthContext);
48
+
45
49
  const { client, wallet } = useDubs();
46
50
  const [status, setStatus] = useState<AuthStatus>('idle');
47
51
  const [user, setUser] = useState<DubsUser | null>(null);
@@ -127,7 +131,11 @@ export function useAuth(): UseAuthResult {
127
131
  });
128
132
 
129
133
  pendingAuth.current = null;
130
- setUser(result.user);
134
+ // Use the chosen avatar locally if the backend didn't store it
135
+ const user = avatarUrl && !result.user.avatar
136
+ ? { ...result.user, avatar: avatarUrl }
137
+ : result.user;
138
+ setUser(user);
131
139
  setToken(result.token);
132
140
  setStatus('authenticated');
133
141
  } catch (err) {
@@ -166,6 +174,9 @@ export function useAuth(): UseAuthResult {
166
174
  }
167
175
  }, [client]);
168
176
 
177
+ // If shared context exists (inside AuthGate), prefer it over local state
178
+ if (sharedAuth) return sharedAuth;
179
+
169
180
  return {
170
181
  status,
171
182
  user,
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback } from 'react';
2
2
  import { useDubs } from '../provider';
3
- import { signAndSendBase64Transaction, pollTransactionConfirmation } from '../utils/transaction';
3
+ import { signAndSendBase64Transaction } from '../utils/transaction';
4
4
  import type { BuildClaimParams, MutationStatus } from '../types';
5
5
 
6
6
  export interface ClaimMutationResult {
@@ -10,7 +10,7 @@ export interface ClaimMutationResult {
10
10
  }
11
11
 
12
12
  export function useClaim() {
13
- const { client, wallet, connection } = useDubs();
13
+ const { client, wallet } = useDubs();
14
14
  const [status, setStatus] = useState<MutationStatus>('idle');
15
15
  const [error, setError] = useState<Error | null>(null);
16
16
  const [data, setData] = useState<ClaimMutationResult | null>(null);
@@ -28,20 +28,20 @@ export function useClaim() {
28
28
 
29
29
  try {
30
30
  // 1. Build unsigned claim transaction
31
+ console.log('[useClaim] Step 1: Building claim transaction...');
31
32
  const claimResult = await client.buildClaimTransaction(params);
33
+ console.log('[useClaim] Step 1 done.');
32
34
 
33
- // 2. Sign transaction
35
+ // 2. Sign and send transaction
34
36
  setStatus('signing');
37
+ console.log('[useClaim] Step 2: Signing and sending...');
35
38
  const signature = await signAndSendBase64Transaction(
36
39
  claimResult.transaction,
37
40
  wallet,
38
- connection,
39
41
  );
42
+ console.log('[useClaim] Step 2 done. Signature:', signature);
40
43
 
41
- // 3. Poll for confirmation (no backend confirm step for claims)
42
- setStatus('confirming');
43
- await pollTransactionConfirmation(signature, connection);
44
-
44
+ // Claims don't need backend confirmation the on-chain tx is the claim
45
45
  const explorerUrl = `https://solscan.io/tx/${signature}`;
46
46
  const result: ClaimMutationResult = {
47
47
  gameId: params.gameId,
@@ -51,14 +51,16 @@ export function useClaim() {
51
51
 
52
52
  setData(result);
53
53
  setStatus('success');
54
+ console.log('[useClaim] Complete!');
54
55
  return result;
55
56
  } catch (err) {
57
+ console.error('[useClaim] FAILED:', err);
56
58
  const error = err instanceof Error ? err : new Error(String(err));
57
59
  setError(error);
58
60
  setStatus('error');
59
61
  throw error;
60
62
  }
61
- }, [client, wallet, connection]);
63
+ }, [client, wallet]);
62
64
 
63
65
  return { execute, status, error, data, reset };
64
66
  }
@@ -1,7 +1,7 @@
1
1
  import { useState, useCallback } from 'react';
2
2
  import { useDubs } from '../provider';
3
- import { signAndSendBase64Transaction, pollTransactionConfirmation } from '../utils/transaction';
4
- import type { CreateGameParams, ConfirmGameResult, MutationStatus } from '../types';
3
+ import { signAndSendBase64Transaction } from '../utils/transaction';
4
+ import type { CreateGameParams, MutationStatus } from '../types';
5
5
 
6
6
  export interface CreateGameMutationResult {
7
7
  gameId: string;
@@ -11,7 +11,7 @@ export interface CreateGameMutationResult {
11
11
  }
12
12
 
13
13
  export function useCreateGame() {
14
- const { client, wallet, connection } = useDubs();
14
+ const { client, wallet } = useDubs();
15
15
  const [status, setStatus] = useState<MutationStatus>('idle');
16
16
  const [error, setError] = useState<Error | null>(null);
17
17
  const [data, setData] = useState<CreateGameMutationResult | null>(null);
@@ -29,23 +29,22 @@ export function useCreateGame() {
29
29
 
30
30
  try {
31
31
  // 1. Build unsigned transaction
32
+ console.log('[useCreateGame] Step 1: Building transaction...');
32
33
  const createResult = await client.createGame(params);
34
+ console.log('[useCreateGame] Step 1 done:', { gameId: createResult.gameId, gameAddress: createResult.gameAddress });
33
35
 
34
- // 2. Sign transaction
36
+ // 2. Sign and send transaction
35
37
  setStatus('signing');
38
+ console.log('[useCreateGame] Step 2: Signing and sending...');
36
39
  const signature = await signAndSendBase64Transaction(
37
40
  createResult.transaction,
38
41
  wallet,
39
- connection,
40
42
  );
43
+ console.log('[useCreateGame] Step 2 done. Signature:', signature);
41
44
 
42
- // 3. Poll for confirmation
45
+ // 3. Confirm with backend (server handles on-chain verification)
43
46
  setStatus('confirming');
44
- await pollTransactionConfirmation(signature, connection);
45
-
46
- // 4. Confirm with backend
47
- setStatus('saving');
48
- const explorerUrl = `https://solscan.io/tx/${signature}`;
47
+ console.log('[useCreateGame] Step 3: Confirming with backend...');
49
48
  await client.confirmGame({
50
49
  gameId: createResult.gameId,
51
50
  playerWallet: params.playerWallet,
@@ -55,7 +54,9 @@ export function useCreateGame() {
55
54
  role: 'creator',
56
55
  gameAddress: createResult.gameAddress,
57
56
  });
57
+ console.log('[useCreateGame] Step 3 done.');
58
58
 
59
+ const explorerUrl = `https://solscan.io/tx/${signature}`;
59
60
  const result: CreateGameMutationResult = {
60
61
  gameId: createResult.gameId,
61
62
  gameAddress: createResult.gameAddress,
@@ -65,14 +66,16 @@ export function useCreateGame() {
65
66
 
66
67
  setData(result);
67
68
  setStatus('success');
69
+ console.log('[useCreateGame] Complete!');
68
70
  return result;
69
71
  } catch (err) {
72
+ console.error('[useCreateGame] FAILED:', err);
70
73
  const error = err instanceof Error ? err : new Error(String(err));
71
74
  setError(error);
72
75
  setStatus('error');
73
76
  throw error;
74
77
  }
75
- }, [client, wallet, connection]);
78
+ }, [client, wallet]);
76
79
 
77
80
  return { execute, status, error, data, reset };
78
81
  }
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback } from 'react';
2
2
  import { useDubs } from '../provider';
3
- import { signAndSendBase64Transaction, pollTransactionConfirmation } from '../utils/transaction';
3
+ import { signAndSendBase64Transaction } from '../utils/transaction';
4
4
  import type { JoinGameParams, MutationStatus } from '../types';
5
5
 
6
6
  export interface JoinGameMutationResult {
@@ -11,7 +11,7 @@ export interface JoinGameMutationResult {
11
11
  }
12
12
 
13
13
  export function useJoinGame() {
14
- const { client, wallet, connection } = useDubs();
14
+ const { client, wallet } = useDubs();
15
15
  const [status, setStatus] = useState<MutationStatus>('idle');
16
16
  const [error, setError] = useState<Error | null>(null);
17
17
  const [data, setData] = useState<JoinGameMutationResult | null>(null);
@@ -29,33 +29,35 @@ export function useJoinGame() {
29
29
 
30
30
  try {
31
31
  // 1. Build unsigned transaction
32
+ console.log('[useJoinGame] Step 1: Building transaction...', { gameId: params.gameId, playerWallet: params.playerWallet, teamChoice: params.teamChoice, amount: params.amount });
32
33
  const joinResult = await client.joinGame(params);
34
+ console.log('[useJoinGame] Step 1 done:', { gameId: joinResult.gameId, gameAddress: joinResult.gameAddress, hasTx: !!joinResult.transaction });
33
35
 
34
- // 2. Sign transaction
36
+ // 2. Sign and send transaction
35
37
  setStatus('signing');
38
+ console.log('[useJoinGame] Step 2: Signing and sending transaction...');
36
39
  const signature = await signAndSendBase64Transaction(
37
40
  joinResult.transaction,
38
41
  wallet,
39
- connection,
40
42
  );
43
+ console.log('[useJoinGame] Step 2 done. Signature:', signature);
41
44
 
42
- // 3. Poll for confirmation
45
+ // 3. Confirm with backend (server handles on-chain verification)
43
46
  setStatus('confirming');
44
- await pollTransactionConfirmation(signature, connection);
45
-
46
- // 4. Confirm with backend
47
- setStatus('saving');
48
- const explorerUrl = `https://solscan.io/tx/${signature}`;
49
- await client.confirmGame({
47
+ const confirmParams = {
50
48
  gameId: params.gameId,
51
49
  playerWallet: params.playerWallet,
52
50
  signature,
53
51
  teamChoice: params.teamChoice,
54
52
  wagerAmount: params.amount,
55
- role: 'joiner',
53
+ role: 'joiner' as const,
56
54
  gameAddress: joinResult.gameAddress,
57
- });
55
+ };
56
+ console.log('[useJoinGame] Step 3: Confirming with backend...', confirmParams);
57
+ await client.confirmGame(confirmParams);
58
+ console.log('[useJoinGame] Step 3 done. Backend confirmed.');
58
59
 
60
+ const explorerUrl = `https://solscan.io/tx/${signature}`;
59
61
  const result: JoinGameMutationResult = {
60
62
  gameId: params.gameId,
61
63
  gameAddress: joinResult.gameAddress,
@@ -65,14 +67,16 @@ export function useJoinGame() {
65
67
 
66
68
  setData(result);
67
69
  setStatus('success');
70
+ console.log('[useJoinGame] Complete!');
68
71
  return result;
69
72
  } catch (err) {
73
+ console.error('[useJoinGame] FAILED at status:', status, err);
70
74
  const error = err instanceof Error ? err : new Error(String(err));
71
75
  setError(error);
72
76
  setStatus('error');
73
77
  throw error;
74
78
  }
75
- }, [client, wallet, connection]);
79
+ }, [client, wallet]);
76
80
 
77
81
  return { execute, status, error, data, reset };
78
82
  }
package/src/index.ts CHANGED
@@ -2,7 +2,12 @@
2
2
  export { DubsClient } from './client';
3
3
  export type { DubsClientConfig } from './client';
4
4
  export { DubsApiError, parseSolanaError, SOLANA_PROGRAM_ERRORS } from './errors';
5
- export { DEFAULT_BASE_URL, DEFAULT_RPC_URL } from './constants';
5
+ export { DEFAULT_BASE_URL, DEFAULT_RPC_URL, NETWORK_CONFIG } from './constants';
6
+ export type { DubsNetwork } from './constants';
7
+
8
+ // Storage
9
+ export { createSecureStoreStorage, STORAGE_KEYS } from './storage';
10
+ export type { TokenStorage } from './storage';
6
11
 
7
12
  // Types
8
13
  export type {
@@ -24,6 +29,7 @@ export type {
24
29
  ConfirmGameResult,
25
30
  BuildClaimParams,
26
31
  BuildClaimResult,
32
+ Bettor,
27
33
  GameDetail,
28
34
  GameMedia,
29
35
  GameListItem,
@@ -46,6 +52,9 @@ export type {
46
52
  RegisterResult,
47
53
  CheckUsernameResult,
48
54
  AuthStatus,
55
+ LiveScore,
56
+ LiveScoreCompetitor,
57
+ UiConfig,
49
58
  } from './types';
50
59
 
51
60
  // Provider
@@ -76,8 +85,18 @@ export type {
76
85
  } from './hooks';
77
86
 
78
87
  // UI
79
- export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, useDubsTheme } from './ui';
88
+ export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, useDubsTheme, mergeTheme } from './ui';
80
89
  export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, UserProfileCardProps, SettingsSheetProps, DubsTheme } from './ui';
81
90
 
91
+ // Game widgets
92
+ export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton } from './ui';
93
+ export type {
94
+ GamePosterProps,
95
+ LivePoolsCardProps,
96
+ PickWinnerCardProps,
97
+ PlayersCardProps,
98
+ JoinGameButtonProps,
99
+ } from './ui';
100
+
82
101
  // Utils
83
- export { signAndSendBase64Transaction, pollTransactionConfirmation } from './utils/transaction';
102
+ export { signAndSendBase64Transaction } from './utils/transaction';
@@ -0,0 +1,158 @@
1
+ import React, { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react';
2
+ import { MwaWalletAdapter } from './wallet/mwa-adapter';
3
+ import { ConnectWalletScreen } from './ui/ConnectWalletScreen';
4
+ import type { ConnectWalletScreenProps } from './ui/ConnectWalletScreen';
5
+ import type { TokenStorage } from './storage';
6
+ import { STORAGE_KEYS } from './storage';
7
+
8
+ // ── Disconnect Context (internal) ──
9
+
10
+ type DisconnectFn = () => Promise<void>;
11
+
12
+ export const DisconnectContext = createContext<DisconnectFn | null>(null);
13
+
14
+ export function useDisconnect(): DisconnectFn | null {
15
+ return useContext(DisconnectContext);
16
+ }
17
+
18
+ // ── Props ──
19
+
20
+ interface ManagedWalletProviderProps {
21
+ appName: string;
22
+ cluster: string;
23
+ storage: TokenStorage;
24
+ renderConnectScreen?: ((props: ConnectWalletScreenProps) => React.ReactNode) | false;
25
+ /** Developer UI config overrides for the connect screen */
26
+ accentColor?: string;
27
+ appIcon?: string;
28
+ tagline?: string;
29
+ children: (adapter: MwaWalletAdapter) => React.ReactNode;
30
+ }
31
+
32
+ // ── Component ──
33
+
34
+ export function ManagedWalletProvider({
35
+ appName,
36
+ cluster,
37
+ storage,
38
+ renderConnectScreen,
39
+ accentColor,
40
+ appIcon,
41
+ tagline,
42
+ children,
43
+ }: ManagedWalletProviderProps) {
44
+ const [connected, setConnected] = useState(false);
45
+ const [connecting, setConnecting] = useState(false);
46
+ const [isReady, setIsReady] = useState(false);
47
+ const [error, setError] = useState<string | null>(null);
48
+ const adapterRef = useRef<MwaWalletAdapter | null>(null);
49
+ const transactRef = useRef<any>(null);
50
+
51
+ // Lazily create adapter
52
+ if (!adapterRef.current) {
53
+ adapterRef.current = new MwaWalletAdapter({
54
+ transact: (...args: any[]) => {
55
+ if (!transactRef.current) {
56
+ throw new Error(
57
+ '@dubsdotapp/expo: @solana-mobile/mobile-wallet-adapter-protocol-web3js is required. ' +
58
+ 'Install it with: npm install @solana-mobile/mobile-wallet-adapter-protocol-web3js',
59
+ );
60
+ }
61
+ return transactRef.current(...args);
62
+ },
63
+ appIdentity: { name: appName },
64
+ cluster,
65
+ onAuthTokenChange: (token) => {
66
+ if (token) {
67
+ storage.setItem(STORAGE_KEYS.MWA_AUTH_TOKEN, token).catch(() => {});
68
+ } else {
69
+ storage.deleteItem(STORAGE_KEYS.MWA_AUTH_TOKEN).catch(() => {});
70
+ }
71
+ },
72
+ });
73
+ }
74
+ const adapter = adapterRef.current;
75
+
76
+ // Dynamic-import transact on mount
77
+ useEffect(() => {
78
+ let cancelled = false;
79
+
80
+ (async () => {
81
+ try {
82
+ const mwa = await import('@solana-mobile/mobile-wallet-adapter-protocol-web3js');
83
+ if (cancelled) return;
84
+ transactRef.current = mwa.transact;
85
+ } catch {
86
+ // MWA not installed — transact calls will throw a clear error
87
+ }
88
+
89
+ // Attempt silent reconnect from saved token
90
+ try {
91
+ const savedToken = await storage.getItem(STORAGE_KEYS.MWA_AUTH_TOKEN);
92
+ if (savedToken && !cancelled) {
93
+ adapter.setAuthToken(savedToken);
94
+ await adapter.connect();
95
+ if (!cancelled) setConnected(true);
96
+ }
97
+ } catch {
98
+ // Token expired or wallet unavailable — user will tap Connect manually
99
+ } finally {
100
+ if (!cancelled) setIsReady(true);
101
+ }
102
+ })();
103
+
104
+ return () => { cancelled = true; };
105
+ }, [adapter, storage]);
106
+
107
+ const handleConnect = useCallback(async () => {
108
+ setConnecting(true);
109
+ setError(null);
110
+ try {
111
+ await adapter.connect();
112
+ setConnected(true);
113
+ } catch (err) {
114
+ const message = err instanceof Error ? err.message : 'Connection failed';
115
+ setError(message);
116
+ } finally {
117
+ setConnecting(false);
118
+ }
119
+ }, [adapter]);
120
+
121
+ const disconnect = useCallback(async () => {
122
+ adapter.disconnect();
123
+ await storage.deleteItem(STORAGE_KEYS.MWA_AUTH_TOKEN).catch(() => {});
124
+ await storage.deleteItem(STORAGE_KEYS.JWT_TOKEN).catch(() => {});
125
+ setConnected(false);
126
+ }, [adapter, storage]);
127
+
128
+ // Show nothing until we've attempted silent reconnect
129
+ if (!isReady) return null;
130
+
131
+ // Not connected — show connect screen
132
+ if (!connected) {
133
+ if (renderConnectScreen === false) {
134
+ // Headless mode — render nothing
135
+ return null;
136
+ }
137
+ const connectProps: ConnectWalletScreenProps = {
138
+ onConnect: handleConnect,
139
+ connecting,
140
+ error,
141
+ appName,
142
+ accentColor,
143
+ appIcon,
144
+ tagline,
145
+ };
146
+ if (renderConnectScreen) {
147
+ return <>{renderConnectScreen(connectProps)}</>;
148
+ }
149
+ return <ConnectWalletScreen {...connectProps} />;
150
+ }
151
+
152
+ // Connected — render children with adapter + disconnect
153
+ return (
154
+ <DisconnectContext.Provider value={disconnect}>
155
+ {children(adapter)}
156
+ </DisconnectContext.Provider>
157
+ );
158
+ }