@dubsdotapp/expo 0.2.70 → 0.2.72

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.2.70",
3
+ "version": "0.2.72",
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
@@ -473,9 +473,13 @@ export class DubsClient {
473
473
 
474
474
  // ── App Config ──
475
475
 
476
- /** Fetch the app's UI customization config (accent color, icon, tagline) */
476
+ /** Fetch the app's UI customization config (accent color, icon, tagline, environment) */
477
477
  async getAppConfig(): Promise<UiConfig> {
478
- const res = await this.request<{ data: { uiConfig: UiConfig } }>('GET', '/apps/config');
479
- return res.data?.uiConfig || {};
478
+ const res = await this.request<{ data: { environment?: string; uiConfig: UiConfig } }>('GET', '/apps/config');
479
+ const config = res.data?.uiConfig || {};
480
+ if (res.data?.environment) {
481
+ config.environment = res.data.environment as UiConfig['environment'];
482
+ }
483
+ return config;
480
484
  }
481
485
  }
@@ -2,6 +2,7 @@ import { useState, useCallback, useRef, useContext } from 'react';
2
2
  import bs58 from 'bs58';
3
3
  import { useDubs } from '../provider';
4
4
  import { AuthContext } from '../auth-context';
5
+ import { useDisconnect } from '../managed-wallet';
5
6
  import { getDeviceInfo } from '../utils/device';
6
7
  import type { AuthStatus, DubsUser } from '../types';
7
8
 
@@ -51,6 +52,7 @@ export function useAuth(): UseAuthResult {
51
52
  const sharedAuth = useContext(AuthContext);
52
53
 
53
54
  const { client, wallet } = useDubs();
55
+ const disconnect = useDisconnect();
54
56
  const [status, setStatus] = useState<AuthStatus>('idle');
55
57
  const [user, setUser] = useState<DubsUser | null>(null);
56
58
  const [token, setToken] = useState<string | null>(null);
@@ -117,10 +119,19 @@ export function useAuth(): UseAuthResult {
117
119
  setToken(result.token!);
118
120
  setStatus('authenticated');
119
121
  } catch (err) {
120
- setError(err instanceof Error ? err : new Error(String(err)));
122
+ const message = err instanceof Error ? err.message : String(err);
123
+ // Phantom 4100 = stale/revoked session — clear it and return to connect screen
124
+ if (message.includes('4100') || message.includes('not been authorized')) {
125
+ console.log('[useAuth] Stale Phantom session detected (4100), forcing disconnect');
126
+ await disconnect?.();
127
+ setStatus('idle');
128
+ setError(null);
129
+ return;
130
+ }
131
+ setError(err instanceof Error ? err : new Error(message));
121
132
  setStatus('error');
122
133
  }
123
- }, [client, wallet]);
134
+ }, [client, wallet, disconnect]);
124
135
 
