@dubsdotapp/expo 0.2.5 → 0.2.6

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/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AuthGate: () => AuthGate,
34
34
  ConnectWalletScreen: () => ConnectWalletScreen,
35
+ CreateCustomGameSheet: () => CreateCustomGameSheet,
35
36
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
36
37
  DEFAULT_RPC_URL: () => DEFAULT_RPC_URL,
37
38
  DubsApiError: () => DubsApiError,
@@ -55,6 +56,7 @@ __export(index_exports, {
55
56
  useAppConfig: () => useAppConfig,
56
57
  useAuth: () => useAuth,
57
58
  useClaim: () => useClaim,
59
+ useCreateCustomGame: () => useCreateCustomGame,
58
60
  useCreateGame: () => useCreateGame,
59
61
  useDubs: () => useDubs,
60
62
  useDubsTheme: () => useDubsTheme,
@@ -327,6 +329,33 @@ var DubsClient = class {
327
329
  message: res.message
328
330
  };
329
331
  }
332
+ // ── Custom Game Lifecycle (game_mode=6) ──
333
+ async createCustomGame(params) {
334
+ const res = await this.request(
335
+ "POST",
336
+ "/games/custom/create",
337
+ params
338
+ );
339
+ return {
340
+ gameId: res.gameId,
341
+ gameAddress: res.gameAddress,
342
+ transaction: res.transaction,
343
+ lockTimestamp: res.lockTimestamp
344
+ };
345
+ }
346
+ async confirmCustomGame(params) {
347
+ const res = await this.request(
348
+ "POST",
349
+ "/games/custom/confirm",
350
+ params
351
+ );
352
+ return {
353
+ gameId: res.gameId,
354
+ signature: res.signature,
355
+ explorerUrl: res.explorerUrl,
356
+ message: res.message
357
+ };
358
+ }
330
359
  async buildClaimTransaction(params) {
331
360
  const res = await this.request(
332
361
  "POST",
@@ -522,7 +551,7 @@ function createSecureStoreStorage() {
522
551
  }
523
552
 
524
553
  // src/provider.tsx
525
- var import_react12 = require("react");
554
+ var import_react13 = require("react");
526
555
  var import_web33 = require("@solana/web3.js");
527
556
 
528
557
  // src/managed-wallet.tsx
@@ -910,7 +939,7 @@ function ManagedWalletProvider({
910
939
  }
911
940
 
912
941
  // src/ui/AuthGate.tsx
913
- var import_react11 = __toESM(require("react"));
942
+ var import_react12 = __toESM(require("react"));
914
943
  var import_react_native3 = require("react-native");
915
944
 
916
945
  // src/hooks/useEvents.ts
@@ -1206,24 +1235,85 @@ function useClaim() {
1206
1235
  return { execute, status, error, data, reset };
1207
1236
  }
1208
1237
 
1238
+ // src/hooks/useCreateCustomGame.ts
1239
+ var import_react9 = require("react");
1240
+ function useCreateCustomGame() {
1241
+ const { client, wallet } = useDubs();
1242
+ const [status, setStatus] = (0, import_react9.useState)("idle");
1243
+ const [error, setError] = (0, import_react9.useState)(null);
1244
+ const [data, setData] = (0, import_react9.useState)(null);
1245
+ const reset = (0, import_react9.useCallback)(() => {
1246
+ setStatus("idle");
1247
+ setError(null);
1248
+ setData(null);
1249
+ }, []);
1250
+ const execute = (0, import_react9.useCallback)(async (params) => {
1251
+ setStatus("building");
1252
+ setError(null);
1253
+ setData(null);
1254
+ try {
1255
+ console.log("[useCreateCustomGame] Step 1: Building transaction...");
1256
+ const createResult = await client.createCustomGame(params);
1257
+ console.log("[useCreateCustomGame] Step 1 done:", { gameId: createResult.gameId, gameAddress: createResult.gameAddress });
1258
+ setStatus("signing");
1259
+ console.log("[useCreateCustomGame] Step 2: Signing and sending...");
1260
+ const signature = await signAndSendBase64Transaction(
1261
+ createResult.transaction,
1262
+ wallet
1263
+ );
1264
+ console.log("[useCreateCustomGame] Step 2 done. Signature:", signature);
1265
+ setStatus("confirming");
1266
+ console.log("[useCreateCustomGame] Step 3: Confirming with backend...");
1267
+ await client.confirmCustomGame({
1268
+ gameId: createResult.gameId,
1269
+ playerWallet: params.playerWallet,
1270
+ signature,
1271
+ teamChoice: params.teamChoice,
1272
+ wagerAmount: params.wagerAmount,
1273
+ role: "creator",
1274
+ gameAddress: createResult.gameAddress
1275
+ });
1276
+ console.log("[useCreateCustomGame] Step 3 done.");
1277
+ const explorerUrl = `https://solscan.io/tx/${signature}`;
1278
+ const result = {
1279
+ gameId: createResult.gameId,
1280
+ gameAddress: createResult.gameAddress,
1281
+ signature,
1282
+ explorerUrl
1283
+ };
1284
+ setData(result);
1285
+ setStatus("success");
1286
+ console.log("[useCreateCustomGame] Complete!");
1287
+ return result;
1288
+ } catch (err) {
1289
+ console.error("[useCreateCustomGame] FAILED:", err);
1290
+ const error2 = err instanceof Error ? err : new Error(String(err));
1291
+ setError(error2);
1292
+ setStatus("error");
1293
+ throw error2;
1294
+ }
1295
+ }, [client, wallet]);
1296
+ return { execute, status, error, data, reset };
1297
+ }
1298
+
1209
1299
  // src/hooks/useAuth.ts
1210
- var import_react10 = require("react");
1300
+ var import_react11 = require("react");
1211
1301
  var import_bs58 = __toESM(require("bs58"));
1212
1302
 
1213
1303
  // src/auth-context.ts
1214
- var import_react9 = require("react");
1215
- var AuthContext = (0, import_react9.createContext)(null);
1304
+ var import_react10 = require("react");
1305
+ var AuthContext = (0, import_react10.createContext)(null);
1216
1306
 
1217
1307
  // src/hooks/useAuth.ts
1218
1308
  function useAuth() {
1219
- const sharedAuth = (0, import_react10.useContext)(AuthContext);
1309
+ const sharedAuth = (0, import_react11.useContext)(AuthContext);
1220
1310
  const { client, wallet } = useDubs();
1221
- const [status, setStatus] = (0, import_react10.useState)("idle");
1222
- const [user, setUser] = (0, import_react10.useState)(null);
1223
- const [token, setToken] = (0, import_react10.useState)(null);
1224
- const [error, setError] = (0, import_react10.useState)(null);
1225
- const pendingAuth = (0, import_react10.useRef)(null);
1226
- const reset = (0, import_react10.useCallback)(() => {
1311
+ const [status, setStatus] = (0, import_react11.useState)("idle");
1312
+ const [user, setUser] = (0, import_react11.useState)(null);
1313
+ const [token, setToken] = (0, import_react11.useState)(null);
1314
+ const [error, setError] = (0, import_react11.useState)(null);
1315
+ const pendingAuth = (0, import_react11.useRef)(null);
1316
+ const reset = (0, import_react11.useCallback)(() => {
1227
1317
  setStatus("idle");
1228
1318
  setUser(null);
1229
1319
  setToken(null);
@@ -1231,7 +1321,7 @@ function useAuth() {
1231
1321
  pendingAuth.current = null;
1232
1322
  client.setToken(null);
1233
1323
  }, [client]);
1234
- const authenticate = (0, import_react10.useCallback)(async () => {
1324
+ const authenticate = (0, import_react11.useCallback)(async () => {
1235
1325
  try {
1236
1326
  if (!wallet.publicKey) {
1237
1327
  throw new Error("Wallet not connected");
@@ -1262,7 +1352,7 @@ function useAuth() {
1262
1352
  setStatus("error");
1263
1353
  }
1264
1354
  }, [client, wallet]);
1265
- const register = (0, import_react10.useCallback)(async (username, referralCode, avatarUrl) => {
1355
+ const register = (0, import_react11.useCallback)(async (username, referralCode, avatarUrl) => {
1266
1356
  try {
1267
1357
  const pending = pendingAuth.current;
1268
1358
  if (!pending) {
@@ -1288,7 +1378,7 @@ function useAuth() {
1288
1378
  setStatus("error");
1289
1379
  }
1290
1380
  }, [client]);
1291
- const logout = (0, import_react10.useCallback)(async () => {
1381
+ const logout = (0, import_react11.useCallback)(async () => {
1292
1382
  try {
1293
1383
  await client.logout();
1294
1384
  } catch {
@@ -1299,7 +1389,7 @@ function useAuth() {
1299
1389
  setError(null);
1300
1390
  pendingAuth.current = null;
1301
1391
  }, [client]);
1302
- const restoreSession = (0, import_react10.useCallback)(async (savedToken) => {
1392
+ const restoreSession = (0, import_react11.useCallback)(async (savedToken) => {
1303
1393
  try {
1304
1394
  client.setToken(savedToken);
1305
1395
  const me = await client.getMe();
@@ -1358,9 +1448,9 @@ function AuthGate({
1358
1448
  }) {
1359
1449
  const { client } = useDubs();
1360
1450
  const auth = useAuth();
1361
- const [phase, setPhase] = (0, import_react11.useState)("init");
1362
- const [registrationPhase, setRegistrationPhase] = (0, import_react11.useState)(false);
1363
- (0, import_react11.useEffect)(() => {
1451
+ const [phase, setPhase] = (0, import_react12.useState)("init");
1452
+ const [registrationPhase, setRegistrationPhase] = (0, import_react12.useState)(false);
1453
+ (0, import_react12.useEffect)(() => {
1364
1454
  let cancelled = false;
1365
1455
  (async () => {
1366
1456
  try {
@@ -1386,18 +1476,18 @@ function AuthGate({
1386
1476
  cancelled = true;
1387
1477
  };
1388
1478
  }, []);
1389
- (0, import_react11.useEffect)(() => {
1479
+ (0, import_react12.useEffect)(() => {
1390
1480
  if (auth.status === "needsRegistration") setRegistrationPhase(true);
1391
1481
  }, [auth.status]);
1392
- (0, import_react11.useEffect)(() => {
1482
+ (0, import_react12.useEffect)(() => {
1393
1483
  if (auth.token) onSaveToken(auth.token);
1394
1484
  }, [auth.token]);
1395
- const retry = (0, import_react11.useCallback)(() => {
1485
+ const retry = (0, import_react12.useCallback)(() => {
1396
1486
  setRegistrationPhase(false);
1397
1487
  auth.reset();
1398
1488
  auth.authenticate();
1399
1489
  }, [auth]);
1400
- const handleRegister = (0, import_react11.useCallback)(
1490
+ const handleRegister = (0, import_react12.useCallback)(
1401
1491
  (username, referralCode, avatarUrl) => {
1402
1492
  auth.register(username, referralCode, avatarUrl);
1403
1493
  },
@@ -1476,7 +1566,7 @@ function DefaultErrorScreen({ error, onRetry, appName, accentColor }) {
1476
1566
  function StepIndicator({ currentStep }) {
1477
1567
  const t = useDubsTheme();
1478
1568
  const steps = [0, 1, 2, 3];
1479
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: s.stepRow, children: steps.map((i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react11.default.Fragment, { children: [
1569
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: s.stepRow, children: steps.map((i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react12.default.Fragment, { children: [
1480
1570
  i > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [s.stepLine, { backgroundColor: i <= currentStep ? t.success : t.border }] }),
1481
1571
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1482
1572
  import_react_native3.View,
@@ -1500,19 +1590,19 @@ function DefaultRegistrationScreen({
1500
1590
  }) {
1501
1591
  const t = useDubsTheme();
1502
1592
  const accent = accentColor || t.accent;
1503
- const [step, setStep] = (0, import_react11.useState)(0);
1504
- const [avatarSeed, setAvatarSeed] = (0, import_react11.useState)(generateSeed);
1505
- const [avatarStyle, setAvatarStyle] = (0, import_react11.useState)("adventurer");
1506
- const [showStyles, setShowStyles] = (0, import_react11.useState)(false);
1507
- const [username, setUsername] = (0, import_react11.useState)("");
1508
- const [referralCode, setReferralCode] = (0, import_react11.useState)("");
1509
- const [checking, setChecking] = (0, import_react11.useState)(false);
1510
- const [availability, setAvailability] = (0, import_react11.useState)(null);
1511
- const debounceRef = (0, import_react11.useRef)(null);
1512
- const fadeAnim = (0, import_react11.useRef)(new import_react_native3.Animated.Value(1)).current;
1513
- const slideAnim = (0, import_react11.useRef)(new import_react_native3.Animated.Value(0)).current;
1593
+ const [step, setStep] = (0, import_react12.useState)(0);
1594
+ const [avatarSeed, setAvatarSeed] = (0, import_react12.useState)(generateSeed);
1595
+ const [avatarStyle, setAvatarStyle] = (0, import_react12.useState)("adventurer");
1596
+ const [showStyles, setShowStyles] = (0, import_react12.useState)(false);
1597
+ const [username, setUsername] = (0, import_react12.useState)("");
1598
+ const [referralCode, setReferralCode] = (0, import_react12.useState)("");
1599
+ const [checking, setChecking] = (0, import_react12.useState)(false);
1600
+ const [availability, setAvailability] = (0, import_react12.useState)(null);
1601
+ const debounceRef = (0, import_react12.useRef)(null);
1602
+ const fadeAnim = (0, import_react12.useRef)(new import_react_native3.Animated.Value(1)).current;
1603
+ const slideAnim = (0, import_react12.useRef)(new import_react_native3.Animated.Value(0)).current;
1514
1604
  const avatarUrl = getAvatarUrl(avatarStyle, avatarSeed);
1515
- (0, import_react11.useEffect)(() => {
1605
+ (0, import_react12.useEffect)(() => {
1516
1606
  if (debounceRef.current) clearTimeout(debounceRef.current);
1517
1607
  const trimmed = username.trim();
1518
1608
  if (trimmed.length < 3) {
@@ -1535,7 +1625,7 @@ function DefaultRegistrationScreen({
1535
1625
  if (debounceRef.current) clearTimeout(debounceRef.current);
1536
1626
  };
1537
1627
  }, [username, client]);
1538
- const animateToStep = (0, import_react11.useCallback)((newStep) => {
1628
+ const animateToStep = (0, import_react12.useCallback)((newStep) => {
1539
1629
  const dir = newStep > step ? 1 : -1;
1540
1630
  import_react_native3.Keyboard.dismiss();
1541
1631
  import_react_native3.Animated.parallel([
@@ -1834,7 +1924,7 @@ var s = import_react_native3.StyleSheet.create({
1834
1924
 
1835
1925
  // src/provider.tsx
1836
1926
  var import_jsx_runtime4 = require("react/jsx-runtime");
1837
- var DubsContext = (0, import_react12.createContext)(null);
1927
+ var DubsContext = (0, import_react13.createContext)(null);
1838
1928
  function DubsProvider({
1839
1929
  apiKey,
1840
1930
  children,
@@ -1854,11 +1944,11 @@ function DubsProvider({
1854
1944
  const baseUrl = baseUrlOverride || config.baseUrl;
1855
1945
  const rpcUrl = rpcUrlOverride || config.rpcUrl;
1856
1946
  const cluster = config.cluster;
1857
- const client = (0, import_react12.useMemo)(() => new DubsClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
1858
- const connection = (0, import_react12.useMemo)(() => new import_web33.Connection(rpcUrl, { commitment: "confirmed" }), [rpcUrl]);
1859
- const storage = (0, import_react12.useMemo)(() => tokenStorage || createSecureStoreStorage(), [tokenStorage]);
1860
- const [uiConfig, setUiConfig] = (0, import_react12.useState)(null);
1861
- (0, import_react12.useEffect)(() => {
1947
+ const client = (0, import_react13.useMemo)(() => new DubsClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
1948
+ const connection = (0, import_react13.useMemo)(() => new import_web33.Connection(rpcUrl, { commitment: "confirmed" }), [rpcUrl]);
1949
+ const storage = (0, import_react13.useMemo)(() => tokenStorage || createSecureStoreStorage(), [tokenStorage]);
1950
+ const [uiConfig, setUiConfig] = (0, import_react13.useState)(null);
1951
+ (0, import_react13.useEffect)(() => {
1862
1952
  client.getAppConfig().then((config2) => {
1863
1953
  console.log("[DubsProvider] UI config loaded:", JSON.stringify(config2));
1864
1954
  setUiConfig(config2);
@@ -1933,11 +2023,11 @@ function ManagedInner({
1933
2023
  children
1934
2024
  }) {
1935
2025
  const managedDisconnect = useDisconnect();
1936
- const disconnect = (0, import_react12.useCallback)(async () => {
2026
+ const disconnect = (0, import_react13.useCallback)(async () => {
1937
2027
  client.setToken(null);
1938
2028
  await managedDisconnect?.();
1939
2029
  }, [client, managedDisconnect]);
1940
- const value = (0, import_react12.useMemo)(
2030
+ const value = (0, import_react13.useMemo)(
1941
2031
  () => ({ client, wallet, connection, appName, network, disconnect, uiConfig }),
1942
2032
  [client, wallet, connection, appName, network, disconnect, uiConfig]
1943
2033
  );
@@ -1973,13 +2063,13 @@ function ExternalWalletProvider({
1973
2063
  uiConfig,
1974
2064
  children
1975
2065
  }) {
1976
- const disconnect = (0, import_react12.useCallback)(async () => {
2066
+ const disconnect = (0, import_react13.useCallback)(async () => {
1977
2067
  client.setToken(null);
1978
2068
  await storage.deleteItem(STORAGE_KEYS.JWT_TOKEN).catch(() => {
1979
2069
  });
1980
2070
  await wallet.disconnect?.();
1981
2071
  }, [client, storage, wallet]);
1982
- const value = (0, import_react12.useMemo)(
2072
+ const value = (0, import_react13.useMemo)(
1983
2073
  () => ({ client, wallet, connection, appName, network, disconnect, uiConfig }),
1984
2074
  [client, wallet, connection, appName, network, disconnect, uiConfig]
1985
2075
  );
@@ -2004,19 +2094,19 @@ function ExternalWalletProvider({
2004
2094
  ) });
2005
2095
  }
2006
2096
  function useDubs() {
2007
- const ctx = (0, import_react12.useContext)(DubsContext);
2097
+ const ctx = (0, import_react13.useContext)(DubsContext);
2008
2098
  if (!ctx) {
2009
2099
  throw new Error("useDubs must be used within a <DubsProvider>");
2010
2100
  }
2011
2101
  return ctx;
2012
2102
  }
2013
2103
  function useAppConfig() {
2014
- const ctx = (0, import_react12.useContext)(DubsContext);
2104
+ const ctx = (0, import_react13.useContext)(DubsContext);
2015
2105
  return ctx?.uiConfig || {};
2016
2106
  }
2017
2107
 
2018
2108
  // src/ui/UserProfileCard.tsx
2019
- var import_react13 = require("react");
2109
+ var import_react14 = require("react");
2020
2110
  var import_react_native4 = require("react-native");
2021
2111
  var import_jsx_runtime5 = require("react/jsx-runtime");
2022
2112
  function truncateAddress(address, chars = 4) {
@@ -2036,7 +2126,7 @@ function UserProfileCard({
2036
2126
  memberSince
2037
2127
  }) {
2038
2128
  const t = useDubsTheme();
2039
- const imageUri = (0, import_react13.useMemo)(
2129
+ const imageUri = (0, import_react14.useMemo)(
2040
2130
  () => avatarUrl || `https://api.dicebear.com/9.x/avataaars/png?seed=${walletAddress}&size=128`,
2041
2131
  [avatarUrl, walletAddress]
2042
2132
  );
@@ -2229,7 +2319,7 @@ var styles3 = import_react_native5.StyleSheet.create({
2229
2319
  });
2230
2320
 
2231
2321
  // src/ui/game/GamePoster.tsx
2232
- var import_react14 = require("react");
2322
+ var import_react15 = require("react");
2233
2323
  var import_react_native6 = require("react-native");
2234
2324
  var import_jsx_runtime7 = require("react/jsx-runtime");
2235
2325
  function computeCountdown(lockTimestamp) {
@@ -2279,7 +2369,7 @@ function GamePoster({ game, ImageComponent }) {
2279
2369
  ] });
2280
2370
  }
2281
2371
  function TeamLogoInternal({ url, size, Img }) {
2282
- const [failed, setFailed] = (0, import_react14.useState)(false);
2372
+ const [failed, setFailed] = (0, import_react15.useState)(false);
2283
2373
  if (!url || failed) {
2284
2374
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.View, { style: [styles4.logoPlaceholder, { width: size, height: size, borderRadius: size / 2 }] });
2285
2375
  }
@@ -2380,7 +2470,7 @@ var styles4 = import_react_native6.StyleSheet.create({
2380
2470
  });
2381
2471
 
2382
2472
  // src/ui/game/LivePoolsCard.tsx
2383
- var import_react15 = require("react");
2473
+ var import_react16 = require("react");
2384
2474
  var import_react_native7 = require("react-native");
2385
2475
  var import_jsx_runtime8 = require("react/jsx-runtime");
2386
2476
  function LivePoolsCard({
@@ -2396,7 +2486,7 @@ function LivePoolsCard({
2396
2486
  const homePool = game.homePool || 0;
2397
2487
  const awayPool = game.awayPool || 0;
2398
2488
  const totalPool = game.totalPool || 0;
2399
- const { homePercent, awayPercent, homeOdds, awayOdds } = (0, import_react15.useMemo)(() => {
2489
+ const { homePercent, awayPercent, homeOdds, awayOdds } = (0, import_react16.useMemo)(() => {
2400
2490
  return {
2401
2491
  homePercent: totalPool > 0 ? homePool / totalPool * 100 : 50,
2402
2492
  awayPercent: totalPool > 0 ? awayPool / totalPool * 100 : 50,
@@ -2459,7 +2549,7 @@ var styles5 = import_react_native7.StyleSheet.create({
2459
2549
  });
2460
2550
 
2461
2551
  // src/ui/game/PickWinnerCard.tsx
2462
- var import_react16 = require("react");
2552
+ var import_react17 = require("react");
2463
2553
  var import_react_native8 = require("react-native");
2464
2554
  var import_jsx_runtime9 = require("react/jsx-runtime");
2465
2555
  function PickWinnerCard({
@@ -2477,7 +2567,7 @@ function PickWinnerCard({
2477
2567
  const totalPool = game.totalPool || 0;
2478
2568
  const homePool = game.homePool || 0;
2479
2569
  const awayPool = game.awayPool || 0;
2480
- const { homeOdds, awayOdds, homeBets, awayBets } = (0, import_react16.useMemo)(() => ({
2570
+ const { homeOdds, awayOdds, homeBets, awayBets } = (0, import_react17.useMemo)(() => ({
2481
2571
  homeOdds: homePool > 0 ? (totalPool / homePool).toFixed(2) : "\u2014",
2482
2572
  awayOdds: awayPool > 0 ? (totalPool / awayPool).toFixed(2) : "\u2014",
2483
2573
  homeBets: bettors.filter((b) => b.team === "home").length,
@@ -2530,7 +2620,7 @@ function TeamOption({
2530
2620
  ImageComponent,
2531
2621
  t
2532
2622
  }) {
2533
- const [imgFailed, setImgFailed] = (0, import_react16.useState)(false);
2623
+ const [imgFailed, setImgFailed] = (0, import_react17.useState)(false);
2534
2624
  const Img = ImageComponent || require("react-native").Image;
2535
2625
  const showImage = imageUrl && !imgFailed;
2536
2626
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
@@ -2571,7 +2661,7 @@ var styles6 = import_react_native8.StyleSheet.create({
2571
2661
  });
2572
2662
 
2573
2663
  // src/ui/game/PlayersCard.tsx
2574
- var import_react17 = require("react");
2664
+ var import_react18 = require("react");
2575
2665
  var import_react_native9 = require("react-native");
2576
2666
  var import_jsx_runtime10 = require("react/jsx-runtime");
2577
2667
  function truncateWallet(addr, chars) {
@@ -2620,7 +2710,7 @@ function BettorRow({
2620
2710
  ImageComponent,
2621
2711
  t
2622
2712
  }) {
2623
- const [imgFailed, setImgFailed] = (0, import_react17.useState)(false);
2713
+ const [imgFailed, setImgFailed] = (0, import_react18.useState)(false);
2624
2714
  const Img = ImageComponent || require("react-native").Image;
2625
2715
  const showAvatar = bettor.avatar && !imgFailed;
2626
2716
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native9.View, { style: [styles7.row, !isFirst && { borderTopColor: t.border, borderTopWidth: 1 }], children: [
@@ -2647,7 +2737,7 @@ var styles7 = import_react_native9.StyleSheet.create({
2647
2737
  });
2648
2738
 
2649
2739
  // src/ui/game/JoinGameButton.tsx
2650
- var import_react18 = require("react");
2740
+ var import_react19 = require("react");
2651
2741
  var import_react_native10 = require("react-native");
2652
2742
  var import_jsx_runtime11 = require("react/jsx-runtime");
2653
2743
  var STATUS_LABELS = {
@@ -2658,7 +2748,7 @@ var STATUS_LABELS = {
2658
2748
  };
2659
2749
  function JoinGameButton({ game, walletAddress, selectedTeam, status, onJoin }) {
2660
2750
  const t = useDubsTheme();
2661
- const alreadyJoined = (0, import_react18.useMemo)(() => {
2751
+ const alreadyJoined = (0, import_react19.useMemo)(() => {
2662
2752
  if (!walletAddress) return false;
2663
2753
  return (game.bettors || []).some((b) => b.wallet === walletAddress);
2664
2754
  }, [game.bettors, walletAddress]);
@@ -2697,10 +2787,357 @@ var styles8 = import_react_native10.StyleSheet.create({
2697
2787
  buttonText: { color: "#000", fontSize: 16, fontWeight: "800" },
2698
2788
  joiningRow: { flexDirection: "row", alignItems: "center", gap: 10 }
2699
2789
  });
2790
+
2791
+ // src/ui/game/CreateCustomGameSheet.tsx
2792
+ var import_react20 = require("react");
2793
+ var import_react_native11 = require("react-native");
2794
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2795
+ var STATUS_LABELS2 = {
2796
+ building: "Building transaction...",
2797
+ signing: "Approve in wallet...",
2798
+ confirming: "Confirming...",
2799
+ success: "Game Created!"
2800
+ };
2801
+ function CreateCustomGameSheet({
2802
+ visible,
2803
+ onDismiss,
2804
+ title,
2805
+ maxPlayers = 2,
2806
+ fee = 5,
2807
+ presetAmounts = [0.1, 0.5, 1],
2808
+ metadata,
2809
+ onSuccess,
2810
+ onError
2811
+ }) {
2812
+ const t = useDubsTheme();
2813
+ const { wallet } = useDubs();
2814
+ const mutation = useCreateCustomGame();
2815
+ const [selectedAmount, setSelectedAmount] = (0, import_react20.useState)(null);
2816
+ const [customAmount, setCustomAmount] = (0, import_react20.useState)("");
2817
+ const [isCustom, setIsCustom] = (0, import_react20.useState)(false);
2818
+ const overlayOpacity = (0, import_react20.useRef)(new import_react_native11.Animated.Value(0)).current;
2819
+ (0, import_react20.useEffect)(() => {
2820
+ import_react_native11.Animated.timing(overlayOpacity, {
2821
+ toValue: visible ? 1 : 0,
2822
+ duration: 250,
2823
+ useNativeDriver: true
2824
+ }).start();
2825
+ }, [visible, overlayOpacity]);
2826
+ (0, import_react20.useEffect)(() => {
2827
+ if (visible) {
2828
+ setSelectedAmount(null);
2829
+ setCustomAmount("");
2830
+ setIsCustom(false);
2831
+ mutation.reset();
2832
+ }
2833
+ }, [visible]);
2834
+ (0, import_react20.useEffect)(() => {
2835
+ if (mutation.status === "success" && mutation.data) {
2836
+ onSuccess?.(mutation.data);
2837
+ const timer = setTimeout(() => {
2838
+ onDismiss();
2839
+ }, 1500);
2840
+ return () => clearTimeout(timer);
2841
+ }
2842
+ }, [mutation.status, mutation.data]);
2843
+ (0, import_react20.useEffect)(() => {
2844
+ if (mutation.status === "error" && mutation.error) {
2845
+ onError?.(mutation.error);
2846
+ }
2847
+ }, [mutation.status, mutation.error]);
2848
+ const handlePresetSelect = (0, import_react20.useCallback)((amount) => {
2849
+ setSelectedAmount(amount);
2850
+ setIsCustom(false);
2851
+ setCustomAmount("");
2852
+ }, []);
2853
+ const handleCustomSelect = (0, import_react20.useCallback)(() => {
2854
+ setIsCustom(true);
2855
+ setSelectedAmount(null);
2856
+ }, []);
2857
+ const handleCustomAmountChange = (0, import_react20.useCallback)((text) => {
2858
+ const cleaned = text.replace(/[^0-9.]/g, "").replace(/(\..*?)\..*/g, "$1");
2859
+ setCustomAmount(cleaned);
2860
+ const parsed = parseFloat(cleaned);
2861
+ setSelectedAmount(parsed > 0 ? parsed : null);
2862
+ }, []);
2863
+ const effectiveAmount = selectedAmount;
2864
+ const playerCount = maxPlayers || 2;
2865
+ const pot = effectiveAmount ? effectiveAmount * playerCount : 0;
2866
+ const winnerTakes = pot * (1 - fee / 100);
2867
+ const isMutating = mutation.status !== "idle" && mutation.status !== "success" && mutation.status !== "error";
2868
+ const canCreate = effectiveAmount !== null && effectiveAmount > 0 && !isMutating && mutation.status !== "success";
2869
+ const handleCreate = (0, import_react20.useCallback)(async () => {
2870
+ if (!effectiveAmount || !wallet.publicKey) return;
2871
+ try {
2872
+ await mutation.execute({
2873
+ playerWallet: wallet.publicKey.toBase58(),
2874
+ teamChoice: "home",
2875
+ wagerAmount: effectiveAmount,
2876
+ title,
2877
+ maxPlayers,
2878
+ metadata
2879
+ });
2880
+ } catch {
2881
+ }
2882
+ }, [effectiveAmount, wallet.publicKey, mutation.execute, title, maxPlayers, metadata]);
2883
+ const statusLabel = STATUS_LABELS2[mutation.status] || "";
2884
+ const playersLabel = playerCount === 2 ? "2 (1v1)" : `${playerCount} players`;
2885
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2886
+ import_react_native11.Modal,
2887
+ {
2888
+ visible,
2889
+ animationType: "slide",
2890
+ transparent: true,
2891
+ onRequestClose: onDismiss,
2892
+ children: [
2893
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Animated.View, { style: [styles9.overlay, { opacity: overlayOpacity }], children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.TouchableOpacity, { style: styles9.overlayTap, activeOpacity: 1, onPress: onDismiss }) }),
2894
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2895
+ import_react_native11.KeyboardAvoidingView,
2896
+ {
2897
+ style: styles9.keyboardView,
2898
+ behavior: import_react_native11.Platform.OS === "ios" ? "padding" : void 0,
2899
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.View, { style: styles9.sheetPositioner, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { style: [styles9.sheet, { backgroundColor: t.background }], children: [
2900
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.View, { style: styles9.handleRow, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.View, { style: [styles9.handle, { backgroundColor: t.textMuted }] }) }),
2901
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { style: styles9.header, children: [
2902
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.headerTitle, { color: t.text }], children: "New Game" }),
2903
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.TouchableOpacity, { onPress: onDismiss, activeOpacity: 0.8, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.closeButton, { color: t.textMuted }], children: "\u2715" }) })
2904
+ ] }),
2905
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { style: styles9.section, children: [
2906
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.sectionLabel, { color: t.textSecondary }], children: "Buy-In Amount" }),
2907
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { style: styles9.chipsRow, children: [
2908
+ presetAmounts.map((amount) => {
2909
+ const active = !isCustom && selectedAmount === amount;
2910
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2911
+ import_react_native11.TouchableOpacity,
2912
+ {
2913
+ style: [
2914
+ styles9.chip,
2915
+ { borderColor: active ? t.accent : t.border },
2916
+ active && { backgroundColor: t.accent }
2917
+ ],
2918
+ onPress: () => handlePresetSelect(amount),
2919
+ activeOpacity: 0.8,
2920
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.Text, { style: [styles9.chipText, { color: active ? "#FFFFFF" : t.text }], children: [
2921
+ amount,
2922
+ " SOL"
2923
+ ] })
2924
+ },
2925
+ amount
2926
+ );
2927
+ }),
2928
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2929
+ import_react_native11.TouchableOpacity,
2930
+ {
2931
+ style: [
2932
+ styles9.chip,
2933
+ { borderColor: isCustom ? t.accent : t.border },
2934
+ isCustom && { backgroundColor: t.accent }
2935
+ ],
2936
+ onPress: handleCustomSelect,
2937
+ activeOpacity: 0.8,
2938
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.chipText, { color: isCustom ? "#FFFFFF" : t.text }], children: "Custom" })
2939
+ }
2940
+ )
2941
+ ] }),
2942
+ isCustom && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2943
+ import_react_native11.TextInput,
2944
+ {
2945
+ style: [styles9.input, { backgroundColor: t.surface, color: t.text, borderColor: t.accent }],
2946
+ placeholder: "Enter amount in SOL",
2947
+ placeholderTextColor: t.textDim,
2948
+ keyboardType: "decimal-pad",
2949
+ value: customAmount,
2950
+ onChangeText: handleCustomAmountChange,
2951
+ autoFocus: true
2952
+ }
2953
+ )
2954
+ ] }),
2955
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { style: [styles9.summaryCard, { backgroundColor: t.surface, borderColor: t.border }], children: [
2956
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { style: styles9.summaryRow, children: [
2957
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.summaryLabel, { color: t.textMuted }], children: "Your buy-in" }),
2958
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.summaryValue, { color: t.text }], children: effectiveAmount ? `${effectiveAmount} SOL` : "\u2014" })
2959
+ ] }),
2960
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.View, { style: [styles9.summarySep, { backgroundColor: t.border }] }),
2961
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { style: styles9.summaryRow, children: [
2962
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.summaryLabel, { color: t.textMuted }], children: "Players" }),
2963
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.summaryValue, { color: t.text }], children: playersLabel })
2964
+ ] }),
2965
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.View, { style: [styles9.summarySep, { backgroundColor: t.border }] }),
2966
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { style: styles9.summaryRow, children: [
2967
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.summaryLabel, { color: t.textMuted }], children: "Winner Takes" }),
2968
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { style: styles9.winnerCol, children: [
2969
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.summaryValue, { color: t.success }], children: effectiveAmount ? `${winnerTakes.toFixed(4)} SOL` : "\u2014" }),
2970
+ effectiveAmount ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.Text, { style: [styles9.feeNote, { color: t.textDim }], children: [
2971
+ fee,
2972
+ "% platform fee"
2973
+ ] }) : null
2974
+ ] })
2975
+ ] })
2976
+ ] }),
2977
+ mutation.error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.View, { style: [styles9.errorBox, { backgroundColor: t.errorBg, borderColor: t.errorBorder }], children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.errorText, { color: t.errorText }], children: mutation.error.message }) }),
2978
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2979
+ import_react_native11.TouchableOpacity,
2980
+ {
2981
+ style: [
2982
+ styles9.ctaButton,
2983
+ { backgroundColor: canCreate ? t.accent : t.border }
2984
+ ],
2985
+ disabled: !canCreate,
2986
+ onPress: handleCreate,
2987
+ activeOpacity: 0.8,
2988
+ children: isMutating ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { style: styles9.ctaLoading, children: [
2989
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.ActivityIndicator, { size: "small", color: "#FFFFFF" }),
2990
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: styles9.ctaText, children: statusLabel })
2991
+ ] }) : mutation.status === "success" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: styles9.ctaText, children: STATUS_LABELS2.success }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.Text, { style: [styles9.ctaText, !canCreate && { opacity: 0.5 }], children: effectiveAmount ? `Create Game \u2014 ${effectiveAmount} SOL` : "Select buy-in amount" })
2992
+ }
2993
+ )
2994
+ ] }) })
2995
+ }
2996
+ )
2997
+ ]
2998
+ }
2999
+ );
3000
+ }
3001
+ var styles9 = import_react_native11.StyleSheet.create({
3002
+ overlay: {
3003
+ ...import_react_native11.StyleSheet.absoluteFillObject,
3004
+ backgroundColor: "rgba(0,0,0,0.5)"
3005
+ },
3006
+ overlayTap: {
3007
+ flex: 1
3008
+ },
3009
+ keyboardView: {
3010
+ flex: 1,
3011
+ justifyContent: "flex-end"
3012
+ },
3013
+ sheetPositioner: {
3014
+ justifyContent: "flex-end"
3015
+ },
3016
+ sheet: {
3017
+ borderTopLeftRadius: 24,
3018
+ borderTopRightRadius: 24,
3019
+ paddingHorizontal: 20,
3020
+ paddingBottom: 40
3021
+ },
3022
+ handleRow: {
3023
+ alignItems: "center",
3024
+ paddingTop: 10,
3025
+ paddingBottom: 8
3026
+ },
3027
+ handle: {
3028
+ width: 36,
3029
+ height: 4,
3030
+ borderRadius: 2,
3031
+ opacity: 0.4
3032
+ },
3033
+ header: {
3034
+ flexDirection: "row",
3035
+ alignItems: "center",
3036
+ justifyContent: "space-between",
3037
+ paddingVertical: 12
3038
+ },
3039
+ headerTitle: {
3040
+ fontSize: 20,
3041
+ fontWeight: "700"
3042
+ },
3043
+ closeButton: {
3044
+ fontSize: 20,
3045
+ padding: 4
3046
+ },
3047
+ section: {
3048
+ gap: 12,
3049
+ paddingTop: 8
3050
+ },
3051
+ sectionLabel: {
3052
+ fontSize: 15,
3053
+ fontWeight: "600"
3054
+ },
3055
+ chipsRow: {
3056
+ flexDirection: "row",
3057
+ flexWrap: "wrap",
3058
+ gap: 10
3059
+ },
3060
+ chip: {
3061
+ paddingHorizontal: 18,
3062
+ paddingVertical: 10,
3063
+ borderRadius: 14,
3064
+ borderWidth: 1.5
3065
+ },
3066
+ chipText: {
3067
+ fontSize: 15,
3068
+ fontWeight: "600"
3069
+ },
3070
+ input: {
3071
+ height: 56,
3072
+ borderRadius: 16,
3073
+ borderWidth: 1.5,
3074
+ paddingHorizontal: 16,
3075
+ fontSize: 16
3076
+ },
3077
+ summaryCard: {
3078
+ marginTop: 20,
3079
+ borderRadius: 16,
3080
+ borderWidth: 1,
3081
+ overflow: "hidden"
3082
+ },
3083
+ summaryRow: {
3084
+ flexDirection: "row",
3085
+ alignItems: "center",
3086
+ justifyContent: "space-between",
3087
+ paddingHorizontal: 16,
3088
+ paddingVertical: 14
3089
+ },
3090
+ summaryLabel: {
3091
+ fontSize: 14
3092
+ },
3093
+ summaryValue: {
3094
+ fontSize: 15,
3095
+ fontWeight: "700"
3096
+ },
3097
+ summarySep: {
3098
+ height: 1,
3099
+ marginHorizontal: 16
3100
+ },
3101
+ winnerCol: {
3102
+ alignItems: "flex-end",
3103
+ gap: 2
3104
+ },
3105
+ feeNote: {
3106
+ fontSize: 11
3107
+ },
3108
+ errorBox: {
3109
+ marginTop: 16,
3110
+ borderRadius: 12,
3111
+ borderWidth: 1,
3112
+ padding: 12
3113
+ },
3114
+ errorText: {
3115
+ fontSize: 13,
3116
+ fontWeight: "500"
3117
+ },
3118
+ ctaButton: {
3119
+ marginTop: 20,
3120
+ height: 56,
3121
+ borderRadius: 14,
3122
+ justifyContent: "center",
3123
+ alignItems: "center"
3124
+ },
3125
+ ctaText: {
3126
+ color: "#FFFFFF",
3127
+ fontSize: 16,
3128
+ fontWeight: "700"
3129
+ },
3130
+ ctaLoading: {
3131
+ flexDirection: "row",
3132
+ alignItems: "center",
3133
+ gap: 10
3134
+ }
3135
+ });
2700
3136
  // Annotate the CommonJS export names for ESM import in node:
2701
3137
  0 && (module.exports = {
2702
3138
  AuthGate,
2703
3139
  ConnectWalletScreen,
3140
+ CreateCustomGameSheet,
2704
3141
  DEFAULT_BASE_URL,
2705
3142
  DEFAULT_RPC_URL,
2706
3143
  DubsApiError,
@@ -2724,6 +3161,7 @@ var styles8 = import_react_native10.StyleSheet.create({
2724
3161
  useAppConfig,
2725
3162
  useAuth,
2726
3163
  useClaim,
3164
+ useCreateCustomGame,
2727
3165
  useCreateGame,
2728
3166
  useDubs,
2729
3167
  useDubsTheme,