@dimcool/sdk 0.1.29 → 0.1.31

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.cjs CHANGED
@@ -214,6 +214,7 @@ __export(index_exports, {
214
214
  MIN_TRANSFER_AMOUNT: () => MIN_TRANSFER_AMOUNT,
215
215
  Markets: () => Markets,
216
216
  NodeStorage: () => NodeStorage,
217
+ NoopAnalyticsClient: () => NoopAnalyticsClient,
217
218
  Referrals: () => Referrals,
218
219
  Reports: () => Reports,
219
220
  SDK: () => SDK,
@@ -241,6 +242,7 @@ __export(index_exports, {
241
242
  formatMoneyMinor: () => import_utils.formatMoneyMinor,
242
243
  isRetryableError: () => isRetryableError,
243
244
  logger: () => logger,
245
+ selectGameLifecycleState: () => selectGameLifecycleState,
244
246
  toMajor: () => import_utils.toMajor,
245
247
  withRetry: () => withRetry
246
248
  });
@@ -748,6 +750,16 @@ var Auth = class {
748
750
  this.logger.debug("Token stored in storage", { tokenKey: TOKEN_KEY });
749
751
  return response;
750
752
  }
753
+ async loginWithExternalSignature(address, signedMessage, options) {
754
+ const response = await this.http.post("/auth/login-wallet", {
755
+ signedMessage,
756
+ address,
757
+ referralCode: options?.referralCode,
758
+ walletMeta: options?.walletMeta
759
+ });
760
+ this.storage.set(TOKEN_KEY, response.access_token);
761
+ return response;
762
+ }
751
763
  logout() {
752
764
  this.logger.debug("Auth.logout called");
753
765
  const hadToken = this.storage.get(TOKEN_KEY) !== null;
@@ -761,6 +773,10 @@ var Auth = class {
761
773
  });
762
774
  return isAuth;
763
775
  }
776
+ async getSessionStats() {
777
+ this.logger.debug("Auth.getSessionStats called");
778
+ return this.http.get("/auth/sessions/stats");
779
+ }
764
780
  async getLatestSessions(limit) {
765
781
  this.logger.debug("Auth.getLatestSessions called", { limit });
766
782
  const params = new URLSearchParams();
@@ -781,6 +797,9 @@ var Admin = class {
781
797
  this.http = http;
782
798
  this.logger = logger2;
783
799
  }
800
+ async getInternalBots() {
801
+ return this.http.get("/admin/internal-bots");
802
+ }
784
803
  async getUserById(id) {
785
804
  return this.http.get(`/admin/users/${id}`);
786
805
  }
@@ -988,11 +1007,25 @@ var Users = class {
988
1007
  async removeFriend(userId) {
989
1008
  return this.http.delete(`/friends/${userId}`);
990
1009
  }
991
- async getIncomingFriendRequests() {
992
- return this.http.get("/friends/requests/incoming");
1010
+ async getIncomingFriendRequests(opts) {
1011
+ const params = new URLSearchParams();
1012
+ if (opts?.limit !== void 0)
1013
+ params.append("limit", opts.limit.toString());
1014
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
1015
+ const qs = params.toString();
1016
+ return this.http.get(
1017
+ qs ? `/friends/requests/incoming?${qs}` : "/friends/requests/incoming"
1018
+ );
993
1019
  }
994
- async getOutgoingFriendRequests() {
995
- return this.http.get("/friends/requests/outgoing");
1020
+ async getOutgoingFriendRequests(opts) {
1021
+ const params = new URLSearchParams();
1022
+ if (opts?.limit !== void 0)
1023
+ params.append("limit", opts.limit.toString());
1024
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
1025
+ const qs = params.toString();
1026
+ return this.http.get(
1027
+ qs ? `/friends/requests/outgoing?${qs}` : "/friends/requests/outgoing"
1028
+ );
996
1029
  }
997
1030
  async acceptFriendRequest(userId) {
998
1031
  return this.http.post(
@@ -1081,6 +1114,9 @@ var FeatureFlags = class {
1081
1114
  this.loaded = true;
1082
1115
  return this.flags;
1083
1116
  }
1117
+ async getAdminFeatureFlags() {
1118
+ return this.http.get("/feature-flags/admin");
1119
+ }
1084
1120
  isEnabledFlag(name) {
1085
1121
  const flag = this.flags.find((f) => f.name === name);
1086
1122
  return flag?.enabled ?? false;
@@ -1100,10 +1136,11 @@ var FeatureFlags = class {
1100
1136
  }
1101
1137
  return flag;
1102
1138
  }
1103
- async createFeatureFlag(name, enabled) {
1139
+ async createFeatureFlag(name, enabled, description) {
1104
1140
  const flag = await this.http.post("/feature-flags", {
1105
1141
  name,
1106
- enabled
1142
+ enabled,
1143
+ ...description !== void 0 && { description }
1107
1144
  });
1108
1145
  this.flags.push(flag);
1109
1146
  return flag;
@@ -1116,11 +1153,17 @@ var Lobbies = class {
1116
1153
  this.http = http;
1117
1154
  this.logger = logger2;
1118
1155
  }
1156
+ /** Called by SDK after the store is created so mutations can update state directly. */
1157
+ setLobbyStore(store) {
1158
+ this.lobbyStore = store;
1159
+ }
1119
1160
  async createLobby(gameType, betAmount) {
1120
1161
  return this.http.post("/lobbies", { gameType, betAmount });
1121
1162
  }
1122
1163
  async getLobby(lobbyId) {
1123
- return this.http.get(`/lobbies/${lobbyId}`);
1164
+ const lobby = await this.http.get(`/lobbies/${lobbyId}`);
1165
+ this.lobbyStore?.setBaseState([lobby]);
1166
+ return lobby;
1124
1167
  }
1125
1168
  async inviteFriend(lobbyId, friendId) {
1126
1169
  return this.http.post(`/lobbies/${lobbyId}/invite`, {
@@ -1131,7 +1174,9 @@ var Lobbies = class {
1131
1174
  return this.http.post(`/lobbies/${lobbyId}/accept-invite`, {});
1132
1175
  }
1133
1176
  async joinLobby(lobbyId) {
1134
- return this.http.post(`/lobbies/${lobbyId}/join`, {});
1177
+ const lobby = await this.http.post(`/lobbies/${lobbyId}/join`, {});
1178
+ this.lobbyStore?.setBaseState([lobby]);
1179
+ return lobby;
1135
1180
  }
1136
1181
  async removePlayer(lobbyId, userId) {
1137
1182
  return this.http.delete(
@@ -1148,12 +1193,17 @@ var Lobbies = class {
1148
1193
  return this.http.post(`/lobbies/${lobbyId}/join-queue`, {});
1149
1194
  }
1150
1195
  async cancelQueue(lobbyId) {
1151
- return this.http.delete(`/lobbies/${lobbyId}/queue`);
1196
+ const lobby = await this.http.delete(`/lobbies/${lobbyId}/queue`);
1197
+ this.lobbyStore?.setBaseState([lobby]);
1198
+ return lobby;
1152
1199
  }
1153
1200
  async updateBetAmount(lobbyId, betAmount) {
1154
- return this.http.patch(`/lobbies/${lobbyId}/bet-amount`, {
1155
- betAmount
1156
- });
1201
+ const lobby = await this.http.patch(
1202
+ `/lobbies/${lobbyId}/bet-amount`,
1203
+ { betAmount }
1204
+ );
1205
+ this.lobbyStore?.setBaseState([lobby]);
1206
+ return lobby;
1157
1207
  }
1158
1208
  async playSound(lobbyId, sound) {
1159
1209
  return this.http.post(`/lobbies/${lobbyId}/sound`, {
@@ -1188,13 +1238,18 @@ var Lobbies = class {
1188
1238
  };
1189
1239
 
1190
1240
  // src/games.ts
1191
- var import_web32 = require("@solana/web3.js");
1192
1241
  var Games = class {
1193
1242
  constructor(http, wallet, logger2) {
1194
1243
  this.http = http;
1195
1244
  this.wallet = wallet;
1196
1245
  this.logger = logger2;
1197
1246
  }
1247
+ setGameStore(store) {
1248
+ this.gameStore = store;
1249
+ }
1250
+ setGameActionsStore(store) {
1251
+ this.gameActionsStore = store;
1252
+ }
1198
1253
  async getAvailableGames() {
1199
1254
  return this.http.get("/games/available");
1200
1255
  }
@@ -1207,7 +1262,11 @@ var Games = class {
1207
1262
  return this.http.get("/games/metrics");
1208
1263
  }
1209
1264
  async getGame(gameId) {
1210
- return this.http.get(`/games/${gameId}`);
1265
+ const existing = this.gameStore?.store.getState().gamesById[gameId];
1266
+ if (existing?.status === "completed") return existing;
1267
+ const game = await this.http.get(`/games/${gameId}`);
1268
+ this.gameStore?.setBaseState([game]);
1269
+ return game;
1211
1270
  }
1212
1271
  /**
1213
1272
  * Get list of currently active (live) games. Public endpoint for spectating.
@@ -1234,7 +1293,13 @@ var Games = class {
1234
1293
  * Get current game state with timer information
1235
1294
  */
1236
1295
  async getGameState(gameId) {
1237
- return this.http.get(`/games/${gameId}/state`);
1296
+ const existing = this.gameActionsStore?.store.getState().statesByGameId[gameId];
1297
+ if (existing?.status === "completed") return existing;
1298
+ const state = await this.http.get(
1299
+ `/games/${gameId}/state`
1300
+ );
1301
+ this.gameActionsStore?.setBaseState(gameId, state);
1302
+ return state;
1238
1303
  }
1239
1304
  /**
1240
1305
  * Request a rematch for a completed game
@@ -1270,8 +1335,18 @@ var Games = class {
1270
1335
  { amountMinor, signedTransaction }
1271
1336
  );
1272
1337
  }
1338
+ /**
1339
+ * Confirm a donation that was already sent by the client (signAndSendTransaction path).
1340
+ */
1341
+ async confirmGameDonationSignature(gameId, amountMinor, signature) {
1342
+ return this.http.post(
1343
+ `/games/${gameId}/donate/confirm-signature`,
1344
+ { amountMinor, signature }
1345
+ );
1346
+ }
1273
1347
  /**
1274
1348
  * One-call donation flow: prepare -> sign -> submit.
1349
+ * Automatically uses the right signing path (embedded vs injected wallet).
1275
1350
  */
1276
1351
  async sendDonation(gameId, amountMinor) {
1277
1352
  if (!this.wallet.hasSigner()) {
@@ -1280,16 +1355,10 @@ var Games = class {
1280
1355
  );
1281
1356
  }
1282
1357
  const prepared = await this.prepareGameDonation(gameId, amountMinor);
1283
- const unsignedTx = import_web32.Transaction.from(base64ToBytes(prepared.transaction));
1284
- const signedTx = await this.wallet.signTransaction(unsignedTx);
1285
- const signedTransaction = bytesToBase64(
1286
- signedTx.serialize({ requireAllSignatures: false })
1287
- );
1288
- const result = await this.donateToGame(
1289
- gameId,
1290
- amountMinor,
1291
- signedTransaction
1292
- );
1358
+ const result = await this.wallet.signAndDispatch(prepared.transaction, {
1359
+ onSigned: (signedTxBase64) => this.donateToGame(gameId, amountMinor, signedTxBase64),
1360
+ onSignedAndSent: (sig) => this.confirmGameDonationSignature(gameId, amountMinor, sig)
1361
+ });
1293
1362
  return {
1294
1363
  ...result,
1295
1364
  escrowAddress: prepared.escrowAddress,
@@ -1388,6 +1457,9 @@ var Chat = class {
1388
1457
  this.logger = logger2;
1389
1458
  this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...retryOptions };
1390
1459
  }
1460
+ setDmThreadsStore(store) {
1461
+ this.dmThreadsStore = store;
1462
+ }
1391
1463
  encodeContextId(id) {
1392
1464
  return encodeURIComponent(id);
1393
1465
  }
@@ -1469,7 +1541,9 @@ var Chat = class {
1469
1541
  return response;
1470
1542
  }
1471
1543
  async listDmThreads() {
1472
- return this.http.get("/chat/dm/threads");
1544
+ const threads = await this.http.get("/chat/dm/threads");
1545
+ this.dmThreadsStore?.setBaseState(threads);
1546
+ return threads;
1473
1547
  }
1474
1548
  async getDmThread(dmKey) {
1475
1549
  return this.http.get(
@@ -1535,7 +1609,6 @@ var Challenges = class {
1535
1609
  };
1536
1610
 
1537
1611
  // src/tips.ts
1538
- var import_web33 = require("@solana/web3.js");
1539
1612
  var Tips = class {
1540
1613
  constructor(http, wallet, chat, logger2) {
1541
1614
  this.http = http;
@@ -1562,21 +1635,32 @@ var Tips = class {
1562
1635
  );
1563
1636
  }
1564
1637
  const { publicKey: senderAddress } = await this.wallet.loadWallet();
1565
- const prepared = await this.prepare({ recipientUsername, amount });
1566
- const unsignedTx = import_web33.Transaction.from(base64ToBytes(prepared.transaction));
1567
- const signedTx = await this.wallet.signTransaction(unsignedTx);
1568
- const signedTxBase64 = bytesToBase64(
1569
- signedTx.serialize({ requireAllSignatures: false })
1570
- );
1571
- const transfer = await this.wallet.submitTransfer(
1572
- signedTxBase64,
1573
- senderAddress,
1574
- prepared.recipientAddress,
1575
- prepared.amount,
1576
- "USDC",
1577
- false,
1578
- prepared.recipientUsername
1579
- );
1638
+ const supportsPresign = !this.wallet.isSignAndSendMode();
1639
+ const prepared = await this.prepare({
1640
+ recipientUsername,
1641
+ amount,
1642
+ supportsPresign
1643
+ });
1644
+ const transfer = await this.wallet.signAndDispatch(prepared.transaction, {
1645
+ onSigned: (signedTxBase64) => this.wallet.submitTransfer(
1646
+ signedTxBase64,
1647
+ senderAddress,
1648
+ prepared.recipientAddress,
1649
+ prepared.amount,
1650
+ "USDC",
1651
+ false,
1652
+ prepared.recipientUsername
1653
+ ),
1654
+ onSignedAndSent: (sig) => this.wallet.confirmTransferSignature(
1655
+ sig,
1656
+ senderAddress,
1657
+ prepared.recipientAddress,
1658
+ prepared.amount,
1659
+ "USDC",
1660
+ false,
1661
+ prepared.recipientUsername
1662
+ )
1663
+ });
1580
1664
  const message = await this.chat.broadcastGlobalTip(
1581
1665
  prepared.recipientUserId,
1582
1666
  prepared.amount
@@ -1688,7 +1772,7 @@ var Achievements = class {
1688
1772
  };
1689
1773
 
1690
1774
  // src/wallet.ts
1691
- var import_web34 = require("@solana/web3.js");
1775
+ var import_web32 = require("@solana/web3.js");
1692
1776
  var TRANSFER_FEE_MINOR = 1e4;
1693
1777
  var MIN_TRANSFER_AMOUNT = 5e4;
1694
1778
  var MIN_SOL_TRANSFER_AMOUNT = 1e6;
@@ -1905,13 +1989,15 @@ var Wallet = class {
1905
1989
  );
1906
1990
  }
1907
1991
  try {
1992
+ const supportsPresign = !this.isSignAndSendMode();
1908
1993
  const response = await this.http.post(
1909
1994
  "/wallets/transfer/prepare",
1910
1995
  {
1911
1996
  senderAddress,
1912
1997
  recipient,
1913
1998
  amount: amountMinor,
1914
- token
1999
+ token,
2000
+ supportsPresign
1915
2001
  }
1916
2002
  );
1917
2003
  this.logger.debug("Transfer prepared", {
@@ -1995,6 +2081,29 @@ var Wallet = class {
1995
2081
  throw error;
1996
2082
  }
1997
2083
  }
2084
+ /**
2085
+ * Sign a prepared transaction and invoke the appropriate backend callback
2086
+ * based on the configured signer — without exposing the mode decision to callers.
2087
+ * - Embedded wallet (signAndSend): signs+sends, calls onSignedAndSent(signature)
2088
+ * - Injected wallet (signTransaction): signs locally, calls onSigned(signedTxBase64)
2089
+ */
2090
+ async signAndDispatch(unsignedTxBase64, handlers) {
2091
+ if (!this.signer) {
2092
+ throw new Error(
2093
+ "No signer configured. Call setSigner() with a WalletSigner implementation first."
2094
+ );
2095
+ }
2096
+ const unsignedTx = import_web32.Transaction.from(base64ToBytes(unsignedTxBase64));
2097
+ if (this.isSignAndSendMode()) {
2098
+ const sig = await this.signAndSendTransaction(unsignedTx);
2099
+ return handlers.onSignedAndSent(sig);
2100
+ }
2101
+ const signedTx = await this.signTransaction(unsignedTx);
2102
+ const signedBase64 = bytesToBase64(
2103
+ signedTx.serialize({ requireAllSignatures: false })
2104
+ );
2105
+ return handlers.onSigned(signedBase64);
2106
+ }
1998
2107
  /**
1999
2108
  * Full transfer flow in one call: prepare -> sign -> submit
2000
2109
  * Recipient can be username, .sol domain, or Solana address (resolved by backend).
@@ -2011,7 +2120,7 @@ var Wallet = class {
2011
2120
  amount,
2012
2121
  token
2013
2122
  );
2014
- const unsignedTx = import_web34.Transaction.from(base64ToBytes(prepared.transaction));
2123
+ const unsignedTx = import_web32.Transaction.from(base64ToBytes(prepared.transaction));
2015
2124
  let submitted;
2016
2125
  if (this.isSignAndSendMode()) {
2017
2126
  const signature = await this.signAndSendTransaction(unsignedTx);
@@ -2051,7 +2160,7 @@ var Wallet = class {
2051
2160
  };
2052
2161
 
2053
2162
  // src/escrow.ts
2054
- var import_web35 = require("@solana/web3.js");
2163
+ var import_web33 = require("@solana/web3.js");
2055
2164
  var Escrow = class {
2056
2165
  constructor(http, wallet, logger2) {
2057
2166
  this.http = http;
@@ -2079,12 +2188,32 @@ var Escrow = class {
2079
2188
  * initializes depositStatus, and prepares the unsigned transaction in one call.
2080
2189
  * Eliminates one HTTP round-trip vs startDeposits() + prepareDepositTransaction().
2081
2190
  */
2082
- async prepareAndStartDeposit(lobbyId) {
2191
+ async prepareAndStartDeposit(lobbyId, supportsPresign) {
2192
+ const presign = supportsPresign ?? !this.wallet.isSignAndSendMode();
2083
2193
  return this.http.post(
2084
2194
  `/escrow/lobby/${lobbyId}/deposit/prepare-and-start`,
2085
- {}
2195
+ { supportsPresign: presign }
2086
2196
  );
2087
2197
  }
2198
+ /**
2199
+ * Sign and submit a prepared (unsigned) deposit transaction using the
2200
+ * configured signer. Automatically picks the right path:
2201
+ * - Embedded wallet: signAndSendTransaction → confirmDepositSignature
2202
+ * - Injected wallet: signTransaction → submitDeposit
2203
+ * Returns the on-chain signature.
2204
+ */
2205
+ async signAndSubmitPreparedDeposit(lobbyId, unsignedTxBase64) {
2206
+ return this.wallet.signAndDispatch(unsignedTxBase64, {
2207
+ onSigned: async (signedTxBase64) => {
2208
+ const result = await this.submitDeposit(lobbyId, signedTxBase64);
2209
+ return result.signature;
2210
+ },
2211
+ onSignedAndSent: async (signature) => {
2212
+ await this.confirmDepositSignature(lobbyId, signature);
2213
+ return signature;
2214
+ }
2215
+ });
2216
+ }
2088
2217
  /**
2089
2218
  * Submit a signed deposit transaction
2090
2219
  * The transaction will be submitted to the Solana network and confirmed
@@ -2127,8 +2256,12 @@ var Escrow = class {
2127
2256
  "No signer configured. Use sdk.wallet.setSigner(...) first."
2128
2257
  );
2129
2258
  }
2130
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
2131
- const unsignedTx = import_web35.Transaction.from(base64ToBytes(transaction));
2259
+ const supportsPresign = !this.wallet.isSignAndSendMode();
2260
+ const { transaction } = await this.prepareAndStartDeposit(
2261
+ lobbyId,
2262
+ supportsPresign
2263
+ );
2264
+ const unsignedTx = import_web33.Transaction.from(base64ToBytes(transaction));
2132
2265
  let signature;
2133
2266
  if (this.wallet.isSignAndSendMode()) {
2134
2267
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
@@ -2162,8 +2295,13 @@ var Escrow = class {
2162
2295
  };
2163
2296
  }
2164
2297
  /**
2165
- * Zero-polling deposit variant using server-side awaitConfirmation.
2166
- * 2 HTTP calls total, 0 client-side polling. Ideal for agents.
2298
+ * Deposit for a lobby and wait until the calling user's own deposit is confirmed.
2299
+ * Ideal for agents: returns as soon as your deposit is on-chain without waiting
2300
+ * for the other player. The server auto-joins the queue when all players deposit.
2301
+ *
2302
+ * Confirmation is handled asynchronously by the transaction queue processor.
2303
+ * This method polls the deposit status endpoint until the signature appears as
2304
+ * confirmed (up to 60 seconds).
2167
2305
  *
2168
2306
  * Automatically uses signAndSendTransaction or signTransaction based on signer capability.
2169
2307
  */
@@ -2173,26 +2311,45 @@ var Escrow = class {
2173
2311
  "No signer configured. Use sdk.wallet.setSigner(...) first."
2174
2312
  );
2175
2313
  }
2176
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
2177
- const unsignedTx = import_web35.Transaction.from(base64ToBytes(transaction));
2314
+ const supportsPresign2 = !this.wallet.isSignAndSendMode();
2315
+ const { transaction } = await this.prepareAndStartDeposit(
2316
+ lobbyId,
2317
+ supportsPresign2
2318
+ );
2319
+ const unsignedTx = import_web33.Transaction.from(base64ToBytes(transaction));
2178
2320
  let signature;
2179
2321
  if (this.wallet.isSignAndSendMode()) {
2180
2322
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
2181
- await this.http.post(
2182
- `/escrow/lobby/${lobbyId}/deposit/confirm-signature?awaitConfirmation=true`,
2183
- { signature }
2184
- );
2323
+ await this.confirmDepositSignature(lobbyId, signature);
2185
2324
  } else {
2186
2325
  const signedTx = await this.wallet.signTransaction(unsignedTx);
2187
2326
  const signedBase64 = bytesToBase64(
2188
2327
  signedTx.serialize({ requireAllSignatures: false })
2189
2328
  );
2190
- const result = await this.http.post(`/escrow/lobby/${lobbyId}/deposit/submit?awaitConfirmation=true`, {
2191
- signedTransaction: signedBase64
2192
- });
2193
- signature = result.signature;
2329
+ const submitResult = await this.submitDeposit(lobbyId, signedBase64);
2330
+ signature = submitResult.signature;
2194
2331
  }
2195
- return { signature, status: "confirmed", canProceedToQueue: true };
2332
+ const MAX_WAIT_MS = 6e4;
2333
+ const POLL_INTERVAL_MS = 1e3;
2334
+ const startTime = Date.now();
2335
+ while (Date.now() - startTime < MAX_WAIT_MS) {
2336
+ const status = await this.getDepositStatus(lobbyId);
2337
+ const myDeposit = status.deposits.find(
2338
+ (d) => d.transactionHash === signature
2339
+ );
2340
+ if (myDeposit?.status === "confirmed") {
2341
+ return {
2342
+ signature,
2343
+ status: "confirmed",
2344
+ canProceedToQueue: status.canProceedToQueue
2345
+ };
2346
+ }
2347
+ if (status.allConfirmed && status.canProceedToQueue) {
2348
+ return { signature, status: "confirmed", canProceedToQueue: true };
2349
+ }
2350
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
2351
+ }
2352
+ return { signature, status: "pending", canProceedToQueue: false };
2196
2353
  }
2197
2354
  async claimLobbyDepositRefund(lobbyId, depositSignature) {
2198
2355
  return this.http.post(
@@ -2548,9 +2705,10 @@ var Support = class {
2548
2705
 
2549
2706
  // src/markets.ts
2550
2707
  var Markets = class {
2551
- constructor(http, logger2) {
2708
+ constructor(http, logger2, wallet) {
2552
2709
  this.http = http;
2553
2710
  this.logger = logger2;
2711
+ this.wallet = wallet;
2554
2712
  }
2555
2713
  /**
2556
2714
  * Get the prediction market state for a game.
@@ -2592,6 +2750,35 @@ var Markets = class {
2592
2750
  { signedTransaction, outcomeId, amountMinor }
2593
2751
  );
2594
2752
  }
2753
+ /**
2754
+ * Confirm a buy order that was already broadcast by the client (signAndSendTransaction path).
2755
+ */
2756
+ async confirmBuyOrderSignature(gameId, depositSignature, outcomeId, amountMinor) {
2757
+ return this.http.post(
2758
+ `/games/${gameId}/market/orders/buy/confirm-signature`,
2759
+ { depositSignature, outcomeId, amountMinor }
2760
+ );
2761
+ }
2762
+ /**
2763
+ * One-call buy order: prepare → sign → submit, automatically choosing
2764
+ * the right signing path (embedded vs injected wallet).
2765
+ */
2766
+ async buy(gameId, outcomeId, amountMinor) {
2767
+ if (!this.wallet?.hasSigner()) {
2768
+ throw new Error(
2769
+ "No signer configured. Use sdk.wallet.setSigner(...) first."
2770
+ );
2771
+ }
2772
+ const { transaction } = await this.prepareBuyOrder(
2773
+ gameId,
2774
+ outcomeId,
2775
+ amountMinor
2776
+ );
2777
+ return this.wallet.signAndDispatch(transaction, {
2778
+ onSigned: (signedTxBase64) => this.submitBuyOrder(gameId, signedTxBase64, outcomeId, amountMinor),
2779
+ onSignedAndSent: (sig) => this.confirmBuyOrderSignature(gameId, sig, outcomeId, amountMinor)
2780
+ });
2781
+ }
2595
2782
  /**
2596
2783
  * Sell shares back to the AMM pool.
2597
2784
  * @param gameId - The game ID
@@ -2641,6 +2828,22 @@ var Markets = class {
2641
2828
  }
2642
2829
  };
2643
2830
 
2831
+ // src/analytics.ts
2832
+ var NoopAnalyticsClient = class {
2833
+ userLoggedIn(_user, _meta) {
2834
+ }
2835
+ userLoggedOut() {
2836
+ }
2837
+ sessionRestored(_user) {
2838
+ }
2839
+ track(_event, _properties) {
2840
+ }
2841
+ setUserProperties(_properties) {
2842
+ }
2843
+ group(_groupType, _groupKey, _properties) {
2844
+ }
2845
+ };
2846
+
2644
2847
  // src/ws/standalone-transport.ts
2645
2848
  var import_socket = require("socket.io-client");
2646
2849
 
@@ -2734,22 +2937,35 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2734
2937
  this.accessToken = null;
2735
2938
  this.reconnectAttempts = 0;
2736
2939
  this.reconnectTimer = null;
2737
- this.periodicReconnectInterval = null;
2738
- this.maxReconnectAttempts = 5;
2739
- this.reconnectDelay = 1e3;
2740
- this.reconnectDelayMax = 3e4;
2741
- // 5 minutes
2940
+ this.needsReconnect = false;
2941
+ this.visibilityHandler = null;
2742
2942
  this.wildcardHandlers = /* @__PURE__ */ new Set();
2743
2943
  this.eventHandlers = /* @__PURE__ */ new Map();
2744
2944
  this.registeredEvents = /* @__PURE__ */ new Set();
2745
2945
  this.wildcardRegistered = false;
2746
2946
  this.url = url;
2947
+ if (typeof document !== "undefined") {
2948
+ this.visibilityHandler = () => {
2949
+ if (document.visibilityState === "visible" && !this.connectionState.connected) {
2950
+ this.reconnectImmediately();
2951
+ }
2952
+ };
2953
+ document.addEventListener("visibilitychange", this.visibilityHandler);
2954
+ }
2747
2955
  }
2748
2956
  connect() {
2957
+ if (this.reconnectTimer !== null) {
2958
+ clearTimeout(this.reconnectTimer);
2959
+ this.reconnectTimer = null;
2960
+ }
2961
+ this.reconnectAttempts = 0;
2749
2962
  this.ensureSocket();
2750
2963
  }
2751
2964
  disconnect() {
2752
- this.clearPeriodicReconnect();
2965
+ if (this.visibilityHandler && typeof document !== "undefined") {
2966
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
2967
+ this.visibilityHandler = null;
2968
+ }
2753
2969
  if (!this.socket) return;
2754
2970
  this.socket.disconnect();
2755
2971
  this.socket = null;
@@ -2848,13 +3064,13 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2848
3064
  reconnection: false
2849
3065
  });
2850
3066
  socket.on("connect", () => {
2851
- this.clearPeriodicReconnect();
2852
3067
  this.updateConnectionState({
2853
3068
  connected: true,
2854
3069
  connecting: false,
2855
3070
  error: null
2856
3071
  });
2857
- const wasReconnect = this.reconnectAttempts > 0;
3072
+ const wasReconnect = this.needsReconnect;
3073
+ this.needsReconnect = false;
2858
3074
  this.reconnectAttempts = 0;
2859
3075
  this.onReconnect();
2860
3076
  if (wasReconnect) {
@@ -2865,6 +3081,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2865
3081
  });
2866
3082
  socket.on("disconnect", (reason) => {
2867
3083
  this.updateConnectionState({ connected: false, connecting: false });
3084
+ this.needsReconnect = true;
2868
3085
  this.clearSocketForReconnect();
2869
3086
  if (reason === "io server disconnect") {
2870
3087
  this.handleAuthFailure("io server disconnect");
@@ -2874,6 +3091,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2874
3091
  });
2875
3092
  socket.on("connect_error", (error) => {
2876
3093
  const message = error?.message || "connect_error";
3094
+ this.needsReconnect = true;
2877
3095
  this.clearSocketForReconnect();
2878
3096
  if (message.includes("unauthorized") || message.includes("401")) {
2879
3097
  this.handleAuthFailure(message);
@@ -2901,31 +3119,31 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2901
3119
  this.socket.removeAllListeners();
2902
3120
  this.socket = null;
2903
3121
  }
2904
- clearPeriodicReconnect() {
2905
- if (this.periodicReconnectInterval !== null) {
2906
- clearInterval(this.periodicReconnectInterval);
2907
- this.periodicReconnectInterval = null;
3122
+ reconnectImmediately() {
3123
+ if (this.reconnectTimer !== null) {
3124
+ clearTimeout(this.reconnectTimer);
3125
+ this.reconnectTimer = null;
2908
3126
  }
2909
- }
2910
- startPeriodicReconnect() {
2911
- if (this.periodicReconnectInterval !== null) return;
2912
- this.periodicReconnectInterval = setInterval(() => {
2913
- if (this.connectionState.connected) return;
2914
- this.reconnectAttempts = 0;
2915
- this.ensureSocket();
2916
- }, _StandaloneWsTransport.PERIODIC_RECONNECT_MS);
3127
+ this.reconnectAttempts = 0;
3128
+ this.ensureSocket();
2917
3129
  }
2918
3130
  scheduleReconnect() {
2919
3131
  if (this.reconnectTimer !== null) return;
2920
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
2921
- this.startPeriodicReconnect();
2922
- return;
2923
- }
2924
- const delay = Math.min(
2925
- this.reconnectDelay * Math.pow(2, this.reconnectAttempts),
2926
- this.reconnectDelayMax
2927
- );
3132
+ const attempt = this.reconnectAttempts;
2928
3133
  this.reconnectAttempts += 1;
3134
+ let delay;
3135
+ if (attempt < _StandaloneWsTransport.FAST_RETRY_LIMIT) {
3136
+ delay = _StandaloneWsTransport.FAST_RETRY_DELAY_MS;
3137
+ } else {
3138
+ const backoffAttempt = attempt - _StandaloneWsTransport.FAST_RETRY_LIMIT;
3139
+ delay = Math.min(
3140
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS * Math.pow(2, backoffAttempt),
3141
+ _StandaloneWsTransport.MAX_BACKOFF_MS
3142
+ );
3143
+ if (attempt === _StandaloneWsTransport.FAST_RETRY_LIMIT) {
3144
+ this.dispatchEvent("connection:slow-retry", { timestamp: Date.now() });
3145
+ }
3146
+ }
2929
3147
  this.reconnectTimer = setTimeout(() => {
2930
3148
  this.reconnectTimer = null;
2931
3149
  this.ensureSocket();
@@ -2986,7 +3204,9 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2986
3204
  this.wildcardRegistered = true;
2987
3205
  }
2988
3206
  };
2989
- _StandaloneWsTransport.PERIODIC_RECONNECT_MS = 5 * 60 * 1e3;
3207
+ _StandaloneWsTransport.FAST_RETRY_LIMIT = 60;
3208
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS = 1e3;
3209
+ _StandaloneWsTransport.MAX_BACKOFF_MS = 3e4;
2990
3210
  var StandaloneWsTransport = _StandaloneWsTransport;
2991
3211
 
2992
3212
  // src/stores/store-utils.ts
@@ -3013,14 +3233,16 @@ function createLobbyStore(transport) {
3013
3233
  depositStatusByLobbyId: {}
3014
3234
  });
3015
3235
  const setBaseState = (lobbies) => {
3016
- const lobbiesById = {};
3017
- for (const lobby of lobbies) {
3018
- lobbiesById[lobby.id] = lobby;
3019
- }
3020
- store.updateState((state) => ({
3021
- ...state,
3022
- lobbiesById
3023
- }));
3236
+ store.updateState((state) => {
3237
+ const lobbiesById = { ...state.lobbiesById };
3238
+ for (const lobby of lobbies) {
3239
+ const existing = lobbiesById[lobby.id];
3240
+ if (!existing || lobby.updatedAt >= existing.updatedAt) {
3241
+ lobbiesById[lobby.id] = lobby;
3242
+ }
3243
+ }
3244
+ return { ...state, lobbiesById };
3245
+ });
3024
3246
  };
3025
3247
  const applyWsEvent = (event) => {
3026
3248
  switch (event.event) {
@@ -3152,12 +3374,23 @@ function createGameStore(transport) {
3152
3374
  gamesById: {},
3153
3375
  spectatorCounts: {}
3154
3376
  });
3377
+ const STATUS_RANK = {
3378
+ active: 0,
3379
+ completed: 1,
3380
+ abandoned: 1
3381
+ };
3155
3382
  const setBaseState = (games) => {
3156
- const gamesById = {};
3157
- for (const game of games) {
3158
- gamesById[game.gameId] = game;
3159
- }
3160
- store.updateState((state) => ({ ...state, gamesById }));
3383
+ store.updateState((state) => {
3384
+ const gamesById = { ...state.gamesById };
3385
+ for (const game of games) {
3386
+ const existing = gamesById[game.gameId];
3387
+ if (existing && STATUS_RANK[existing.status] > STATUS_RANK[game.status]) {
3388
+ continue;
3389
+ }
3390
+ gamesById[game.gameId] = game;
3391
+ }
3392
+ return { ...state, gamesById };
3393
+ });
3161
3394
  };
3162
3395
  const applyWsEvent = (event) => {
3163
3396
  switch (event.event) {
@@ -3234,6 +3467,19 @@ function createGameStore(transport) {
3234
3467
  }
3235
3468
 
3236
3469
  // src/stores/game-actions-store.ts
3470
+ function selectGameLifecycleState(gameState) {
3471
+ const s = gameState;
3472
+ if (!s) return null;
3473
+ const raw = s;
3474
+ return {
3475
+ status: raw.status,
3476
+ winnerId: raw.winnerId ?? null,
3477
+ betAmount: raw.betAmount ?? 0,
3478
+ wonAmount: raw.wonAmount ?? null,
3479
+ totalPotMinor: raw.totalPotMinor,
3480
+ currentPlayerId: raw.currentPlayerId ?? null
3481
+ };
3482
+ }
3237
3483
  var isRpsCompletionPayload = (payload) => payload.gameType === "rock-paper-scissors";
3238
3484
  function createGameActionsStore(transport) {
3239
3485
  const store = createSdkStore({
@@ -3255,8 +3501,21 @@ function createGameActionsStore(transport) {
3255
3501
  });
3256
3502
  };
3257
3503
  const isNonRpsState = (state) => Boolean(state && !isRpsState(state));
3504
+ const pendingEvents = /* @__PURE__ */ new Map();
3505
+ function enqueue(gameId, event) {
3506
+ const q = pendingEvents.get(gameId) ?? [];
3507
+ q.push(event);
3508
+ pendingEvents.set(gameId, q);
3509
+ }
3510
+ function drainQueue(gameId) {
3511
+ const q = pendingEvents.get(gameId);
3512
+ if (!q?.length) return;
3513
+ pendingEvents.delete(gameId);
3514
+ for (const ev of q) applyWsEvent(ev);
3515
+ }
3258
3516
  const setBaseState = (gameId, state) => {
3259
3517
  updateState(gameId, state);
3518
+ drainQueue(gameId);
3260
3519
  };
3261
3520
  const clearState = (gameId) => {
3262
3521
  store.updateState((state) => {
@@ -3266,6 +3525,7 @@ function createGameActionsStore(transport) {
3266
3525
  const { [gameId]: _, ...rest } = state.statesByGameId;
3267
3526
  return { ...state, statesByGameId: rest };
3268
3527
  });
3528
+ pendingEvents.delete(gameId);
3269
3529
  };
3270
3530
  const applyWsEvent = (event) => {
3271
3531
  switch (event.event) {
@@ -3284,7 +3544,10 @@ function createGameActionsStore(transport) {
3284
3544
  }
3285
3545
  case "game:rps:starting": {
3286
3546
  const current = store.getState().statesByGameId[event.payload.gameId];
3287
- if (!current || !isRpsState(current)) return;
3547
+ if (!current || !isRpsState(current)) {
3548
+ enqueue(event.payload.gameId, event);
3549
+ return;
3550
+ }
3288
3551
  const betAmount = typeof event.payload.betAmount === "number" ? event.payload.betAmount : void 0;
3289
3552
  const startedAt = typeof event.payload.startedAt === "string" ? event.payload.startedAt : current.roundState.startedAt;
3290
3553
  const bufferEndsAt = typeof event.payload.bufferEndsAt === "string" ? event.payload.bufferEndsAt : current.roundState.selectionEndsAt;
@@ -3305,7 +3568,10 @@ function createGameActionsStore(transport) {
3305
3568
  }
3306
3569
  case "game:rps:round:started": {
3307
3570
  const current = store.getState().statesByGameId[event.payload.gameId];
3308
- if (!current || !isRpsState(current)) return;
3571
+ if (!current || !isRpsState(current)) {
3572
+ enqueue(event.payload.gameId, event);
3573
+ return;
3574
+ }
3309
3575
  const actions = {};
3310
3576
  const baseUsers = /* @__PURE__ */ new Set();
3311
3577
  Object.keys(current.roundState.actions).forEach(
@@ -3339,7 +3605,10 @@ function createGameActionsStore(transport) {
3339
3605
  }
3340
3606
  case "game:rps:action:received": {
3341
3607
  const current = store.getState().statesByGameId[event.payload.gameId];
3342
- if (!current || !isRpsState(current)) return;
3608
+ if (!current || !isRpsState(current)) {
3609
+ enqueue(event.payload.gameId, event);
3610
+ return;
3611
+ }
3343
3612
  const updated = {
3344
3613
  ...current,
3345
3614
  roundState: {
@@ -3358,7 +3627,10 @@ function createGameActionsStore(transport) {
3358
3627
  }
3359
3628
  case "game:rps:timer:cutoff": {
3360
3629
  const current = store.getState().statesByGameId[event.payload.gameId];
3361
- if (!current || !isRpsState(current)) return;
3630
+ if (!current || !isRpsState(current)) {
3631
+ enqueue(event.payload.gameId, event);
3632
+ return;
3633
+ }
3362
3634
  const updated = {
3363
3635
  ...current,
3364
3636
  roundState: {
@@ -3372,7 +3644,10 @@ function createGameActionsStore(transport) {
3372
3644
  }
3373
3645
  case "game:rps:round:reveal": {
3374
3646
  const current = store.getState().statesByGameId[event.payload.gameId];
3375
- if (!current || !isRpsState(current)) return;
3647
+ if (!current || !isRpsState(current)) {
3648
+ enqueue(event.payload.gameId, event);
3649
+ return;
3650
+ }
3376
3651
  const actions = {};
3377
3652
  const payloadActions = event.payload.actions;
3378
3653
  Object.keys(payloadActions || {}).forEach((userId) => {
@@ -3396,7 +3671,10 @@ function createGameActionsStore(transport) {
3396
3671
  }
3397
3672
  case "game:rps:round:completed": {
3398
3673
  const current = store.getState().statesByGameId[event.payload.gameId];
3399
- if (!current || !isRpsState(current)) return;
3674
+ if (!current || !isRpsState(current)) {
3675
+ enqueue(event.payload.gameId, event);
3676
+ return;
3677
+ }
3400
3678
  const roundHistory = [
3401
3679
  ...current.roundHistory || [],
3402
3680
  {
@@ -3421,7 +3699,10 @@ function createGameActionsStore(transport) {
3421
3699
  }
3422
3700
  case "game:rps:timeout": {
3423
3701
  const current = store.getState().statesByGameId[event.payload.gameId];
3424
- if (!current || !isRpsState(current)) return;
3702
+ if (!current || !isRpsState(current)) {
3703
+ enqueue(event.payload.gameId, event);
3704
+ return;
3705
+ }
3425
3706
  const timedOutUser = event.payload.playerId;
3426
3707
  const action = event.payload.action;
3427
3708
  const updated = {
@@ -3446,7 +3727,10 @@ function createGameActionsStore(transport) {
3446
3727
  const payload = event.payload;
3447
3728
  const { gameId } = payload;
3448
3729
  const current = store.getState().statesByGameId[gameId];
3449
- if (!current) return;
3730
+ if (!current) {
3731
+ enqueue(gameId, event);
3732
+ return;
3733
+ }
3450
3734
  const updated = isRpsCompletionPayload(payload) && isRpsState(current) ? {
3451
3735
  ...current,
3452
3736
  status: "completed",
@@ -3495,11 +3779,15 @@ function createGameActionsStore(transport) {
3495
3779
  const current = store.getState().statesByGameId[gameId];
3496
3780
  const updated = current ? { ...current, ...incoming } : incoming;
3497
3781
  updateState(gameId, updated);
3782
+ drainQueue(gameId);
3498
3783
  break;
3499
3784
  }
3500
3785
  case "game:rematch:requested": {
3501
3786
  const current = store.getState().statesByGameId[event.payload.gameId];
3502
- if (!current) return;
3787
+ if (!current) {
3788
+ enqueue(event.payload.gameId, event);
3789
+ return;
3790
+ }
3503
3791
  const requestedBy = event.payload.requestedBy;
3504
3792
  const userId = event.payload.userId;
3505
3793
  const requested = new Set(
@@ -3515,7 +3803,10 @@ function createGameActionsStore(transport) {
3515
3803
  }
3516
3804
  case "game:rematch:cancelled": {
3517
3805
  const current = store.getState().statesByGameId[event.payload.gameId];
3518
- if (!current) return;
3806
+ if (!current) {
3807
+ enqueue(event.payload.gameId, event);
3808
+ return;
3809
+ }
3519
3810
  const requestedBy = event.payload.requestedBy ?? [];
3520
3811
  const updated = {
3521
3812
  ...current,
@@ -3526,7 +3817,10 @@ function createGameActionsStore(transport) {
3526
3817
  }
3527
3818
  case "game:rematch:started": {
3528
3819
  const current = store.getState().statesByGameId[event.payload.gameId];
3529
- if (!current) return;
3820
+ if (!current) {
3821
+ enqueue(event.payload.gameId, event);
3822
+ return;
3823
+ }
3530
3824
  const updated = {
3531
3825
  ...current,
3532
3826
  rematchRequestedBy: event.payload.playerIds ?? []
@@ -3537,7 +3831,10 @@ function createGameActionsStore(transport) {
3537
3831
  case "game:pot:updated": {
3538
3832
  const { gameId, totalPotMinor } = event.payload;
3539
3833
  const current = store.getState().statesByGameId[gameId];
3540
- if (!current) return;
3834
+ if (!current) {
3835
+ enqueue(gameId, event);
3836
+ return;
3837
+ }
3541
3838
  const updated = {
3542
3839
  ...current,
3543
3840
  totalPotMinor
@@ -3550,12 +3847,123 @@ function createGameActionsStore(transport) {
3550
3847
  }
3551
3848
  };
3552
3849
  const joinGame = (gameId) => transport.joinRoom(`game:${gameId}`);
3850
+ const getCountdownDigit = (gameId, nowMs) => {
3851
+ const state = store.getState().statesByGameId[gameId];
3852
+ if (!state) return null;
3853
+ if (isRpsState(state)) {
3854
+ if (state.roundState.phase !== "starting") return null;
3855
+ const remaining = new Date(state.roundState.selectionEndsAt).getTime() - nowMs;
3856
+ if (remaining <= 0) return null;
3857
+ return Math.ceil(remaining / 1e3);
3858
+ }
3859
+ const bufferEndsAt = state.bufferEndsAt;
3860
+ if (bufferEndsAt) {
3861
+ const remaining = new Date(bufferEndsAt).getTime() - nowMs;
3862
+ if (remaining <= 0) return null;
3863
+ return Math.ceil(remaining / 1e3);
3864
+ }
3865
+ return null;
3866
+ };
3867
+ const getChessClockTimes = (gameId, nowMs) => {
3868
+ const state = store.getState().statesByGameId[gameId];
3869
+ if (!state || state.gameType !== "chess") return null;
3870
+ const s = state;
3871
+ let whiteMs = s.whiteTimeMs ?? 0;
3872
+ let blackMs = s.blackTimeMs ?? 0;
3873
+ if (s.status === "active" && s.currentPlayerId) {
3874
+ const startedAt = Date.parse(s.turnStartedAt);
3875
+ if (!Number.isNaN(startedAt)) {
3876
+ const elapsed = Math.max(0, nowMs - startedAt);
3877
+ if (s.currentPlayerId === s.whitePlayerId) {
3878
+ whiteMs = Math.max(0, whiteMs - elapsed);
3879
+ } else if (s.currentPlayerId === s.blackPlayerId) {
3880
+ blackMs = Math.max(0, blackMs - elapsed);
3881
+ }
3882
+ }
3883
+ }
3884
+ return { whiteTimeMs: whiteMs, blackTimeMs: blackMs };
3885
+ };
3886
+ const getChessCapturedPieces = (gameId) => {
3887
+ const state = store.getState().statesByGameId[gameId];
3888
+ if (!state || state.gameType !== "chess") return null;
3889
+ const fen = state.fen;
3890
+ if (!fen) return { capturedByWhite: [], capturedByBlack: [] };
3891
+ const placement = fen.split(" ")[0];
3892
+ const white = {};
3893
+ const black = {};
3894
+ for (const char of placement) {
3895
+ if (char === "/" || char >= "1" && char <= "8") continue;
3896
+ const lower = char.toLowerCase();
3897
+ if (char === lower) {
3898
+ black[lower] = (black[lower] ?? 0) + 1;
3899
+ } else {
3900
+ white[lower] = (white[lower] ?? 0) + 1;
3901
+ }
3902
+ }
3903
+ const INITIAL = {
3904
+ p: 8,
3905
+ r: 2,
3906
+ n: 2,
3907
+ b: 2,
3908
+ q: 1,
3909
+ k: 1
3910
+ };
3911
+ const capturedByWhite = [];
3912
+ const capturedByBlack = [];
3913
+ for (const [type, initial] of Object.entries(INITIAL)) {
3914
+ const missingBlack = initial - (black[type] ?? 0);
3915
+ for (let i = 0; i < missingBlack; i++) capturedByWhite.push(`b${type}`);
3916
+ const missingWhite = initial - (white[type] ?? 0);
3917
+ for (let i = 0; i < missingWhite; i++) capturedByBlack.push(`w${type}`);
3918
+ }
3919
+ return { capturedByWhite, capturedByBlack };
3920
+ };
3921
+ const getTicTacToeClockTimes = (gameId, nowMs) => {
3922
+ const state = store.getState().statesByGameId[gameId];
3923
+ if (!state || state.gameType !== "tic-tac-toe") return null;
3924
+ const s = state;
3925
+ let xMs = s.xTimeMs ?? 0;
3926
+ let oMs = s.oTimeMs ?? 0;
3927
+ if (s.status === "active" && s.currentPlayerId) {
3928
+ const currentMark = s.playerMarks[s.currentPlayerId];
3929
+ const startedAt = Date.parse(s.turnStartedAt);
3930
+ if (!Number.isNaN(startedAt)) {
3931
+ const elapsed = Math.max(0, nowMs - startedAt);
3932
+ if (currentMark === "X") xMs = Math.max(0, xMs - elapsed);
3933
+ else if (currentMark === "O") oMs = Math.max(0, oMs - elapsed);
3934
+ }
3935
+ }
3936
+ return { xTimeMs: xMs, oTimeMs: oMs };
3937
+ };
3938
+ const getConnect4ClockTimes = (gameId, nowMs) => {
3939
+ const state = store.getState().statesByGameId[gameId];
3940
+ if (!state || state.gameType !== "connect-four") return null;
3941
+ const s = state;
3942
+ let redMs = s.redTimeMs ?? 0;
3943
+ let yellowMs = s.yellowTimeMs ?? 0;
3944
+ if (s.status === "active" && s.currentPlayerId) {
3945
+ const currentColor = s.playerColors[s.currentPlayerId];
3946
+ const startedAt = Date.parse(s.turnStartedAt);
3947
+ if (!Number.isNaN(startedAt)) {
3948
+ const elapsed = Math.max(0, nowMs - startedAt);
3949
+ if (currentColor === "RED") redMs = Math.max(0, redMs - elapsed);
3950
+ else if (currentColor === "YELLOW")
3951
+ yellowMs = Math.max(0, yellowMs - elapsed);
3952
+ }
3953
+ }
3954
+ return { redTimeMs: redMs, yellowTimeMs: yellowMs };
3955
+ };
3553
3956
  return {
3554
3957
  store,
3555
3958
  setBaseState,
3556
3959
  clearState,
3557
3960
  applyWsEvent,
3558
- joinGame
3961
+ joinGame,
3962
+ getCountdownDigit,
3963
+ getChessClockTimes,
3964
+ getChessCapturedPieces,
3965
+ getTicTacToeClockTimes,
3966
+ getConnect4ClockTimes
3559
3967
  };
3560
3968
  }
3561
3969
  function isRpsState(state) {
@@ -3903,7 +4311,38 @@ function createNotificationsStore() {
3903
4311
  appNotifications: []
3904
4312
  }));
3905
4313
  };
3906
- return { store, applyWsEvent, setListFromApi, clear };
4314
+ const markAsRead = (id) => {
4315
+ store.updateState((state) => ({
4316
+ ...state,
4317
+ appNotifications: state.appNotifications.map(
4318
+ (n) => n.id === id ? { ...n, read: true } : n
4319
+ )
4320
+ }));
4321
+ };
4322
+ const markAllAsRead = () => {
4323
+ store.updateState((state) => ({
4324
+ ...state,
4325
+ appNotifications: state.appNotifications.map((n) => ({
4326
+ ...n,
4327
+ read: true
4328
+ }))
4329
+ }));
4330
+ };
4331
+ const dismiss = (id) => {
4332
+ store.updateState((state) => ({
4333
+ ...state,
4334
+ appNotifications: state.appNotifications.filter((n) => n.id !== id)
4335
+ }));
4336
+ };
4337
+ return {
4338
+ store,
4339
+ applyWsEvent,
4340
+ setListFromApi,
4341
+ clear,
4342
+ markAsRead,
4343
+ markAllAsRead,
4344
+ dismiss
4345
+ };
3907
4346
  }
3908
4347
 
3909
4348
  // src/stores/friends-store.ts
@@ -4194,12 +4633,14 @@ var WsRouter = class {
4194
4633
  }
4195
4634
  const decoded = decodeWsEvent(eventName, payload);
4196
4635
  if (!decoded) return;
4197
- this.deps.lobbyStore.applyWsEvent(decoded);
4198
- this.deps.gameStore.applyWsEvent(decoded);
4199
- this.deps.gameActionsStore.applyWsEvent(decoded);
4200
- this.deps.chatStore.applyWsEvent(decoded);
4201
- this.deps.dmThreadsStore.applyWsEvent(decoded);
4202
- this.deps.notificationsStore.applyWsEvent(decoded);
4636
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
4637
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
4638
+ this.deps.lobbyStore.applyWsEvent(event);
4639
+ this.deps.gameStore.applyWsEvent(event);
4640
+ this.deps.gameActionsStore.applyWsEvent(event);
4641
+ this.deps.chatStore.applyWsEvent(event);
4642
+ this.deps.dmThreadsStore.applyWsEvent(event);
4643
+ this.deps.notificationsStore.applyWsEvent(event);
4203
4644
  });
4204
4645
  }
4205
4646
  stop() {
@@ -4211,12 +4652,14 @@ var WsRouter = class {
4211
4652
  this.transport.subscribeEvent(eventName, (payload) => {
4212
4653
  const decoded = decodeWsEvent(eventName, payload);
4213
4654
  if (!decoded) return;
4214
- this.deps.lobbyStore.applyWsEvent(decoded);
4215
- this.deps.gameStore.applyWsEvent(decoded);
4216
- this.deps.gameActionsStore.applyWsEvent(decoded);
4217
- this.deps.chatStore.applyWsEvent(decoded);
4218
- this.deps.dmThreadsStore.applyWsEvent(decoded);
4219
- this.deps.notificationsStore.applyWsEvent(decoded);
4655
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
4656
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
4657
+ this.deps.lobbyStore.applyWsEvent(event);
4658
+ this.deps.gameStore.applyWsEvent(event);
4659
+ this.deps.gameActionsStore.applyWsEvent(event);
4660
+ this.deps.chatStore.applyWsEvent(event);
4661
+ this.deps.dmThreadsStore.applyWsEvent(event);
4662
+ this.deps.notificationsStore.applyWsEvent(event);
4220
4663
  });
4221
4664
  }
4222
4665
  }
@@ -4260,6 +4703,7 @@ var SDK = class {
4260
4703
  constructor(config) {
4261
4704
  const baseUrl = config.baseUrl || "http://localhost:3000";
4262
4705
  const logger2 = config.logger || logger;
4706
+ this.analytics = config.analytics ?? new NoopAnalyticsClient();
4263
4707
  this.http = config.httpClient || new HttpClient(
4264
4708
  baseUrl,
4265
4709
  config.storage,
@@ -4292,14 +4736,18 @@ var SDK = class {
4292
4736
  this.referrals = new Referrals(this.http, logger2);
4293
4737
  this.reports = new Reports(this.http, logger2);
4294
4738
  this.support = new Support(this.http, logger2);
4295
- this.markets = new Markets(this.http, logger2);
4739
+ this.markets = new Markets(this.http, logger2, this.wallet);
4296
4740
  this.wsTransport = config.wsTransport || new StandaloneWsTransport(baseUrl);
4297
4741
  this.wsTransport.setAppId(config.appId);
4298
4742
  this.lobbyStore = createLobbyStore(this.wsTransport);
4743
+ this.lobbies.setLobbyStore(this.lobbyStore);
4299
4744
  this.gameStore = createGameStore(this.wsTransport);
4745
+ this.games.setGameStore(this.gameStore);
4300
4746
  this.gameActionsStore = createGameActionsStore(this.wsTransport);
4747
+ this.games.setGameActionsStore(this.gameActionsStore);
4301
4748
  this.chatStore = createChatStore(this.wsTransport);
4302
4749
  this.dmThreadsStore = createDmThreadsStore();
4750
+ this.chat.setDmThreadsStore(this.dmThreadsStore);
4303
4751
  this.notificationsStore = createNotificationsStore();
4304
4752
  this.friendsStore = createFriendsStore();
4305
4753
  this.notifications = new Notifications(
@@ -4329,6 +4777,29 @@ var SDK = class {
4329
4777
  this.wsTransport.connect();
4330
4778
  await this.wsTransport.waitUntilConnected(timeoutMs);
4331
4779
  }
4780
+ /**
4781
+ * Handle the full deposit-to-queue lifecycle for a lobby.
4782
+ *
4783
+ * After the deposit is confirmed, the backend may not have processed the
4784
+ * `lobby.deposit.allConfirmed` event yet, so the lobby can still be in
4785
+ * 'preparing' status. This method re-fetches the lobby and explicitly
4786
+ * joins the queue if needed, then pushes the latest state to the store
4787
+ * so the UI transitions immediately.
4788
+ */
4789
+ async depositAndJoinQueue(lobbyId) {
4790
+ const result = await this.escrow.depositForLobby(lobbyId);
4791
+ if (!result.canProceedToQueue) {
4792
+ const lobby2 = await this.lobbies.getLobby(lobbyId);
4793
+ this.lobbyStore.setBaseState([lobby2]);
4794
+ return { ...result, lobby: lobby2 };
4795
+ }
4796
+ let lobby = await this.lobbies.getLobby(lobbyId);
4797
+ if (lobby.status === "preparing") {
4798
+ lobby = await this.lobbies.joinQueue(lobbyId);
4799
+ }
4800
+ this.lobbyStore.setBaseState([lobby]);
4801
+ return { ...result, lobby };
4802
+ }
4332
4803
  };
4333
4804
 
4334
4805
  // ../ws-shared-worker/dist/protocol.js
@@ -4436,6 +4907,26 @@ var SharedWorkerClient = class {
4436
4907
  }
4437
4908
  }
4438
4909
  }
4910
+ /**
4911
+ * Dispatch a synthetic local event directly to all registered handlers for
4912
+ * that event name (and wildcard handlers), without sending anything over the
4913
+ * wire. Used by SharedWorkerTransport to fire synthetic events like
4914
+ * `connection:reconnected` that originate from transport-layer logic rather
4915
+ * than from the server.
4916
+ */
4917
+ dispatchLocalEvent(eventName, payload) {
4918
+ const handlers = this.eventHandlers.get(eventName);
4919
+ if (handlers) {
4920
+ for (const handler of handlers) {
4921
+ handler(payload);
4922
+ }
4923
+ }
4924
+ if (this.wildcardHandlers.size > 0) {
4925
+ for (const handler of this.wildcardHandlers) {
4926
+ handler(eventName, payload);
4927
+ }
4928
+ }
4929
+ }
4439
4930
  postMessage(command) {
4440
4931
  this.port.postMessage(command);
4441
4932
  }
@@ -4445,6 +4936,10 @@ var SharedWorkerClient = class {
4445
4936
  var SharedWorkerTransport = class extends BaseWsTransport {
4446
4937
  constructor(workerUrl, socketUrl) {
4447
4938
  super();
4939
+ /** Tracks whether a successful connection has been established at least once,
4940
+ * so that subsequent ws_open events are treated as reconnects and the
4941
+ * `connection:reconnected` event is dispatched to subscribers. */
4942
+ this.hasConnectedBefore = false;
4448
4943
  this.client = new SharedWorkerClient(workerUrl);
4449
4944
  this.url = socketUrl;
4450
4945
  this.client.onMessage((event) => this.handleWorkerMessage(event));
@@ -4485,14 +4980,22 @@ var SharedWorkerTransport = class extends BaseWsTransport {
4485
4980
  }
4486
4981
  handleWorkerMessage(event) {
4487
4982
  switch (event.type) {
4488
- case "ws_open":
4983
+ case "ws_open": {
4984
+ const wasReconnect = this.hasConnectedBefore;
4985
+ this.hasConnectedBefore = true;
4489
4986
  this.updateConnectionState({
4490
4987
  connected: true,
4491
4988
  connecting: false,
4492
4989
  error: null
4493
4990
  });
4494
4991
  this.onReconnect();
4992
+ if (wasReconnect) {
4993
+ this.client.dispatchLocalEvent("connection:reconnected", {
4994
+ timestamp: Date.now()
4995
+ });
4996
+ }
4495
4997
  break;
4998
+ }
4496
4999
  case "ws_close":
4497
5000
  this.updateConnectionState({
4498
5001
  connected: false,
@@ -4529,6 +5032,7 @@ var import_utils = __toESM(require_dist(), 1);
4529
5032
  MIN_TRANSFER_AMOUNT,
4530
5033
  Markets,
4531
5034
  NodeStorage,
5035
+ NoopAnalyticsClient,
4532
5036
  Referrals,
4533
5037
  Reports,
4534
5038
  SDK,
@@ -4556,6 +5060,7 @@ var import_utils = __toESM(require_dist(), 1);
4556
5060
  formatMoneyMinor,
4557
5061
  isRetryableError,
4558
5062
  logger,
5063
+ selectGameLifecycleState,
4559
5064
  toMajor,
4560
5065
  withRetry
4561
5066
  });