125
136
  const register = useCallback(async (username: string, referralCode?: string, avatarUrl?: string) => {
126
137
  try {
@@ -47,7 +47,7 @@ export function useCreateCustomGame() {
47
47
  // 3. Confirm with backend
48
48
  setStatus('confirming');
49
49
  console.log('[useCreateCustomGame] Step 3: Confirming with backend...');
50
- await client.confirmCustomGame({
50
+ const confirmResult = await client.confirmCustomGame({
51
51
  gameId: createResult.gameId,
52
52
  playerWallet: params.playerWallet,
53
53
  signature,
@@ -58,12 +58,11 @@ export function useCreateCustomGame() {
58
58
  });
59
59
  console.log('[useCreateCustomGame] Step 3 done.');
60
60
 
61
- const explorerUrl = `https://solscan.io/tx/${signature}`;
62
61
  const result: CreateCustomGameMutationResult = {
63
62
  gameId: createResult.gameId,
64
63
  gameAddress: createResult.gameAddress,
65
64
  signature,
66
- explorerUrl,
65
+ explorerUrl: confirmResult.explorerUrl,
67
66
  buyIn: params.wagerAmount,
68
67
  };
69
68
 
@@ -46,7 +46,7 @@ export function useCreateGame() {
46
46
  // 3. Confirm with backend (server handles on-chain verification)
47
47
  setStatus('confirming');
48
48
  console.log('[useCreateGame] Step 3: Confirming with backend...');
49
- await client.confirmGame({
49
+ const confirmResult = await client.confirmGame({
50
50
  gameId: createResult.gameId,
51
51
  playerWallet: params.playerWallet,
52
52
  signature,
@@ -57,12 +57,11 @@ export function useCreateGame() {
57
57
  });
58
58
  console.log('[useCreateGame] Step 3 done.');
59
59
 
60
- const explorerUrl = `https://solscan.io/tx/${signature}`;
61
60
  const result: CreateGameMutationResult = {
62
61
  gameId: createResult.gameId,
63
62
  gameAddress: createResult.gameAddress,
64
63
  signature,
65
- explorerUrl,
64
+ explorerUrl: confirmResult.explorerUrl,
66
65
  };
67
66
 
68
67
  setData(result);
@@ -55,15 +55,14 @@ export function useJoinGame() {
55
55
  gameAddress: joinResult.gameAddress,
56
56
  };
57
57
  console.log('[useJoinGame] Step 3: Confirming with backend...', confirmParams);
58
- await client.confirmGame(confirmParams);
58
+ const confirmResult = await client.confirmGame(confirmParams);
59
59
  console.log('[useJoinGame] Step 3 done. Backend confirmed.');
60
60
 
61
- const explorerUrl = `https://solscan.io/tx/${signature}`;
62
61
  const result: JoinGameMutationResult = {
63
62
  gameId: params.gameId,
64
63
  gameAddress: joinResult.gameAddress,
65
64
  signature,
66
- explorerUrl,
65
+ explorerUrl: confirmResult.explorerUrl,
67
66
  };
68
67
 
69
68
  setData(result);
package/src/provider.tsx CHANGED
@@ -86,20 +86,26 @@ export function DubsProvider({
86
86
  const config = NETWORK_CONFIG[network];
87
87
  const baseUrl = baseUrlOverride || config.baseUrl;
88
88
  const rpcUrl = rpcUrlOverride || config.rpcUrl;
89
- const cluster = config.cluster;
90
89
 
91
90
  // Create stable instances
92
91
  const client = useMemo(() => new DubsClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
93
- const connection = useMemo(() => new Connection(rpcUrl, { commitment: 'confirmed' }), [rpcUrl]);
94
92
  const storage = useMemo(() => tokenStorage || createSecureStoreStorage(), [tokenStorage]);
95
93
 
96
94
  // Fetch developer UI config on mount (silent fail = default theme)
95
+ // Also auto-detects sandbox/production environment from the API key
97
96
  const [uiConfig, setUiConfig] = useState<UiConfig | null>(null);
97
+ const [resolvedNetwork, setResolvedNetwork] = useState<DubsNetwork>(network);
98
98
  useEffect(() => {
99
99
  client.getAppConfig()
100
- .then((config) => {
101
- console.log('[DubsProvider] UI config loaded:', JSON.stringify(config));
102
- setUiConfig(config);
100
+ .then((cfg) => {
101
+ console.log('[DubsProvider] UI config loaded:', JSON.stringify(cfg));
102
+ setUiConfig(cfg);
103
+ // Auto-detect network from API key environment (sandbox → devnet)
104
+ // Only override if the user didn't explicitly set network or rpcUrl
105
+ if (cfg.environment === 'sandbox' && network === 'mainnet-beta' && !rpcUrlOverride) {
106
+ console.log('[DubsProvider] Sandbox API key detected — auto-switching to devnet');
107
+ setResolvedNetwork('devnet');
108
+ }
103
109
  })
104
110
  .catch((err) => {
105
111
  console.log('[DubsProvider] UI config fetch failed, using defaults:', err?.message);
@@ -110,6 +116,14 @@ export function DubsProvider({
110
116
  // Wait for config before rendering so accent color is applied on first paint
111
117
  if (uiConfig === null) return null;
112
118
 
119
+ // Use resolved network (may differ from prop if sandbox was auto-detected)
120
+ const resolvedConfig = NETWORK_CONFIG[resolvedNetwork];
121
+ const resolvedRpcUrl = rpcUrlOverride || resolvedConfig.rpcUrl;
122
+ const cluster = resolvedConfig.cluster;
123
+
124
+ // Connection uses the resolved RPC (auto-switched to devnet for sandbox keys)
125
+ const connection = useMemo(() => new Connection(resolvedRpcUrl, { commitment: 'confirmed' }), [resolvedRpcUrl]);
126
+
113
127
  // Build theme overrides from server config so all SDK components respect the tint
114
128
  const themeOverrides: Partial<DubsTheme> = {};
115
129
  if (uiConfig.accentColor) {
@@ -125,7 +139,7 @@ export function DubsProvider({
125
139
  connection={connection}
126
140
  wallet={externalWallet}
127
141
  appName={uiConfig.appName || appName}
128
- network={network}
142
+ network={resolvedNetwork}
129
143
  storage={storage}
130
144
  managed={managed}
131
145
  renderLoading={renderLoading}
@@ -161,7 +175,7 @@ export function DubsProvider({
161
175
  connection={connection}
162
176
  wallet={adapter}
163
177
  appName={uiConfig.appName || appName}
164
- network={network}
178
+ network={resolvedNetwork}
165
179
  storage={storage}
166
180
  renderLoading={renderLoading}
167
181
  renderError={renderError}
package/src/types.ts CHANGED
@@ -489,4 +489,5 @@ export interface UiConfig {
489
489
  appName?: string;
490
490
  appUrl?: string;
491
491
  tagline?: string;
492
+ environment?: 'sandbox' | 'production';
492
493
  }