@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.js CHANGED
@@ -696,6 +696,16 @@ var Auth = class {
696
696
  this.logger.debug("Token stored in storage", { tokenKey: TOKEN_KEY });
697
697
  return response;
698
698
  }
699
+ async loginWithExternalSignature(address, signedMessage, options) {
700
+ const response = await this.http.post("/auth/login-wallet", {
701
+ signedMessage,
702
+ address,
703
+ referralCode: options?.referralCode,
704
+ walletMeta: options?.walletMeta
705
+ });
706
+ this.storage.set(TOKEN_KEY, response.access_token);
707
+ return response;
708
+ }
699
709
  logout() {
700
710
  this.logger.debug("Auth.logout called");
701
711
  const hadToken = this.storage.get(TOKEN_KEY) !== null;
@@ -709,6 +719,10 @@ var Auth = class {
709
719
  });
710
720
  return isAuth;
711
721
  }
722
+ async getSessionStats() {
723
+ this.logger.debug("Auth.getSessionStats called");
724
+ return this.http.get("/auth/sessions/stats");
725
+ }
712
726
  async getLatestSessions(limit) {
713
727
  this.logger.debug("Auth.getLatestSessions called", { limit });
714
728
  const params = new URLSearchParams();
@@ -729,6 +743,9 @@ var Admin = class {
729
743
  this.http = http;
730
744
  this.logger = logger2;
731
745
  }
746
+ async getInternalBots() {
747
+ return this.http.get("/admin/internal-bots");
748
+ }
732
749
  async getUserById(id) {
733
750
  return this.http.get(`/admin/users/${id}`);
734
751
  }
@@ -936,11 +953,25 @@ var Users = class {
936
953
  async removeFriend(userId) {
937
954
  return this.http.delete(`/friends/${userId}`);
938
955
  }
939
- async getIncomingFriendRequests() {
940
- return this.http.get("/friends/requests/incoming");
956
+ async getIncomingFriendRequests(opts) {
957
+ const params = new URLSearchParams();
958
+ if (opts?.limit !== void 0)
959
+ params.append("limit", opts.limit.toString());
960
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
961
+ const qs = params.toString();
962
+ return this.http.get(
963
+ qs ? `/friends/requests/incoming?${qs}` : "/friends/requests/incoming"
964
+ );
941
965
  }
942
- async getOutgoingFriendRequests() {
943
- return this.http.get("/friends/requests/outgoing");
966
+ async getOutgoingFriendRequests(opts) {
967
+ const params = new URLSearchParams();
968
+ if (opts?.limit !== void 0)
969
+ params.append("limit", opts.limit.toString());
970
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
971
+ const qs = params.toString();
972
+ return this.http.get(
973
+ qs ? `/friends/requests/outgoing?${qs}` : "/friends/requests/outgoing"
974
+ );
944
975
  }
945
976
  async acceptFriendRequest(userId) {
946
977
  return this.http.post(
@@ -1029,6 +1060,9 @@ var FeatureFlags = class {
1029
1060
  this.loaded = true;
1030
1061
  return this.flags;
1031
1062
  }
1063
+ async getAdminFeatureFlags() {
1064
+ return this.http.get("/feature-flags/admin");
1065
+ }
1032
1066
  isEnabledFlag(name) {
1033
1067
  const flag = this.flags.find((f) => f.name === name);
1034
1068
  return flag?.enabled ?? false;
@@ -1048,10 +1082,11 @@ var FeatureFlags = class {
1048
1082
  }
1049
1083
  return flag;
1050
1084
  }
1051
- async createFeatureFlag(name, enabled) {
1085
+ async createFeatureFlag(name, enabled, description) {
1052
1086
  const flag = await this.http.post("/feature-flags", {
1053
1087
  name,
1054
- enabled
1088
+ enabled,
1089
+ ...description !== void 0 && { description }
1055
1090
  });
1056
1091
  this.flags.push(flag);
1057
1092
  return flag;
@@ -1064,11 +1099,17 @@ var Lobbies = class {
1064
1099
  this.http = http;
1065
1100
  this.logger = logger2;
1066
1101
  }
1102
+ /** Called by SDK after the store is created so mutations can update state directly. */
1103
+ setLobbyStore(store) {
1104
+ this.lobbyStore = store;
1105
+ }
1067
1106
  async createLobby(gameType, betAmount) {
1068
1107
  return this.http.post("/lobbies", { gameType, betAmount });
1069
1108
  }
1070
1109
  async getLobby(lobbyId) {
1071
- return this.http.get(`/lobbies/${lobbyId}`);
1110
+ const lobby = await this.http.get(`/lobbies/${lobbyId}`);
1111
+ this.lobbyStore?.setBaseState([lobby]);
1112
+ return lobby;
1072
1113
  }
1073
1114
  async inviteFriend(lobbyId, friendId) {
1074
1115
  return this.http.post(`/lobbies/${lobbyId}/invite`, {
@@ -1079,7 +1120,9 @@ var Lobbies = class {
1079
1120
  return this.http.post(`/lobbies/${lobbyId}/accept-invite`, {});
1080
1121
  }
1081
1122
  async joinLobby(lobbyId) {
1082
- return this.http.post(`/lobbies/${lobbyId}/join`, {});
1123
+ const lobby = await this.http.post(`/lobbies/${lobbyId}/join`, {});
1124
+ this.lobbyStore?.setBaseState([lobby]);
1125
+ return lobby;
1083
1126
  }
1084
1127
  async removePlayer(lobbyId, userId) {
1085
1128
  return this.http.delete(
@@ -1096,12 +1139,17 @@ var Lobbies = class {
1096
1139
  return this.http.post(`/lobbies/${lobbyId}/join-queue`, {});
1097
1140
  }
1098
1141
  async cancelQueue(lobbyId) {
1099
- return this.http.delete(`/lobbies/${lobbyId}/queue`);
1142
+ const lobby = await this.http.delete(`/lobbies/${lobbyId}/queue`);
1143
+ this.lobbyStore?.setBaseState([lobby]);
1144
+ return lobby;
1100
1145
  }
1101
1146
  async updateBetAmount(lobbyId, betAmount) {
1102
- return this.http.patch(`/lobbies/${lobbyId}/bet-amount`, {
1103
- betAmount
1104
- });
1147
+ const lobby = await this.http.patch(
1148
+ `/lobbies/${lobbyId}/bet-amount`,
1149
+ { betAmount }
1150
+ );
1151
+ this.lobbyStore?.setBaseState([lobby]);
1152
+ return lobby;
1105
1153
  }
1106
1154
  async playSound(lobbyId, sound) {
1107
1155
  return this.http.post(`/lobbies/${lobbyId}/sound`, {
@@ -1136,13 +1184,18 @@ var Lobbies = class {
1136
1184
  };
1137
1185
 
1138
1186
  // src/games.ts
1139
- import { Transaction as Transaction2 } from "@solana/web3.js";
1140
1187
  var Games = class {
1141
1188
  constructor(http, wallet, logger2) {
1142
1189
  this.http = http;
1143
1190
  this.wallet = wallet;
1144
1191
  this.logger = logger2;
1145
1192
  }
1193
+ setGameStore(store) {
1194
+ this.gameStore = store;
1195
+ }
1196
+ setGameActionsStore(store) {
1197
+ this.gameActionsStore = store;
1198
+ }
1146
1199
  async getAvailableGames() {
1147
1200
  return this.http.get("/games/available");
1148
1201
  }
@@ -1155,7 +1208,11 @@ var Games = class {
1155
1208
  return this.http.get("/games/metrics");
1156
1209
  }
1157
1210
  async getGame(gameId) {
1158
- return this.http.get(`/games/${gameId}`);
1211
+ const existing = this.gameStore?.store.getState().gamesById[gameId];
1212
+ if (existing?.status === "completed") return existing;
1213
+ const game = await this.http.get(`/games/${gameId}`);
1214
+ this.gameStore?.setBaseState([game]);
1215
+ return game;
1159
1216
  }
1160
1217
  /**
1161
1218
  * Get list of currently active (live) games. Public endpoint for spectating.
@@ -1182,7 +1239,13 @@ var Games = class {
1182
1239
  * Get current game state with timer information
1183
1240
  */
1184
1241
  async getGameState(gameId) {
1185
- return this.http.get(`/games/${gameId}/state`);
1242
+ const existing = this.gameActionsStore?.store.getState().statesByGameId[gameId];
1243
+ if (existing?.status === "completed") return existing;
1244
+ const state = await this.http.get(
1245
+ `/games/${gameId}/state`
1246
+ );
1247
+ this.gameActionsStore?.setBaseState(gameId, state);
1248
+ return state;
1186
1249
  }
1187
1250
  /**
1188
1251
  * Request a rematch for a completed game
@@ -1218,8 +1281,18 @@ var Games = class {
1218
1281
  { amountMinor, signedTransaction }
1219
1282
  );
1220
1283
  }
1284
+ /**
1285
+ * Confirm a donation that was already sent by the client (signAndSendTransaction path).
1286
+ */
1287
+ async confirmGameDonationSignature(gameId, amountMinor, signature) {
1288
+ return this.http.post(
1289
+ `/games/${gameId}/donate/confirm-signature`,
1290
+ { amountMinor, signature }
1291
+ );
1292
+ }
1221
1293
  /**
1222
1294
  * One-call donation flow: prepare -> sign -> submit.
1295
+ * Automatically uses the right signing path (embedded vs injected wallet).
1223
1296
  */
1224
1297
  async sendDonation(gameId, amountMinor) {
1225
1298
  if (!this.wallet.hasSigner()) {
@@ -1228,16 +1301,10 @@ var Games = class {
1228
1301
  );
1229
1302
  }
1230
1303
  const prepared = await this.prepareGameDonation(gameId, amountMinor);
1231
- const unsignedTx = Transaction2.from(base64ToBytes(prepared.transaction));
1232
- const signedTx = await this.wallet.signTransaction(unsignedTx);
1233
- const signedTransaction = bytesToBase64(
1234
- signedTx.serialize({ requireAllSignatures: false })
1235
- );
1236
- const result = await this.donateToGame(
1237
- gameId,
1238
- amountMinor,
1239
- signedTransaction
1240
- );
1304
+ const result = await this.wallet.signAndDispatch(prepared.transaction, {
1305
+ onSigned: (signedTxBase64) => this.donateToGame(gameId, amountMinor, signedTxBase64),
1306
+ onSignedAndSent: (sig) => this.confirmGameDonationSignature(gameId, amountMinor, sig)
1307
+ });
1241
1308
  return {
1242
1309
  ...result,
1243
1310
  escrowAddress: prepared.escrowAddress,
@@ -1336,6 +1403,9 @@ var Chat = class {
1336
1403
  this.logger = logger2;
1337
1404
  this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...retryOptions };
1338
1405
  }
1406
+ setDmThreadsStore(store) {
1407
+ this.dmThreadsStore = store;
1408
+ }
1339
1409
  encodeContextId(id) {
1340
1410
  return encodeURIComponent(id);
1341
1411
  }
@@ -1417,7 +1487,9 @@ var Chat = class {
1417
1487
  return response;
1418
1488
  }
1419
1489
  async listDmThreads() {
1420
- return this.http.get("/chat/dm/threads");
1490
+ const threads = await this.http.get("/chat/dm/threads");
1491
+ this.dmThreadsStore?.setBaseState(threads);
1492
+ return threads;
1421
1493
  }
1422
1494
  async getDmThread(dmKey) {
1423
1495
  return this.http.get(
@@ -1483,7 +1555,6 @@ var Challenges = class {
1483
1555
  };
1484
1556
 
1485
1557
  // src/tips.ts
1486
- import { Transaction as Transaction3 } from "@solana/web3.js";
1487
1558
  var Tips = class {
1488
1559
  constructor(http, wallet, chat, logger2) {
1489
1560
  this.http = http;
@@ -1510,21 +1581,32 @@ var Tips = class {
1510
1581
  );
1511
1582
  }
1512
1583
  const { publicKey: senderAddress } = await this.wallet.loadWallet();
1513
- const prepared = await this.prepare({ recipientUsername, amount });
1514
- const unsignedTx = Transaction3.from(base64ToBytes(prepared.transaction));
1515
- const signedTx = await this.wallet.signTransaction(unsignedTx);
1516
- const signedTxBase64 = bytesToBase64(
1517
- signedTx.serialize({ requireAllSignatures: false })
1518
- );
1519
- const transfer = await this.wallet.submitTransfer(
1520
- signedTxBase64,
1521
- senderAddress,
1522
- prepared.recipientAddress,
1523
- prepared.amount,
1524
- "USDC",
1525
- false,
1526
- prepared.recipientUsername
1527
- );
1584
+ const supportsPresign = !this.wallet.isSignAndSendMode();
1585
+ const prepared = await this.prepare({
1586
+ recipientUsername,
1587
+ amount,
1588
+ supportsPresign
1589
+ });
1590
+ const transfer = await this.wallet.signAndDispatch(prepared.transaction, {
1591
+ onSigned: (signedTxBase64) => this.wallet.submitTransfer(
1592
+ signedTxBase64,
1593
+ senderAddress,
1594
+ prepared.recipientAddress,
1595
+ prepared.amount,
1596
+ "USDC",
1597
+ false,
1598
+ prepared.recipientUsername
1599
+ ),
1600
+ onSignedAndSent: (sig) => this.wallet.confirmTransferSignature(
1601
+ sig,
1602
+ senderAddress,
1603
+ prepared.recipientAddress,
1604
+ prepared.amount,
1605
+ "USDC",
1606
+ false,
1607
+ prepared.recipientUsername
1608
+ )
1609
+ });
1528
1610
  const message = await this.chat.broadcastGlobalTip(
1529
1611
  prepared.recipientUserId,
1530
1612
  prepared.amount
@@ -1636,7 +1718,7 @@ var Achievements = class {
1636
1718
  };
1637
1719
 
1638
1720
  // src/wallet.ts
1639
- import { Transaction as Transaction4 } from "@solana/web3.js";
1721
+ import { Transaction as Transaction2 } from "@solana/web3.js";
1640
1722
  var TRANSFER_FEE_MINOR = 1e4;
1641
1723
  var MIN_TRANSFER_AMOUNT = 5e4;
1642
1724
  var MIN_SOL_TRANSFER_AMOUNT = 1e6;
@@ -1853,13 +1935,15 @@ var Wallet = class {
1853
1935
  );
1854
1936
  }
1855
1937
  try {
1938
+ const supportsPresign = !this.isSignAndSendMode();
1856
1939
  const response = await this.http.post(
1857
1940
  "/wallets/transfer/prepare",
1858
1941
  {
1859
1942
  senderAddress,
1860
1943
  recipient,
1861
1944
  amount: amountMinor,
1862
- token
1945
+ token,
1946
+ supportsPresign
1863
1947
  }
1864
1948
  );
1865
1949
  this.logger.debug("Transfer prepared", {
@@ -1943,6 +2027,29 @@ var Wallet = class {
1943
2027
  throw error;
1944
2028
  }
1945
2029
  }
2030
+ /**
2031
+ * Sign a prepared transaction and invoke the appropriate backend callback
2032
+ * based on the configured signer — without exposing the mode decision to callers.
2033
+ * - Embedded wallet (signAndSend): signs+sends, calls onSignedAndSent(signature)
2034
+ * - Injected wallet (signTransaction): signs locally, calls onSigned(signedTxBase64)
2035
+ */
2036
+ async signAndDispatch(unsignedTxBase64, handlers) {
2037
+ if (!this.signer) {
2038
+ throw new Error(
2039
+ "No signer configured. Call setSigner() with a WalletSigner implementation first."
2040
+ );
2041
+ }
2042
+ const unsignedTx = Transaction2.from(base64ToBytes(unsignedTxBase64));
2043
+ if (this.isSignAndSendMode()) {
2044
+ const sig = await this.signAndSendTransaction(unsignedTx);
2045
+ return handlers.onSignedAndSent(sig);
2046
+ }
2047
+ const signedTx = await this.signTransaction(unsignedTx);
2048
+ const signedBase64 = bytesToBase64(
2049
+ signedTx.serialize({ requireAllSignatures: false })
2050
+ );
2051
+ return handlers.onSigned(signedBase64);
2052
+ }
1946
2053
  /**
1947
2054
  * Full transfer flow in one call: prepare -> sign -> submit
1948
2055
  * Recipient can be username, .sol domain, or Solana address (resolved by backend).
@@ -1959,7 +2066,7 @@ var Wallet = class {
1959
2066
  amount,
1960
2067
  token
1961
2068
  );
1962
- const unsignedTx = Transaction4.from(base64ToBytes(prepared.transaction));
2069
+ const unsignedTx = Transaction2.from(base64ToBytes(prepared.transaction));
1963
2070
  let submitted;
1964
2071
  if (this.isSignAndSendMode()) {
1965
2072
  const signature = await this.signAndSendTransaction(unsignedTx);
@@ -1999,7 +2106,7 @@ var Wallet = class {
1999
2106
  };
2000
2107
 
2001
2108
  // src/escrow.ts
2002
- import { Transaction as Transaction5 } from "@solana/web3.js";
2109
+ import { Transaction as Transaction3 } from "@solana/web3.js";
2003
2110
  var Escrow = class {
2004
2111
  constructor(http, wallet, logger2) {
2005
2112
  this.http = http;
@@ -2027,12 +2134,32 @@ var Escrow = class {
2027
2134
  * initializes depositStatus, and prepares the unsigned transaction in one call.
2028
2135
  * Eliminates one HTTP round-trip vs startDeposits() + prepareDepositTransaction().
2029
2136
  */
2030
- async prepareAndStartDeposit(lobbyId) {
2137
+ async prepareAndStartDeposit(lobbyId, supportsPresign) {
2138
+ const presign = supportsPresign ?? !this.wallet.isSignAndSendMode();
2031
2139
  return this.http.post(
2032
2140
  `/escrow/lobby/${lobbyId}/deposit/prepare-and-start`,
2033
- {}
2141
+ { supportsPresign: presign }
2034
2142
  );
2035
2143
  }
2144
+ /**
2145
+ * Sign and submit a prepared (unsigned) deposit transaction using the
2146
+ * configured signer. Automatically picks the right path:
2147
+ * - Embedded wallet: signAndSendTransaction → confirmDepositSignature
2148
+ * - Injected wallet: signTransaction → submitDeposit
2149
+ * Returns the on-chain signature.
2150
+ */
2151
+ async signAndSubmitPreparedDeposit(lobbyId, unsignedTxBase64) {
2152
+ return this.wallet.signAndDispatch(unsignedTxBase64, {
2153
+ onSigned: async (signedTxBase64) => {
2154
+ const result = await this.submitDeposit(lobbyId, signedTxBase64);
2155
+ return result.signature;
2156
+ },
2157
+ onSignedAndSent: async (signature) => {
2158
+ await this.confirmDepositSignature(lobbyId, signature);
2159
+ return signature;
2160
+ }
2161
+ });
2162
+ }
2036
2163
  /**
2037
2164
  * Submit a signed deposit transaction
2038
2165
  * The transaction will be submitted to the Solana network and confirmed
@@ -2075,8 +2202,12 @@ var Escrow = class {
2075
2202
  "No signer configured. Use sdk.wallet.setSigner(...) first."
2076
2203
  );
2077
2204
  }
2078
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
2079
- const unsignedTx = Transaction5.from(base64ToBytes(transaction));
2205
+ const supportsPresign = !this.wallet.isSignAndSendMode();
2206
+ const { transaction } = await this.prepareAndStartDeposit(
2207
+ lobbyId,
2208
+ supportsPresign
2209
+ );
2210
+ const unsignedTx = Transaction3.from(base64ToBytes(transaction));
2080
2211
  let signature;
2081
2212
  if (this.wallet.isSignAndSendMode()) {
2082
2213
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
@@ -2110,8 +2241,13 @@ var Escrow = class {
2110
2241
  };
2111
2242
  }
2112
2243
  /**
2113
- * Zero-polling deposit variant using server-side awaitConfirmation.
2114
- * 2 HTTP calls total, 0 client-side polling. Ideal for agents.
2244
+ * Deposit for a lobby and wait until the calling user's own deposit is confirmed.
2245
+ * Ideal for agents: returns as soon as your deposit is on-chain without waiting
2246
+ * for the other player. The server auto-joins the queue when all players deposit.
2247
+ *
2248
+ * Confirmation is handled asynchronously by the transaction queue processor.
2249
+ * This method polls the deposit status endpoint until the signature appears as
2250
+ * confirmed (up to 60 seconds).
2115
2251
  *
2116
2252
  * Automatically uses signAndSendTransaction or signTransaction based on signer capability.
2117
2253
  */
@@ -2121,26 +2257,45 @@ var Escrow = class {
2121
2257
  "No signer configured. Use sdk.wallet.setSigner(...) first."
2122
2258
  );
2123
2259
  }
2124
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
2125
- const unsignedTx = Transaction5.from(base64ToBytes(transaction));
2260
+ const supportsPresign2 = !this.wallet.isSignAndSendMode();
2261
+ const { transaction } = await this.prepareAndStartDeposit(
2262
+ lobbyId,
2263
+ supportsPresign2
2264
+ );
2265
+ const unsignedTx = Transaction3.from(base64ToBytes(transaction));
2126
2266
  let signature;
2127
2267
  if (this.wallet.isSignAndSendMode()) {
2128
2268
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
2129
- await this.http.post(
2130
- `/escrow/lobby/${lobbyId}/deposit/confirm-signature?awaitConfirmation=true`,
2131
- { signature }
2132
- );
2269
+ await this.confirmDepositSignature(lobbyId, signature);
2133
2270
  } else {
2134
2271
  const signedTx = await this.wallet.signTransaction(unsignedTx);
2135
2272
  const signedBase64 = bytesToBase64(
2136
2273
  signedTx.serialize({ requireAllSignatures: false })
2137
2274
  );
2138
- const result = await this.http.post(`/escrow/lobby/${lobbyId}/deposit/submit?awaitConfirmation=true`, {
2139
- signedTransaction: signedBase64
2140
- });
2141
- signature = result.signature;
2275
+ const submitResult = await this.submitDeposit(lobbyId, signedBase64);
2276
+ signature = submitResult.signature;
2142
2277
  }
2143
- return { signature, status: "confirmed", canProceedToQueue: true };
2278
+ const MAX_WAIT_MS = 6e4;
2279
+ const POLL_INTERVAL_MS = 1e3;
2280
+ const startTime = Date.now();
2281
+ while (Date.now() - startTime < MAX_WAIT_MS) {
2282
+ const status = await this.getDepositStatus(lobbyId);
2283
+ const myDeposit = status.deposits.find(
2284
+ (d) => d.transactionHash === signature
2285
+ );
2286
+ if (myDeposit?.status === "confirmed") {
2287
+ return {
2288
+ signature,
2289
+ status: "confirmed",
2290
+ canProceedToQueue: status.canProceedToQueue
2291
+ };
2292
+ }
2293
+ if (status.allConfirmed && status.canProceedToQueue) {
2294
+ return { signature, status: "confirmed", canProceedToQueue: true };
2295
+ }
2296
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
2297
+ }
2298
+ return { signature, status: "pending", canProceedToQueue: false };
2144
2299
  }
2145
2300
  async claimLobbyDepositRefund(lobbyId, depositSignature) {
2146
2301
  return this.http.post(
@@ -2496,9 +2651,10 @@ var Support = class {
2496
2651
 
2497
2652
  // src/markets.ts
2498
2653
  var Markets = class {
2499
- constructor(http, logger2) {
2654
+ constructor(http, logger2, wallet) {
2500
2655
  this.http = http;
2501
2656
  this.logger = logger2;
2657
+ this.wallet = wallet;
2502
2658
  }
2503
2659
  /**
2504
2660
  * Get the prediction market state for a game.
@@ -2540,6 +2696,35 @@ var Markets = class {
2540
2696
  { signedTransaction, outcomeId, amountMinor }
2541
2697
  );
2542
2698
  }
2699
+ /**
2700
+ * Confirm a buy order that was already broadcast by the client (signAndSendTransaction path).
2701
+ */
2702
+ async confirmBuyOrderSignature(gameId, depositSignature, outcomeId, amountMinor) {
2703
+ return this.http.post(
2704
+ `/games/${gameId}/market/orders/buy/confirm-signature`,
2705
+ { depositSignature, outcomeId, amountMinor }
2706
+ );
2707
+ }
2708
+ /**
2709
+ * One-call buy order: prepare → sign → submit, automatically choosing
2710
+ * the right signing path (embedded vs injected wallet).
2711
+ */
2712
+ async buy(gameId, outcomeId, amountMinor) {
2713
+ if (!this.wallet?.hasSigner()) {
2714
+ throw new Error(
2715
+ "No signer configured. Use sdk.wallet.setSigner(...) first."
2716
+ );
2717
+ }
2718
+ const { transaction } = await this.prepareBuyOrder(
2719
+ gameId,
2720
+ outcomeId,
2721
+ amountMinor
2722
+ );
2723
+ return this.wallet.signAndDispatch(transaction, {
2724
+ onSigned: (signedTxBase64) => this.submitBuyOrder(gameId, signedTxBase64, outcomeId, amountMinor),
2725
+ onSignedAndSent: (sig) => this.confirmBuyOrderSignature(gameId, sig, outcomeId, amountMinor)
2726
+ });
2727
+ }
2543
2728
  /**
2544
2729
  * Sell shares back to the AMM pool.
2545
2730
  * @param gameId - The game ID
@@ -2589,6 +2774,22 @@ var Markets = class {
2589
2774
  }
2590
2775
  };
2591
2776
 
2777
+ // src/analytics.ts
2778
+ var NoopAnalyticsClient = class {
2779
+ userLoggedIn(_user, _meta) {
2780
+ }
2781
+ userLoggedOut() {
2782
+ }
2783
+ sessionRestored(_user) {
2784
+ }
2785
+ track(_event, _properties) {
2786
+ }
2787
+ setUserProperties(_properties) {
2788
+ }
2789
+ group(_groupType, _groupKey, _properties) {
2790
+ }
2791
+ };
2792
+
2592
2793
  // src/ws/standalone-transport.ts
2593
2794
  import { io } from "socket.io-client";
2594
2795
 
@@ -2682,22 +2883,35 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2682
2883
  this.accessToken = null;
2683
2884
  this.reconnectAttempts = 0;
2684
2885
  this.reconnectTimer = null;
2685
- this.periodicReconnectInterval = null;
2686
- this.maxReconnectAttempts = 5;
2687
- this.reconnectDelay = 1e3;
2688
- this.reconnectDelayMax = 3e4;
2689
- // 5 minutes
2886
+ this.needsReconnect = false;
2887
+ this.visibilityHandler = null;
2690
2888
  this.wildcardHandlers = /* @__PURE__ */ new Set();
2691
2889
  this.eventHandlers = /* @__PURE__ */ new Map();
2692
2890
  this.registeredEvents = /* @__PURE__ */ new Set();
2693
2891
  this.wildcardRegistered = false;
2694
2892
  this.url = url;
2893
+ if (typeof document !== "undefined") {
2894
+ this.visibilityHandler = () => {
2895
+ if (document.visibilityState === "visible" && !this.connectionState.connected) {
2896
+ this.reconnectImmediately();
2897
+ }
2898
+ };
2899
+ document.addEventListener("visibilitychange", this.visibilityHandler);
2900
+ }
2695
2901
  }
2696
2902
  connect() {
2903
+ if (this.reconnectTimer !== null) {
2904
+ clearTimeout(this.reconnectTimer);
2905
+ this.reconnectTimer = null;
2906
+ }
2907
+ this.reconnectAttempts = 0;
2697
2908
  this.ensureSocket();
2698
2909
  }
2699
2910
  disconnect() {
2700
- this.clearPeriodicReconnect();
2911
+ if (this.visibilityHandler && typeof document !== "undefined") {
2912
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
2913
+ this.visibilityHandler = null;
2914
+ }
2701
2915
  if (!this.socket) return;
2702
2916
  this.socket.disconnect();
2703
2917
  this.socket = null;
@@ -2796,13 +3010,13 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2796
3010
  reconnection: false
2797
3011
  });
2798
3012
  socket.on("connect", () => {
2799
- this.clearPeriodicReconnect();
2800
3013
  this.updateConnectionState({
2801
3014
  connected: true,
2802
3015
  connecting: false,
2803
3016
  error: null
2804
3017
  });
2805
- const wasReconnect = this.reconnectAttempts > 0;
3018
+ const wasReconnect = this.needsReconnect;
3019
+ this.needsReconnect = false;
2806
3020
  this.reconnectAttempts = 0;
2807
3021
  this.onReconnect();
2808
3022
  if (wasReconnect) {
@@ -2813,6 +3027,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2813
3027
  });
2814
3028
  socket.on("disconnect", (reason) => {
2815
3029
  this.updateConnectionState({ connected: false, connecting: false });
3030
+ this.needsReconnect = true;
2816
3031
  this.clearSocketForReconnect();
2817
3032
  if (reason === "io server disconnect") {
2818
3033
  this.handleAuthFailure("io server disconnect");
@@ -2822,6 +3037,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2822
3037
  });
2823
3038
  socket.on("connect_error", (error) => {
2824
3039
  const message = error?.message || "connect_error";
3040
+ this.needsReconnect = true;
2825
3041
  this.clearSocketForReconnect();
2826
3042
  if (message.includes("unauthorized") || message.includes("401")) {
2827
3043
  this.handleAuthFailure(message);
@@ -2849,31 +3065,31 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2849
3065
  this.socket.removeAllListeners();
2850
3066
  this.socket = null;
2851
3067
  }
2852
- clearPeriodicReconnect() {
2853
- if (this.periodicReconnectInterval !== null) {
2854
- clearInterval(this.periodicReconnectInterval);
2855
- this.periodicReconnectInterval = null;
3068
+ reconnectImmediately() {
3069
+ if (this.reconnectTimer !== null) {
3070
+ clearTimeout(this.reconnectTimer);
3071
+ this.reconnectTimer = null;
2856
3072
  }
2857
- }
2858
- startPeriodicReconnect() {
2859
- if (this.periodicReconnectInterval !== null) return;
2860
- this.periodicReconnectInterval = setInterval(() => {
2861
- if (this.connectionState.connected) return;
2862
- this.reconnectAttempts = 0;
2863
- this.ensureSocket();
2864
- }, _StandaloneWsTransport.PERIODIC_RECONNECT_MS);
3073
+ this.reconnectAttempts = 0;
3074
+ this.ensureSocket();
2865
3075
  }
2866
3076
  scheduleReconnect() {
2867
3077
  if (this.reconnectTimer !== null) return;
2868
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
2869
- this.startPeriodicReconnect();
2870
- return;
2871
- }
2872
- const delay = Math.min(
2873
- this.reconnectDelay * Math.pow(2, this.reconnectAttempts),
2874
- this.reconnectDelayMax
2875
- );
3078
+ const attempt = this.reconnectAttempts;
2876
3079
  this.reconnectAttempts += 1;
3080
+ let delay;
3081
+ if (attempt < _StandaloneWsTransport.FAST_RETRY_LIMIT) {
3082
+ delay = _StandaloneWsTransport.FAST_RETRY_DELAY_MS;
3083
+ } else {
3084
+ const backoffAttempt = attempt - _StandaloneWsTransport.FAST_RETRY_LIMIT;
3085
+ delay = Math.min(
3086
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS * Math.pow(2, backoffAttempt),
3087
+ _StandaloneWsTransport.MAX_BACKOFF_MS
3088
+ );
3089
+ if (attempt === _StandaloneWsTransport.FAST_RETRY_LIMIT) {
3090
+ this.dispatchEvent("connection:slow-retry", { timestamp: Date.now() });
3091
+ }
3092
+ }
2877
3093
  this.reconnectTimer = setTimeout(() => {
2878
3094
  this.reconnectTimer = null;
2879
3095
  this.ensureSocket();
@@ -2934,7 +3150,9 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2934
3150
  this.wildcardRegistered = true;
2935
3151
  }
2936
3152
  };
2937
- _StandaloneWsTransport.PERIODIC_RECONNECT_MS = 5 * 60 * 1e3;
3153
+ _StandaloneWsTransport.FAST_RETRY_LIMIT = 60;
3154
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS = 1e3;
3155
+ _StandaloneWsTransport.MAX_BACKOFF_MS = 3e4;
2938
3156
  var StandaloneWsTransport = _StandaloneWsTransport;
2939
3157
 
2940
3158
  // src/stores/store-utils.ts
@@ -2961,14 +3179,16 @@ function createLobbyStore(transport) {
2961
3179
  depositStatusByLobbyId: {}
2962
3180
  });
2963
3181
  const setBaseState = (lobbies) => {
2964
- const lobbiesById = {};
2965
- for (const lobby of lobbies) {
2966
- lobbiesById[lobby.id] = lobby;
2967
- }
2968
- store.updateState((state) => ({
2969
- ...state,
2970
- lobbiesById
2971
- }));
3182
+ store.updateState((state) => {
3183
+ const lobbiesById = { ...state.lobbiesById };
3184
+ for (const lobby of lobbies) {
3185
+ const existing = lobbiesById[lobby.id];
3186
+ if (!existing || lobby.updatedAt >= existing.updatedAt) {
3187
+ lobbiesById[lobby.id] = lobby;
3188
+ }
3189
+ }
3190
+ return { ...state, lobbiesById };
3191
+ });
2972
3192
  };
2973
3193
  const applyWsEvent = (event) => {
2974
3194
  switch (event.event) {
@@ -3100,12 +3320,23 @@ function createGameStore(transport) {
3100
3320
  gamesById: {},
3101
3321
  spectatorCounts: {}
3102
3322
  });
3323
+ const STATUS_RANK = {
3324
+ active: 0,
3325
+ completed: 1,
3326
+ abandoned: 1
3327
+ };
3103
3328
  const setBaseState = (games) => {
3104
- const gamesById = {};
3105
- for (const game of games) {
3106
- gamesById[game.gameId] = game;
3107
- }
3108
- store.updateState((state) => ({ ...state, gamesById }));
3329
+ store.updateState((state) => {
3330
+ const gamesById = { ...state.gamesById };
3331
+ for (const game of games) {
3332
+ const existing = gamesById[game.gameId];
3333
+ if (existing && STATUS_RANK[existing.status] > STATUS_RANK[game.status]) {
3334
+ continue;
3335
+ }
3336
+ gamesById[game.gameId] = game;
3337
+ }
3338
+ return { ...state, gamesById };
3339
+ });
3109
3340
  };
3110
3341
  const applyWsEvent = (event) => {
3111
3342
  switch (event.event) {
@@ -3182,6 +3413,19 @@ function createGameStore(transport) {
3182
3413
  }
3183
3414
 
3184
3415
  // src/stores/game-actions-store.ts
3416
+ function selectGameLifecycleState(gameState) {
3417
+ const s = gameState;
3418
+ if (!s) return null;
3419
+ const raw = s;
3420
+ return {
3421
+ status: raw.status,
3422
+ winnerId: raw.winnerId ?? null,
3423
+ betAmount: raw.betAmount ?? 0,
3424
+ wonAmount: raw.wonAmount ?? null,
3425
+ totalPotMinor: raw.totalPotMinor,
3426
+ currentPlayerId: raw.currentPlayerId ?? null
3427
+ };
3428
+ }
3185
3429
  var isRpsCompletionPayload = (payload) => payload.gameType === "rock-paper-scissors";
3186
3430
  function createGameActionsStore(transport) {
3187
3431
  const store = createSdkStore({
@@ -3203,8 +3447,21 @@ function createGameActionsStore(transport) {
3203
3447
  });
3204
3448
  };
3205
3449
  const isNonRpsState = (state) => Boolean(state && !isRpsState(state));
3450
+ const pendingEvents = /* @__PURE__ */ new Map();
3451
+ function enqueue(gameId, event) {
3452
+ const q = pendingEvents.get(gameId) ?? [];
3453
+ q.push(event);
3454
+ pendingEvents.set(gameId, q);
3455
+ }
3456
+ function drainQueue(gameId) {
3457
+ const q = pendingEvents.get(gameId);
3458
+ if (!q?.length) return;
3459
+ pendingEvents.delete(gameId);
3460
+ for (const ev of q) applyWsEvent(ev);
3461
+ }
3206
3462
  const setBaseState = (gameId, state) => {
3207
3463
  updateState(gameId, state);
3464
+ drainQueue(gameId);
3208
3465
  };
3209
3466
  const clearState = (gameId) => {
3210
3467
  store.updateState((state) => {
@@ -3214,6 +3471,7 @@ function createGameActionsStore(transport) {
3214
3471
  const { [gameId]: _, ...rest } = state.statesByGameId;
3215
3472
  return { ...state, statesByGameId: rest };
3216
3473
  });
3474
+ pendingEvents.delete(gameId);
3217
3475
  };
3218
3476
  const applyWsEvent = (event) => {
3219
3477
  switch (event.event) {
@@ -3232,7 +3490,10 @@ function createGameActionsStore(transport) {
3232
3490
  }
3233
3491
  case "game:rps:starting": {
3234
3492
  const current = store.getState().statesByGameId[event.payload.gameId];
3235
- if (!current || !isRpsState(current)) return;
3493
+ if (!current || !isRpsState(current)) {
3494
+ enqueue(event.payload.gameId, event);
3495
+ return;
3496
+ }
3236
3497
  const betAmount = typeof event.payload.betAmount === "number" ? event.payload.betAmount : void 0;
3237
3498
  const startedAt = typeof event.payload.startedAt === "string" ? event.payload.startedAt : current.roundState.startedAt;
3238
3499
  const bufferEndsAt = typeof event.payload.bufferEndsAt === "string" ? event.payload.bufferEndsAt : current.roundState.selectionEndsAt;
@@ -3253,7 +3514,10 @@ function createGameActionsStore(transport) {
3253
3514
  }
3254
3515
  case "game:rps:round:started": {
3255
3516
  const current = store.getState().statesByGameId[event.payload.gameId];
3256
- if (!current || !isRpsState(current)) return;
3517
+ if (!current || !isRpsState(current)) {
3518
+ enqueue(event.payload.gameId, event);
3519
+ return;
3520
+ }
3257
3521
  const actions = {};
3258
3522
  const baseUsers = /* @__PURE__ */ new Set();
3259
3523
  Object.keys(current.roundState.actions).forEach(
@@ -3287,7 +3551,10 @@ function createGameActionsStore(transport) {
3287
3551
  }
3288
3552
  case "game:rps:action:received": {
3289
3553
  const current = store.getState().statesByGameId[event.payload.gameId];
3290
- if (!current || !isRpsState(current)) return;
3554
+ if (!current || !isRpsState(current)) {
3555
+ enqueue(event.payload.gameId, event);
3556
+ return;
3557
+ }
3291
3558
  const updated = {
3292
3559
  ...current,
3293
3560
  roundState: {
@@ -3306,7 +3573,10 @@ function createGameActionsStore(transport) {
3306
3573
  }
3307
3574
  case "game:rps:timer:cutoff": {
3308
3575
  const current = store.getState().statesByGameId[event.payload.gameId];
3309
- if (!current || !isRpsState(current)) return;
3576
+ if (!current || !isRpsState(current)) {
3577
+ enqueue(event.payload.gameId, event);
3578
+ return;
3579
+ }
3310
3580
  const updated = {
3311
3581
  ...current,
3312
3582
  roundState: {
@@ -3320,7 +3590,10 @@ function createGameActionsStore(transport) {
3320
3590
  }
3321
3591
  case "game:rps:round:reveal": {
3322
3592
  const current = store.getState().statesByGameId[event.payload.gameId];
3323
- if (!current || !isRpsState(current)) return;
3593
+ if (!current || !isRpsState(current)) {
3594
+ enqueue(event.payload.gameId, event);
3595
+ return;
3596
+ }
3324
3597
  const actions = {};
3325
3598
  const payloadActions = event.payload.actions;
3326
3599
  Object.keys(payloadActions || {}).forEach((userId) => {
@@ -3344,7 +3617,10 @@ function createGameActionsStore(transport) {
3344
3617
  }
3345
3618
  case "game:rps:round:completed": {
3346
3619
  const current = store.getState().statesByGameId[event.payload.gameId];
3347
- if (!current || !isRpsState(current)) return;
3620
+ if (!current || !isRpsState(current)) {
3621
+ enqueue(event.payload.gameId, event);
3622
+ return;
3623
+ }
3348
3624
  const roundHistory = [
3349
3625
  ...current.roundHistory || [],
3350
3626
  {
@@ -3369,7 +3645,10 @@ function createGameActionsStore(transport) {
3369
3645
  }
3370
3646
  case "game:rps:timeout": {
3371
3647
  const current = store.getState().statesByGameId[event.payload.gameId];
3372
- if (!current || !isRpsState(current)) return;
3648
+ if (!current || !isRpsState(current)) {
3649
+ enqueue(event.payload.gameId, event);
3650
+ return;
3651
+ }
3373
3652
  const timedOutUser = event.payload.playerId;
3374
3653
  const action = event.payload.action;
3375
3654
  const updated = {
@@ -3394,7 +3673,10 @@ function createGameActionsStore(transport) {
3394
3673
  const payload = event.payload;
3395
3674
  const { gameId } = payload;
3396
3675
  const current = store.getState().statesByGameId[gameId];
3397
- if (!current) return;
3676
+ if (!current) {
3677
+ enqueue(gameId, event);
3678
+ return;
3679
+ }
3398
3680
  const updated = isRpsCompletionPayload(payload) && isRpsState(current) ? {
3399
3681
  ...current,
3400
3682
  status: "completed",
@@ -3443,11 +3725,15 @@ function createGameActionsStore(transport) {
3443
3725
  const current = store.getState().statesByGameId[gameId];
3444
3726
  const updated = current ? { ...current, ...incoming } : incoming;
3445
3727
  updateState(gameId, updated);
3728
+ drainQueue(gameId);
3446
3729
  break;
3447
3730
  }
3448
3731
  case "game:rematch:requested": {
3449
3732
  const current = store.getState().statesByGameId[event.payload.gameId];
3450
- if (!current) return;
3733
+ if (!current) {
3734
+ enqueue(event.payload.gameId, event);
3735
+ return;
3736
+ }
3451
3737
  const requestedBy = event.payload.requestedBy;
3452
3738
  const userId = event.payload.userId;
3453
3739
  const requested = new Set(
@@ -3463,7 +3749,10 @@ function createGameActionsStore(transport) {
3463
3749
  }
3464
3750
  case "game:rematch:cancelled": {
3465
3751
  const current = store.getState().statesByGameId[event.payload.gameId];
3466
- if (!current) return;
3752
+ if (!current) {
3753
+ enqueue(event.payload.gameId, event);
3754
+ return;
3755
+ }
3467
3756
  const requestedBy = event.payload.requestedBy ?? [];
3468
3757
  const updated = {
3469
3758
  ...current,
@@ -3474,7 +3763,10 @@ function createGameActionsStore(transport) {
3474
3763
  }
3475
3764
  case "game:rematch:started": {
3476
3765
  const current = store.getState().statesByGameId[event.payload.gameId];
3477
- if (!current) return;
3766
+ if (!current) {
3767
+ enqueue(event.payload.gameId, event);
3768
+ return;
3769
+ }
3478
3770
  const updated = {
3479
3771
  ...current,
3480
3772
  rematchRequestedBy: event.payload.playerIds ?? []
@@ -3485,7 +3777,10 @@ function createGameActionsStore(transport) {
3485
3777
  case "game:pot:updated": {
3486
3778
  const { gameId, totalPotMinor } = event.payload;
3487
3779
  const current = store.getState().statesByGameId[gameId];
3488
- if (!current) return;
3780
+ if (!current) {
3781
+ enqueue(gameId, event);
3782
+ return;
3783
+ }
3489
3784
  const updated = {
3490
3785
  ...current,
3491
3786
  totalPotMinor
@@ -3498,12 +3793,123 @@ function createGameActionsStore(transport) {
3498
3793
  }
3499
3794
  };
3500
3795
  const joinGame = (gameId) => transport.joinRoom(`game:${gameId}`);
3796
+ const getCountdownDigit = (gameId, nowMs) => {
3797
+ const state = store.getState().statesByGameId[gameId];
3798
+ if (!state) return null;
3799
+ if (isRpsState(state)) {
3800
+ if (state.roundState.phase !== "starting") return null;
3801
+ const remaining = new Date(state.roundState.selectionEndsAt).getTime() - nowMs;
3802
+ if (remaining <= 0) return null;
3803
+ return Math.ceil(remaining / 1e3);
3804
+ }
3805
+ const bufferEndsAt = state.bufferEndsAt;
3806
+ if (bufferEndsAt) {
3807
+ const remaining = new Date(bufferEndsAt).getTime() - nowMs;
3808
+ if (remaining <= 0) return null;
3809
+ return Math.ceil(remaining / 1e3);
3810
+ }
3811
+ return null;
3812
+ };
3813
+ const getChessClockTimes = (gameId, nowMs) => {
3814
+ const state = store.getState().statesByGameId[gameId];
3815
+ if (!state || state.gameType !== "chess") return null;
3816
+ const s = state;
3817
+ let whiteMs = s.whiteTimeMs ?? 0;
3818
+ let blackMs = s.blackTimeMs ?? 0;
3819
+ if (s.status === "active" && s.currentPlayerId) {
3820
+ const startedAt = Date.parse(s.turnStartedAt);
3821
+ if (!Number.isNaN(startedAt)) {
3822
+ const elapsed = Math.max(0, nowMs - startedAt);
3823
+ if (s.currentPlayerId === s.whitePlayerId) {
3824
+ whiteMs = Math.max(0, whiteMs - elapsed);
3825
+ } else if (s.currentPlayerId === s.blackPlayerId) {
3826
+ blackMs = Math.max(0, blackMs - elapsed);
3827
+ }
3828
+ }
3829
+ }
3830
+ return { whiteTimeMs: whiteMs, blackTimeMs: blackMs };
3831
+ };
3832
+ const getChessCapturedPieces = (gameId) => {
3833
+ const state = store.getState().statesByGameId[gameId];
3834
+ if (!state || state.gameType !== "chess") return null;
3835
+ const fen = state.fen;
3836
+ if (!fen) return { capturedByWhite: [], capturedByBlack: [] };
3837
+ const placement = fen.split(" ")[0];
3838
+ const white = {};
3839
+ const black = {};
3840
+ for (const char of placement) {
3841
+ if (char === "/" || char >= "1" && char <= "8") continue;
3842
+ const lower = char.toLowerCase();
3843
+ if (char === lower) {
3844
+ black[lower] = (black[lower] ?? 0) + 1;
3845
+ } else {
3846
+ white[lower] = (white[lower] ?? 0) + 1;
3847
+ }
3848
+ }
3849
+ const INITIAL = {
3850
+ p: 8,
3851
+ r: 2,
3852
+ n: 2,
3853
+ b: 2,
3854
+ q: 1,
3855
+ k: 1
3856
+ };
3857
+ const capturedByWhite = [];
3858
+ const capturedByBlack = [];
3859
+ for (const [type, initial] of Object.entries(INITIAL)) {
3860
+ const missingBlack = initial - (black[type] ?? 0);
3861
+ for (let i = 0; i < missingBlack; i++) capturedByWhite.push(`b${type}`);
3862
+ const missingWhite = initial - (white[type] ?? 0);
3863
+ for (let i = 0; i < missingWhite; i++) capturedByBlack.push(`w${type}`);
3864
+ }
3865
+ return { capturedByWhite, capturedByBlack };
3866
+ };
3867
+ const getTicTacToeClockTimes = (gameId, nowMs) => {
3868
+ const state = store.getState().statesByGameId[gameId];
3869
+ if (!state || state.gameType !== "tic-tac-toe") return null;
3870
+ const s = state;
3871
+ let xMs = s.xTimeMs ?? 0;
3872
+ let oMs = s.oTimeMs ?? 0;
3873
+ if (s.status === "active" && s.currentPlayerId) {
3874
+ const currentMark = s.playerMarks[s.currentPlayerId];
3875
+ const startedAt = Date.parse(s.turnStartedAt);
3876
+ if (!Number.isNaN(startedAt)) {
3877
+ const elapsed = Math.max(0, nowMs - startedAt);
3878
+ if (currentMark === "X") xMs = Math.max(0, xMs - elapsed);
3879
+ else if (currentMark === "O") oMs = Math.max(0, oMs - elapsed);
3880
+ }
3881
+ }
3882
+ return { xTimeMs: xMs, oTimeMs: oMs };
3883
+ };
3884
+ const getConnect4ClockTimes = (gameId, nowMs) => {
3885
+ const state = store.getState().statesByGameId[gameId];
3886
+ if (!state || state.gameType !== "connect-four") return null;
3887
+ const s = state;
3888
+ let redMs = s.redTimeMs ?? 0;
3889
+ let yellowMs = s.yellowTimeMs ?? 0;
3890
+ if (s.status === "active" && s.currentPlayerId) {
3891
+ const currentColor = s.playerColors[s.currentPlayerId];
3892
+ const startedAt = Date.parse(s.turnStartedAt);
3893
+ if (!Number.isNaN(startedAt)) {
3894
+ const elapsed = Math.max(0, nowMs - startedAt);
3895
+ if (currentColor === "RED") redMs = Math.max(0, redMs - elapsed);
3896
+ else if (currentColor === "YELLOW")
3897
+ yellowMs = Math.max(0, yellowMs - elapsed);
3898
+ }
3899
+ }
3900
+ return { redTimeMs: redMs, yellowTimeMs: yellowMs };
3901
+ };
3501
3902
  return {
3502
3903
  store,
3503
3904
  setBaseState,
3504
3905
  clearState,
3505
3906
  applyWsEvent,
3506
- joinGame
3907
+ joinGame,
3908
+ getCountdownDigit,
3909
+ getChessClockTimes,
3910
+ getChessCapturedPieces,
3911
+ getTicTacToeClockTimes,
3912
+ getConnect4ClockTimes
3507
3913
  };
3508
3914
  }
3509
3915
  function isRpsState(state) {
@@ -3851,7 +4257,38 @@ function createNotificationsStore() {
3851
4257
  appNotifications: []
3852
4258
  }));
3853
4259
  };
3854
- return { store, applyWsEvent, setListFromApi, clear };
4260
+ const markAsRead = (id) => {
4261
+ store.updateState((state) => ({
4262
+ ...state,
4263
+ appNotifications: state.appNotifications.map(
4264
+ (n) => n.id === id ? { ...n, read: true } : n
4265
+ )
4266
+ }));
4267
+ };
4268
+ const markAllAsRead = () => {
4269
+ store.updateState((state) => ({
4270
+ ...state,
4271
+ appNotifications: state.appNotifications.map((n) => ({
4272
+ ...n,
4273
+ read: true
4274
+ }))
4275
+ }));
4276
+ };
4277
+ const dismiss = (id) => {
4278
+ store.updateState((state) => ({
4279
+ ...state,
4280
+ appNotifications: state.appNotifications.filter((n) => n.id !== id)
4281
+ }));
4282
+ };
4283
+ return {
4284
+ store,
4285
+ applyWsEvent,
4286
+ setListFromApi,
4287
+ clear,
4288
+ markAsRead,
4289
+ markAllAsRead,
4290
+ dismiss
4291
+ };
3855
4292
  }
3856
4293
 
3857
4294
  // src/stores/friends-store.ts
@@ -4142,12 +4579,14 @@ var WsRouter = class {
4142
4579
  }
4143
4580
  const decoded = decodeWsEvent(eventName, payload);
4144
4581
  if (!decoded) return;
4145
- this.deps.lobbyStore.applyWsEvent(decoded);
4146
- this.deps.gameStore.applyWsEvent(decoded);
4147
- this.deps.gameActionsStore.applyWsEvent(decoded);
4148
- this.deps.chatStore.applyWsEvent(decoded);
4149
- this.deps.dmThreadsStore.applyWsEvent(decoded);
4150
- this.deps.notificationsStore.applyWsEvent(decoded);
4582
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
4583
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
4584
+ this.deps.lobbyStore.applyWsEvent(event);
4585
+ this.deps.gameStore.applyWsEvent(event);
4586
+ this.deps.gameActionsStore.applyWsEvent(event);
4587
+ this.deps.chatStore.applyWsEvent(event);
4588
+ this.deps.dmThreadsStore.applyWsEvent(event);
4589
+ this.deps.notificationsStore.applyWsEvent(event);
4151
4590
  });
4152
4591
  }
4153
4592
  stop() {
@@ -4159,12 +4598,14 @@ var WsRouter = class {
4159
4598
  this.transport.subscribeEvent(eventName, (payload) => {
4160
4599
  const decoded = decodeWsEvent(eventName, payload);
4161
4600
  if (!decoded) return;
4162
- this.deps.lobbyStore.applyWsEvent(decoded);
4163
- this.deps.gameStore.applyWsEvent(decoded);
4164
- this.deps.gameActionsStore.applyWsEvent(decoded);
4165
- this.deps.chatStore.applyWsEvent(decoded);
4166
- this.deps.dmThreadsStore.applyWsEvent(decoded);
4167
- this.deps.notificationsStore.applyWsEvent(decoded);
4601
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
4602
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
4603
+ this.deps.lobbyStore.applyWsEvent(event);
4604
+ this.deps.gameStore.applyWsEvent(event);
4605
+ this.deps.gameActionsStore.applyWsEvent(event);
4606
+ this.deps.chatStore.applyWsEvent(event);
4607
+ this.deps.dmThreadsStore.applyWsEvent(event);
4608
+ this.deps.notificationsStore.applyWsEvent(event);
4168
4609
  });
4169
4610
  }
4170
4611
  }
@@ -4208,6 +4649,7 @@ var SDK = class {
4208
4649
  constructor(config) {
4209
4650
  const baseUrl = config.baseUrl || "http://localhost:3000";
4210
4651
  const logger2 = config.logger || logger;
4652
+ this.analytics = config.analytics ?? new NoopAnalyticsClient();
4211
4653
  this.http = config.httpClient || new HttpClient(
4212
4654
  baseUrl,
4213
4655
  config.storage,
@@ -4240,14 +4682,18 @@ var SDK = class {
4240
4682
  this.referrals = new Referrals(this.http, logger2);
4241
4683
  this.reports = new Reports(this.http, logger2);
4242
4684
  this.support = new Support(this.http, logger2);
4243
- this.markets = new Markets(this.http, logger2);
4685
+ this.markets = new Markets(this.http, logger2, this.wallet);
4244
4686
  this.wsTransport = config.wsTransport || new StandaloneWsTransport(baseUrl);
4245
4687
  this.wsTransport.setAppId(config.appId);
4246
4688
  this.lobbyStore = createLobbyStore(this.wsTransport);
4689
+ this.lobbies.setLobbyStore(this.lobbyStore);
4247
4690
  this.gameStore = createGameStore(this.wsTransport);
4691
+ this.games.setGameStore(this.gameStore);
4248
4692
  this.gameActionsStore = createGameActionsStore(this.wsTransport);
4693
+ this.games.setGameActionsStore(this.gameActionsStore);
4249
4694
  this.chatStore = createChatStore(this.wsTransport);
4250
4695
  this.dmThreadsStore = createDmThreadsStore();
4696
+ this.chat.setDmThreadsStore(this.dmThreadsStore);
4251
4697
  this.notificationsStore = createNotificationsStore();
4252
4698
  this.friendsStore = createFriendsStore();
4253
4699
  this.notifications = new Notifications(
@@ -4277,6 +4723,29 @@ var SDK = class {
4277
4723
  this.wsTransport.connect();
4278
4724
  await this.wsTransport.waitUntilConnected(timeoutMs);
4279
4725
  }
4726
+ /**
4727
+ * Handle the full deposit-to-queue lifecycle for a lobby.
4728
+ *
4729
+ * After the deposit is confirmed, the backend may not have processed the
4730
+ * `lobby.deposit.allConfirmed` event yet, so the lobby can still be in
4731
+ * 'preparing' status. This method re-fetches the lobby and explicitly
4732
+ * joins the queue if needed, then pushes the latest state to the store
4733
+ * so the UI transitions immediately.
4734
+ */
4735
+ async depositAndJoinQueue(lobbyId) {
4736
+ const result = await this.escrow.depositForLobby(lobbyId);
4737
+ if (!result.canProceedToQueue) {
4738
+ const lobby2 = await this.lobbies.getLobby(lobbyId);
4739
+ this.lobbyStore.setBaseState([lobby2]);
4740
+ return { ...result, lobby: lobby2 };
4741
+ }
4742
+ let lobby = await this.lobbies.getLobby(lobbyId);
4743
+ if (lobby.status === "preparing") {
4744
+ lobby = await this.lobbies.joinQueue(lobbyId);
4745
+ }
4746
+ this.lobbyStore.setBaseState([lobby]);
4747
+ return { ...result, lobby };
4748
+ }
4280
4749
  };
4281
4750
 
4282
4751
  // ../ws-shared-worker/dist/protocol.js
@@ -4384,6 +4853,26 @@ var SharedWorkerClient = class {
4384
4853
  }
4385
4854
  }
4386
4855
  }
4856
+ /**
4857
+ * Dispatch a synthetic local event directly to all registered handlers for
4858
+ * that event name (and wildcard handlers), without sending anything over the
4859
+ * wire. Used by SharedWorkerTransport to fire synthetic events like
4860
+ * `connection:reconnected` that originate from transport-layer logic rather
4861
+ * than from the server.
4862
+ */
4863
+ dispatchLocalEvent(eventName, payload) {
4864
+ const handlers = this.eventHandlers.get(eventName);
4865
+ if (handlers) {
4866
+ for (const handler of handlers) {
4867
+ handler(payload);
4868
+ }
4869
+ }
4870
+ if (this.wildcardHandlers.size > 0) {
4871
+ for (const handler of this.wildcardHandlers) {
4872
+ handler(eventName, payload);
4873
+ }
4874
+ }
4875
+ }
4387
4876
  postMessage(command) {
4388
4877
  this.port.postMessage(command);
4389
4878
  }
@@ -4393,6 +4882,10 @@ var SharedWorkerClient = class {
4393
4882
  var SharedWorkerTransport = class extends BaseWsTransport {
4394
4883
  constructor(workerUrl, socketUrl) {
4395
4884
  super();
4885
+ /** Tracks whether a successful connection has been established at least once,
4886
+ * so that subsequent ws_open events are treated as reconnects and the
4887
+ * `connection:reconnected` event is dispatched to subscribers. */
4888
+ this.hasConnectedBefore = false;
4396
4889
  this.client = new SharedWorkerClient(workerUrl);
4397
4890
  this.url = socketUrl;
4398
4891
  this.client.onMessage((event) => this.handleWorkerMessage(event));
@@ -4433,14 +4926,22 @@ var SharedWorkerTransport = class extends BaseWsTransport {
4433
4926
  }
4434
4927
  handleWorkerMessage(event) {
4435
4928
  switch (event.type) {
4436
- case "ws_open":
4929
+ case "ws_open": {
4930
+ const wasReconnect = this.hasConnectedBefore;
4931
+ this.hasConnectedBefore = true;
4437
4932
  this.updateConnectionState({
4438
4933
  connected: true,
4439
4934
  connecting: false,
4440
4935
  error: null
4441
4936
  });
4442
4937
  this.onReconnect();
4938
+ if (wasReconnect) {
4939
+ this.client.dispatchLocalEvent("connection:reconnected", {
4940
+ timestamp: Date.now()
4941
+ });
4942
+ }
4443
4943
  break;
4944
+ }
4444
4945
  case "ws_close":
4445
4946
  this.updateConnectionState({
4446
4947
  connected: false,
@@ -4479,6 +4980,7 @@ export {
4479
4980
  MIN_TRANSFER_AMOUNT,
4480
4981
  Markets,
4481
4982
  NodeStorage,
4983
+ NoopAnalyticsClient,
4482
4984
  Referrals,
4483
4985
  Reports,
4484
4986
  SDK,
@@ -4506,6 +5008,7 @@ export {
4506
5008
  export_formatMoneyMinor as formatMoneyMinor,
4507
5009
  isRetryableError,
4508
5010
  logger,
5011
+ selectGameLifecycleState,
4509
5012
  export_toMajor as toMajor,
4510
5013
  withRetry
4511
5014
  };