@dubsdotapp/expo 0.1.2 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dubsdotapp/expo",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
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,7 @@ import type {
29
29
  DubsPublicUser,
30
30
  DubsAppUser,
31
31
  CheckUsernameResult,
32
+ LiveScore,
32
33
  } from './types';
33
34
 
34
35
  export interface DubsClientConfig {
@@ -69,13 +70,23 @@ export class DubsClient {
69
70
  headers['Authorization'] = `Bearer ${this._token}`;
70
71
  }
71
72
 
73
+ console.log(`[DubsClient] ${method} ${url}`, body ? JSON.stringify(body).slice(0, 200) : '');
74
+
72
75
  const res = await fetch(url, {
73
76
  method,
74
77
  headers,
75
78
  body: body ? JSON.stringify(body) : undefined,
76
79
  });
77
80
 
78
- const json = await res.json();
81
+ const text = await res.text();
82
+ console.log(`[DubsClient] ${method} ${path} → ${res.status}`, text.slice(0, 300));
83
+
84
+ let json: any;
85
+ try {
86
+ json = JSON.parse(text);
87
+ } catch {
88
+ throw new DubsApiError('parse_error', `Invalid JSON response: ${text.slice(0, 100)}`, res.status);
89
+ }
79
90
 
80
91
  if (!json.success) {
81
92
  const err = json.error;
@@ -211,6 +222,14 @@ export class DubsClient {
211
222
  return res.game;
212
223
  }
213
224
 
225
+ async getLiveScore(gameId: string): Promise<LiveScore | null> {
226
+ const res = await this.request<{ success: true; liveScore: LiveScore | null }>(
227
+ 'GET',
228
+ `/games/${encodeURIComponent(gameId)}/live-score`,
229
+ );
230
+ return res.liveScore;
231
+ }
232
+
214
233
  async getGames(params?: GetGamesParams): Promise<GameListItem[]> {
215
234
  const qs = new URLSearchParams();
216
235
  if (params?.wallet) qs.set('wallet', params.wallet);
@@ -230,6 +249,7 @@ export class DubsClient {
230
249
  async getNetworkGames(params?: GetNetworkGamesParams): Promise<{ games: GameListItem[]; pagination: Pagination }> {
231
250
  const qs = new URLSearchParams();
232
251
  if (params?.league) qs.set('league', params.league);
252
+ if (params?.exclude_wallet) qs.set('exclude_wallet', params.exclude_wallet);
233
253
  if (params?.limit != null) qs.set('limit', String(params.limit));
234
254
  if (params?.offset != null) qs.set('offset', String(params.offset));
235
255
  const query = qs.toString();
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 {
@@ -26,7 +27,7 @@ export interface UseAuthResult {
26
27
  * Register a new user after authenticate() returned needsRegistration.
27
28
  * Reuses the nonce+signature from authenticate() — no second signing prompt.
28
29
  */
29
- register: (username: string, referralCode?: string) => Promise<void>;
30
+ register: (username: string, referralCode?: string, avatarUrl?: string) => Promise<void>;
30
31
 
31
32
  /** Log out and clear state */
32
33
  logout: () => Promise<void>;
@@ -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);
@@ -107,7 +111,7 @@ export function useAuth(): UseAuthResult {
107
111
  }
108
112
  }, [client, wallet]);
109
113
 
110
- const register = useCallback(async (username: string, referralCode?: string) => {
114
+ const register = useCallback(async (username: string, referralCode?: string, avatarUrl?: string) => {
111
115
  try {
112
116
  const pending = pendingAuth.current;
113
117
  if (!pending) {
@@ -123,10 +127,15 @@ export function useAuth(): UseAuthResult {
123
127
  nonce: pending.nonce,
124
128
  username,
125
129
  referralCode,
130
+ avatarUrl,
126
131
  });
127
132
 
128
133
  pendingAuth.current = null;
129
- 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);
130
139
  setToken(result.token);
131
140
  setStatus('authenticated');
132
141
  } catch (err) {
@@ -165,6 +174,9 @@ export function useAuth(): UseAuthResult {
165
174
  }
166
175
  }, [client]);
167
176
 
177
+ // If shared context exists (inside AuthGate), prefer it over local state
178
+ if (sharedAuth) return sharedAuth;
179
+
168
180
  return {
169
181
  status,
170
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,8 @@ export type {
46
52
  RegisterResult,
47
53
  CheckUsernameResult,
48
54
  AuthStatus,
55
+ LiveScore,
56
+ LiveScoreCompetitor,
49
57
  } from './types';
50
58
 
51
59
  // Provider
@@ -79,5 +87,15 @@ export type {
79
87
  export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, useDubsTheme } from './ui';
80
88
  export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, UserProfileCardProps, SettingsSheetProps, DubsTheme } from './ui';
81
89
 
90
+ // Game widgets
91
+ export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton } from './ui';
92
+ export type {
93
+ GamePosterProps,
94
+ LivePoolsCardProps,
95
+ PickWinnerCardProps,
96
+ PlayersCardProps,
97
+ JoinGameButtonProps,
98
+ } from './ui';
99
+
82
100
  // Utils
83
- export { signAndSendBase64Transaction, pollTransactionConfirmation } from './utils/transaction';
101
+ export { signAndSendBase64Transaction } from './utils/transaction';
@@ -0,0 +1,148 @@
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
+ children: (adapter: MwaWalletAdapter) => React.ReactNode;
26
+ }
27
+
28
+ // ── Component ──
29
+
30
+ export function ManagedWalletProvider({
31
+ appName,
32
+ cluster,
33
+ storage,
34
+ renderConnectScreen,
35
+ children,
36
+ }: ManagedWalletProviderProps) {
37
+ const [connected, setConnected] = useState(false);
38
+ const [connecting, setConnecting] = useState(false);
39
+ const [isReady, setIsReady] = useState(false);
40
+ const [error, setError] = useState<string | null>(null);
41
+ const adapterRef = useRef<MwaWalletAdapter | null>(null);
42
+ const transactRef = useRef<any>(null);
43
+
44
+ // Lazily create adapter
45
+ if (!adapterRef.current) {
46
+ adapterRef.current = new MwaWalletAdapter({
47
+ transact: (...args: any[]) => {
48
+ if (!transactRef.current) {
49
+ throw new Error(
50
+ '@dubsdotapp/expo: @solana-mobile/mobile-wallet-adapter-protocol-web3js is required. ' +
51
+ 'Install it with: npm install @solana-mobile/mobile-wallet-adapter-protocol-web3js',
52
+ );
53
+ }
54
+ return transactRef.current(...args);
55
+ },
56
+ appIdentity: { name: appName },
57
+ cluster,
58
+ onAuthTokenChange: (token) => {
59
+ if (token) {
60
+ storage.setItem(STORAGE_KEYS.MWA_AUTH_TOKEN, token).catch(() => {});
61
+ } else {
62
+ storage.deleteItem(STORAGE_KEYS.MWA_AUTH_TOKEN).catch(() => {});
63
+ }
64
+ },
65
+ });
66
+ }
67
+ const adapter = adapterRef.current;
68
+
69
+ // Dynamic-import transact on mount
70
+ useEffect(() => {
71
+ let cancelled = false;
72
+
73
+ (async () => {
74
+ try {
75
+ const mwa = await import('@solana-mobile/mobile-wallet-adapter-protocol-web3js');
76
+ if (cancelled) return;
77
+ transactRef.current = mwa.transact;
78
+ } catch {
79
+ // MWA not installed — transact calls will throw a clear error
80
+ }
81
+
82
+ // Attempt silent reconnect from saved token
83
+ try {
84
+ const savedToken = await storage.getItem(STORAGE_KEYS.MWA_AUTH_TOKEN);
85
+ if (savedToken && !cancelled) {
86
+ adapter.setAuthToken(savedToken);
87
+ await adapter.connect();
88
+ if (!cancelled) setConnected(true);
89
+ }
90
+ } catch {
91
+ // Token expired or wallet unavailable — user will tap Connect manually
92
+ } finally {
93
+ if (!cancelled) setIsReady(true);
94
+ }
95
+ })();
96
+
97
+ return () => { cancelled = true; };
98
+ }, [adapter, storage]);
99
+
100
+ const handleConnect = useCallback(async () => {
101
+ setConnecting(true);
102
+ setError(null);
103
+ try {
104
+ await adapter.connect();
105
+ setConnected(true);
106
+ } catch (err) {
107
+ const message = err instanceof Error ? err.message : 'Connection failed';
108
+ setError(message);
109
+ } finally {
110
+ setConnecting(false);
111
+ }
112
+ }, [adapter]);
113
+
114
+ const disconnect = useCallback(async () => {
115
+ adapter.disconnect();
116
+ await storage.deleteItem(STORAGE_KEYS.MWA_AUTH_TOKEN).catch(() => {});
117
+ await storage.deleteItem(STORAGE_KEYS.JWT_TOKEN).catch(() => {});
118
+ setConnected(false);
119
+ }, [adapter, storage]);
120
+
121
+ // Show nothing until we've attempted silent reconnect
122
+ if (!isReady) return null;
123
+
124
+ // Not connected — show connect screen
125
+ if (!connected) {
126
+ if (renderConnectScreen === false) {
127
+ // Headless mode — render nothing
128
+ return null;
129
+ }
130
+ const connectProps: ConnectWalletScreenProps = {
131
+ onConnect: handleConnect,
132
+ connecting,
133
+ error,
134
+ appName,
135
+ };
136
+ if (renderConnectScreen) {
137
+ return <>{renderConnectScreen(connectProps)}</>;
138
+ }
139
+ return <ConnectWalletScreen {...connectProps} />;
140
+ }
141
+
142
+ // Connected — render children with adapter + disconnect
143
+ return (
144
+ <DisconnectContext.Provider value={disconnect}>
145
+ {children(adapter)}
146
+ </DisconnectContext.Provider>
147
+ );
148
+ }