@dimcool/sdk 0.1.28 → 0.1.30

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();
@@ -936,11 +950,25 @@ var Users = class {
936
950
  async removeFriend(userId) {
937
951
  return this.http.delete(`/friends/${userId}`);
938
952
  }
939
- async getIncomingFriendRequests() {
940
- return this.http.get("/friends/requests/incoming");
953
+ async getIncomingFriendRequests(opts) {
954
+ const params = new URLSearchParams();
955
+ if (opts?.limit !== void 0)
956
+ params.append("limit", opts.limit.toString());
957
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
958
+ const qs = params.toString();
959
+ return this.http.get(
960
+ qs ? `/friends/requests/incoming?${qs}` : "/friends/requests/incoming"
961
+ );
941
962
  }
942
- async getOutgoingFriendRequests() {
943
- return this.http.get("/friends/requests/outgoing");
963
+ async getOutgoingFriendRequests(opts) {
964
+ const params = new URLSearchParams();
965
+ if (opts?.limit !== void 0)
966
+ params.append("limit", opts.limit.toString());
967
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
968
+ const qs = params.toString();
969
+ return this.http.get(
970
+ qs ? `/friends/requests/outgoing?${qs}` : "/friends/requests/outgoing"
971
+ );
944
972
  }
945
973
  async acceptFriendRequest(userId) {
946
974
  return this.http.post(
@@ -1029,6 +1057,9 @@ var FeatureFlags = class {
1029
1057
  this.loaded = true;
1030
1058
  return this.flags;
1031
1059
  }
1060
+ async getAdminFeatureFlags() {
1061
+ return this.http.get("/feature-flags/admin");
1062
+ }
1032
1063
  isEnabledFlag(name) {
1033
1064
  const flag = this.flags.find((f) => f.name === name);
1034
1065
  return flag?.enabled ?? false;
@@ -1048,10 +1079,11 @@ var FeatureFlags = class {
1048
1079
  }
1049
1080
  return flag;
1050
1081
  }
1051
- async createFeatureFlag(name, enabled) {
1082
+ async createFeatureFlag(name, enabled, description) {
1052
1083
  const flag = await this.http.post("/feature-flags", {
1053
1084
  name,
1054
- enabled
1085
+ enabled,
1086
+ ...description !== void 0 && { description }
1055
1087
  });
1056
1088
  this.flags.push(flag);
1057
1089
  return flag;
@@ -1064,11 +1096,17 @@ var Lobbies = class {
1064
1096
  this.http = http;
1065
1097
  this.logger = logger2;
1066
1098
  }
1099
+ /** Called by SDK after the store is created so mutations can update state directly. */
1100
+ setLobbyStore(store) {
1101
+ this.lobbyStore = store;
1102
+ }
1067
1103
  async createLobby(gameType, betAmount) {
1068
1104
  return this.http.post("/lobbies", { gameType, betAmount });
1069
1105
  }
1070
1106
  async getLobby(lobbyId) {
1071
- return this.http.get(`/lobbies/${lobbyId}`);
1107
+ const lobby = await this.http.get(`/lobbies/${lobbyId}`);
1108
+ this.lobbyStore?.setBaseState([lobby]);
1109
+ return lobby;
1072
1110
  }
1073
1111
  async inviteFriend(lobbyId, friendId) {
1074
1112
  return this.http.post(`/lobbies/${lobbyId}/invite`, {
@@ -1079,7 +1117,9 @@ var Lobbies = class {
1079
1117
  return this.http.post(`/lobbies/${lobbyId}/accept-invite`, {});
1080
1118
  }
1081
1119
  async joinLobby(lobbyId) {
1082
- return this.http.post(`/lobbies/${lobbyId}/join`, {});
1120
+ const lobby = await this.http.post(`/lobbies/${lobbyId}/join`, {});
1121
+ this.lobbyStore?.setBaseState([lobby]);
1122
+ return lobby;
1083
1123
  }
1084
1124
  async removePlayer(lobbyId, userId) {
1085
1125
  return this.http.delete(
@@ -1096,12 +1136,17 @@ var Lobbies = class {
1096
1136
  return this.http.post(`/lobbies/${lobbyId}/join-queue`, {});
1097
1137
  }
1098
1138
  async cancelQueue(lobbyId) {
1099
- return this.http.delete(`/lobbies/${lobbyId}/queue`);
1139
+ const lobby = await this.http.delete(`/lobbies/${lobbyId}/queue`);
1140
+ this.lobbyStore?.setBaseState([lobby]);
1141
+ return lobby;
1100
1142
  }
1101
1143
  async updateBetAmount(lobbyId, betAmount) {
1102
- return this.http.patch(`/lobbies/${lobbyId}/bet-amount`, {
1103
- betAmount
1104
- });
1144
+ const lobby = await this.http.patch(
1145
+ `/lobbies/${lobbyId}/bet-amount`,
1146
+ { betAmount }
1147
+ );
1148
+ this.lobbyStore?.setBaseState([lobby]);
1149
+ return lobby;
1105
1150
  }
1106
1151
  async playSound(lobbyId, sound) {
1107
1152
  return this.http.post(`/lobbies/${lobbyId}/sound`, {
@@ -1136,13 +1181,18 @@ var Lobbies = class {
1136
1181
  };
1137
1182
 
1138
1183
  // src/games.ts
1139
- import { Transaction as Transaction2 } from "@solana/web3.js";
1140
1184
  var Games = class {
1141
1185
  constructor(http, wallet, logger2) {
1142
1186
  this.http = http;
1143
1187
  this.wallet = wallet;
1144
1188
  this.logger = logger2;
1145
1189
  }
1190
+ setGameStore(store) {
1191
+ this.gameStore = store;
1192
+ }
1193
+ setGameActionsStore(store) {
1194
+ this.gameActionsStore = store;
1195
+ }
1146
1196
  async getAvailableGames() {
1147
1197
  return this.http.get("/games/available");
1148
1198
  }
@@ -1155,7 +1205,11 @@ var Games = class {
1155
1205
  return this.http.get("/games/metrics");
1156
1206
  }
1157
1207
  async getGame(gameId) {
1158
- return this.http.get(`/games/${gameId}`);
1208
+ const existing = this.gameStore?.store.getState().gamesById[gameId];
1209
+ if (existing?.status === "completed") return existing;
1210
+ const game = await this.http.get(`/games/${gameId}`);
1211
+ this.gameStore?.setBaseState([game]);
1212
+ return game;
1159
1213
  }
1160
1214
  /**
1161
1215
  * Get list of currently active (live) games. Public endpoint for spectating.
@@ -1182,7 +1236,13 @@ var Games = class {
1182
1236
  * Get current game state with timer information
1183
1237
  */
1184
1238
  async getGameState(gameId) {
1185
- return this.http.get(`/games/${gameId}/state`);
1239
+ const existing = this.gameActionsStore?.store.getState().statesByGameId[gameId];
1240
+ if (existing?.status === "completed") return existing;
1241
+ const state = await this.http.get(
1242
+ `/games/${gameId}/state`
1243
+ );
1244
+ this.gameActionsStore?.setBaseState(gameId, state);
1245
+ return state;
1186
1246
  }
1187
1247
  /**
1188
1248
  * Request a rematch for a completed game
@@ -1218,8 +1278,18 @@ var Games = class {
1218
1278
  { amountMinor, signedTransaction }
1219
1279
  );
1220
1280
  }
1281
+ /**
1282
+ * Confirm a donation that was already sent by the client (signAndSendTransaction path).
1283
+ */
1284
+ async confirmGameDonationSignature(gameId, amountMinor, signature) {
1285
+ return this.http.post(
1286
+ `/games/${gameId}/donate/confirm-signature`,
1287
+ { amountMinor, signature }
1288
+ );
1289
+ }
1221
1290
  /**
1222
1291
  * One-call donation flow: prepare -> sign -> submit.
1292
+ * Automatically uses the right signing path (embedded vs injected wallet).
1223
1293
  */
1224
1294
  async sendDonation(gameId, amountMinor) {
1225
1295
  if (!this.wallet.hasSigner()) {
@@ -1228,16 +1298,10 @@ var Games = class {
1228
1298
  );
1229
1299
  }
1230
1300
  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
- );
1301
+ const result = await this.wallet.signAndDispatch(prepared.transaction, {
1302
+ onSigned: (signedTxBase64) => this.donateToGame(gameId, amountMinor, signedTxBase64),
1303
+ onSignedAndSent: (sig) => this.confirmGameDonationSignature(gameId, amountMinor, sig)
1304
+ });
1241
1305
  return {
1242
1306
  ...result,
1243
1307
  escrowAddress: prepared.escrowAddress,
@@ -1336,6 +1400,9 @@ var Chat = class {
1336
1400
  this.logger = logger2;
1337
1401
  this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...retryOptions };
1338
1402
  }
1403
+ setDmThreadsStore(store) {
1404
+ this.dmThreadsStore = store;
1405
+ }
1339
1406
  encodeContextId(id) {
1340
1407
  return encodeURIComponent(id);
1341
1408
  }
@@ -1417,7 +1484,9 @@ var Chat = class {
1417
1484
  return response;
1418
1485
  }
1419
1486
  async listDmThreads() {
1420
- return this.http.get("/chat/dm/threads");
1487
+ const threads = await this.http.get("/chat/dm/threads");
1488
+ this.dmThreadsStore?.setBaseState(threads);
1489
+ return threads;
1421
1490
  }
1422
1491
  async getDmThread(dmKey) {
1423
1492
  return this.http.get(
@@ -1483,7 +1552,6 @@ var Challenges = class {
1483
1552
  };
1484
1553
 
1485
1554
  // src/tips.ts
1486
- import { Transaction as Transaction3 } from "@solana/web3.js";
1487
1555
  var Tips = class {
1488
1556
  constructor(http, wallet, chat, logger2) {
1489
1557
  this.http = http;
@@ -1510,21 +1578,32 @@ var Tips = class {
1510
1578
  );
1511
1579
  }
1512
1580
  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
- );
1581
+ const supportsPresign = !this.wallet.isSignAndSendMode();
1582
+ const prepared = await this.prepare({
1583
+ recipientUsername,
1584
+ amount,
1585
+ supportsPresign
1586
+ });
1587
+ const transfer = await this.wallet.signAndDispatch(prepared.transaction, {
1588
+ onSigned: (signedTxBase64) => this.wallet.submitTransfer(
1589
+ signedTxBase64,
1590
+ senderAddress,
1591
+ prepared.recipientAddress,
1592
+ prepared.amount,
1593
+ "USDC",
1594
+ false,
1595
+ prepared.recipientUsername
1596
+ ),
1597
+ onSignedAndSent: (sig) => this.wallet.confirmTransferSignature(
1598
+ sig,
1599
+ senderAddress,
1600
+ prepared.recipientAddress,
1601
+ prepared.amount,
1602
+ "USDC",
1603
+ false,
1604
+ prepared.recipientUsername
1605
+ )
1606
+ });
1528
1607
  const message = await this.chat.broadcastGlobalTip(
1529
1608
  prepared.recipientUserId,
1530
1609
  prepared.amount
@@ -1636,7 +1715,7 @@ var Achievements = class {
1636
1715
  };
1637
1716
 
1638
1717
  // src/wallet.ts
1639
- import { Transaction as Transaction4 } from "@solana/web3.js";
1718
+ import { Transaction as Transaction2 } from "@solana/web3.js";
1640
1719
  var TRANSFER_FEE_MINOR = 1e4;
1641
1720
  var MIN_TRANSFER_AMOUNT = 5e4;
1642
1721
  var MIN_SOL_TRANSFER_AMOUNT = 1e6;
@@ -1853,13 +1932,15 @@ var Wallet = class {
1853
1932
  );
1854
1933
  }
1855
1934
  try {
1935
+ const supportsPresign = !this.isSignAndSendMode();
1856
1936
  const response = await this.http.post(
1857
1937
  "/wallets/transfer/prepare",
1858
1938
  {
1859
1939
  senderAddress,
1860
1940
  recipient,
1861
1941
  amount: amountMinor,
1862
- token
1942
+ token,
1943
+ supportsPresign
1863
1944
  }
1864
1945
  );
1865
1946
  this.logger.debug("Transfer prepared", {
@@ -1943,6 +2024,29 @@ var Wallet = class {
1943
2024
  throw error;
1944
2025
  }
1945
2026
  }
2027
+ /**
2028
+ * Sign a prepared transaction and invoke the appropriate backend callback
2029
+ * based on the configured signer — without exposing the mode decision to callers.
2030
+ * - Embedded wallet (signAndSend): signs+sends, calls onSignedAndSent(signature)
2031
+ * - Injected wallet (signTransaction): signs locally, calls onSigned(signedTxBase64)
2032
+ */
2033
+ async signAndDispatch(unsignedTxBase64, handlers) {
2034
+ if (!this.signer) {
2035
+ throw new Error(
2036
+ "No signer configured. Call setSigner() with a WalletSigner implementation first."
2037
+ );
2038
+ }
2039
+ const unsignedTx = Transaction2.from(base64ToBytes(unsignedTxBase64));
2040
+ if (this.isSignAndSendMode()) {
2041
+ const sig = await this.signAndSendTransaction(unsignedTx);
2042
+ return handlers.onSignedAndSent(sig);
2043
+ }
2044
+ const signedTx = await this.signTransaction(unsignedTx);
2045
+ const signedBase64 = bytesToBase64(
2046
+ signedTx.serialize({ requireAllSignatures: false })
2047
+ );
2048
+ return handlers.onSigned(signedBase64);
2049
+ }
1946
2050
  /**
1947
2051
  * Full transfer flow in one call: prepare -> sign -> submit
1948
2052
  * Recipient can be username, .sol domain, or Solana address (resolved by backend).
@@ -1959,7 +2063,7 @@ var Wallet = class {
1959
2063
  amount,
1960
2064
  token
1961
2065
  );
1962
- const unsignedTx = Transaction4.from(base64ToBytes(prepared.transaction));
2066
+ const unsignedTx = Transaction2.from(base64ToBytes(prepared.transaction));
1963
2067
  let submitted;
1964
2068
  if (this.isSignAndSendMode()) {
1965
2069
  const signature = await this.signAndSendTransaction(unsignedTx);
@@ -1999,7 +2103,7 @@ var Wallet = class {
1999
2103
  };
2000
2104
 
2001
2105
  // src/escrow.ts
2002
- import { Transaction as Transaction5 } from "@solana/web3.js";
2106
+ import { Transaction as Transaction3 } from "@solana/web3.js";
2003
2107
  var Escrow = class {
2004
2108
  constructor(http, wallet, logger2) {
2005
2109
  this.http = http;
@@ -2027,12 +2131,32 @@ var Escrow = class {
2027
2131
  * initializes depositStatus, and prepares the unsigned transaction in one call.
2028
2132
  * Eliminates one HTTP round-trip vs startDeposits() + prepareDepositTransaction().
2029
2133
  */
2030
- async prepareAndStartDeposit(lobbyId) {
2134
+ async prepareAndStartDeposit(lobbyId, supportsPresign) {
2135
+ const presign = supportsPresign ?? !this.wallet.isSignAndSendMode();
2031
2136
  return this.http.post(
2032
2137
  `/escrow/lobby/${lobbyId}/deposit/prepare-and-start`,
2033
- {}
2138
+ { supportsPresign: presign }
2034
2139
  );
2035
2140
  }
2141
+ /**
2142
+ * Sign and submit a prepared (unsigned) deposit transaction using the
2143
+ * configured signer. Automatically picks the right path:
2144
+ * - Embedded wallet: signAndSendTransaction → confirmDepositSignature
2145
+ * - Injected wallet: signTransaction → submitDeposit
2146
+ * Returns the on-chain signature.
2147
+ */
2148
+ async signAndSubmitPreparedDeposit(lobbyId, unsignedTxBase64) {
2149
+ return this.wallet.signAndDispatch(unsignedTxBase64, {
2150
+ onSigned: async (signedTxBase64) => {
2151
+ const result = await this.submitDeposit(lobbyId, signedTxBase64);
2152
+ return result.signature;
2153
+ },
2154
+ onSignedAndSent: async (signature) => {
2155
+ await this.confirmDepositSignature(lobbyId, signature);
2156
+ return signature;
2157
+ }
2158
+ });
2159
+ }
2036
2160
  /**
2037
2161
  * Submit a signed deposit transaction
2038
2162
  * The transaction will be submitted to the Solana network and confirmed
@@ -2075,8 +2199,12 @@ var Escrow = class {
2075
2199
  "No signer configured. Use sdk.wallet.setSigner(...) first."
2076
2200
  );
2077
2201
  }
2078
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
2079
- const unsignedTx = Transaction5.from(base64ToBytes(transaction));
2202
+ const supportsPresign = !this.wallet.isSignAndSendMode();
2203
+ const { transaction } = await this.prepareAndStartDeposit(
2204
+ lobbyId,
2205
+ supportsPresign
2206
+ );
2207
+ const unsignedTx = Transaction3.from(base64ToBytes(transaction));
2080
2208
  let signature;
2081
2209
  if (this.wallet.isSignAndSendMode()) {
2082
2210
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
@@ -2110,8 +2238,13 @@ var Escrow = class {
2110
2238
  };
2111
2239
  }
2112
2240
  /**
2113
- * Zero-polling deposit variant using server-side awaitConfirmation.
2114
- * 2 HTTP calls total, 0 client-side polling. Ideal for agents.
2241
+ * Deposit for a lobby and wait until the calling user's own deposit is confirmed.
2242
+ * Ideal for agents: returns as soon as your deposit is on-chain without waiting
2243
+ * for the other player. The server auto-joins the queue when all players deposit.
2244
+ *
2245
+ * Confirmation is handled asynchronously by the transaction queue processor.
2246
+ * This method polls the deposit status endpoint until the signature appears as
2247
+ * confirmed (up to 60 seconds).
2115
2248
  *
2116
2249
  * Automatically uses signAndSendTransaction or signTransaction based on signer capability.
2117
2250
  */
@@ -2121,26 +2254,45 @@ var Escrow = class {
2121
2254
  "No signer configured. Use sdk.wallet.setSigner(...) first."
2122
2255
  );
2123
2256
  }
2124
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
2125
- const unsignedTx = Transaction5.from(base64ToBytes(transaction));
2257
+ const supportsPresign2 = !this.wallet.isSignAndSendMode();
2258
+ const { transaction } = await this.prepareAndStartDeposit(
2259
+ lobbyId,
2260
+ supportsPresign2
2261
+ );
2262
+ const unsignedTx = Transaction3.from(base64ToBytes(transaction));
2126
2263
  let signature;
2127
2264
  if (this.wallet.isSignAndSendMode()) {
2128
2265
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
2129
- await this.http.post(
2130
- `/escrow/lobby/${lobbyId}/deposit/confirm-signature?awaitConfirmation=true`,
2131
- { signature }
2132
- );
2266
+ await this.confirmDepositSignature(lobbyId, signature);
2133
2267
  } else {
2134
2268
  const signedTx = await this.wallet.signTransaction(unsignedTx);
2135
2269
  const signedBase64 = bytesToBase64(
2136
2270
  signedTx.serialize({ requireAllSignatures: false })
2137
2271
  );
2138
- const result = await this.http.post(`/escrow/lobby/${lobbyId}/deposit/submit?awaitConfirmation=true`, {
2139
- signedTransaction: signedBase64
2140
- });
2141
- signature = result.signature;
2272
+ const submitResult = await this.submitDeposit(lobbyId, signedBase64);
2273
+ signature = submitResult.signature;
2274
+ }
2275
+ const MAX_WAIT_MS = 6e4;
2276
+ const POLL_INTERVAL_MS = 1e3;
2277
+ const startTime = Date.now();
2278
+ while (Date.now() - startTime < MAX_WAIT_MS) {
2279
+ const status = await this.getDepositStatus(lobbyId);
2280
+ const myDeposit = status.deposits.find(
2281
+ (d) => d.transactionHash === signature
2282
+ );
2283
+ if (myDeposit?.status === "confirmed") {
2284
+ return {
2285
+ signature,
2286
+ status: "confirmed",
2287
+ canProceedToQueue: status.canProceedToQueue
2288
+ };
2289
+ }
2290
+ if (status.allConfirmed && status.canProceedToQueue) {
2291
+ return { signature, status: "confirmed", canProceedToQueue: true };
2292
+ }
2293
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
2142
2294
  }
2143
- return { signature, status: "confirmed", canProceedToQueue: true };
2295
+ return { signature, status: "pending", canProceedToQueue: false };
2144
2296
  }
2145
2297
  async claimLobbyDepositRefund(lobbyId, depositSignature) {
2146
2298
  return this.http.post(
@@ -2496,9 +2648,10 @@ var Support = class {
2496
2648
 
2497
2649
  // src/markets.ts
2498
2650
  var Markets = class {
2499
- constructor(http, logger2) {
2651
+ constructor(http, logger2, wallet) {
2500
2652
  this.http = http;
2501
2653
  this.logger = logger2;
2654
+ this.wallet = wallet;
2502
2655
  }
2503
2656
  /**
2504
2657
  * Get the prediction market state for a game.
@@ -2540,6 +2693,35 @@ var Markets = class {
2540
2693
  { signedTransaction, outcomeId, amountMinor }
2541
2694
  );
2542
2695
  }
2696
+ /**
2697
+ * Confirm a buy order that was already broadcast by the client (signAndSendTransaction path).
2698
+ */
2699
+ async confirmBuyOrderSignature(gameId, depositSignature, outcomeId, amountMinor) {
2700
+ return this.http.post(
2701
+ `/games/${gameId}/market/orders/buy/confirm-signature`,
2702
+ { depositSignature, outcomeId, amountMinor }
2703
+ );
2704
+ }
2705
+ /**
2706
+ * One-call buy order: prepare → sign → submit, automatically choosing
2707
+ * the right signing path (embedded vs injected wallet).
2708
+ */
2709
+ async buy(gameId, outcomeId, amountMinor) {
2710
+ if (!this.wallet?.hasSigner()) {
2711
+ throw new Error(
2712
+ "No signer configured. Use sdk.wallet.setSigner(...) first."
2713
+ );
2714
+ }
2715
+ const { transaction } = await this.prepareBuyOrder(
2716
+ gameId,
2717
+ outcomeId,
2718
+ amountMinor
2719
+ );
2720
+ return this.wallet.signAndDispatch(transaction, {
2721
+ onSigned: (signedTxBase64) => this.submitBuyOrder(gameId, signedTxBase64, outcomeId, amountMinor),
2722
+ onSignedAndSent: (sig) => this.confirmBuyOrderSignature(gameId, sig, outcomeId, amountMinor)
2723
+ });
2724
+ }
2543
2725
  /**
2544
2726
  * Sell shares back to the AMM pool.
2545
2727
  * @param gameId - The game ID
@@ -2589,6 +2771,22 @@ var Markets = class {
2589
2771
  }
2590
2772
  };
2591
2773
 
2774
+ // src/analytics.ts
2775
+ var NoopAnalyticsClient = class {
2776
+ userLoggedIn(_user, _meta) {
2777
+ }
2778
+ userLoggedOut() {
2779
+ }
2780
+ sessionRestored(_user) {
2781
+ }
2782
+ track(_event, _properties) {
2783
+ }
2784
+ setUserProperties(_properties) {
2785
+ }
2786
+ group(_groupType, _groupKey, _properties) {
2787
+ }
2788
+ };
2789
+
2592
2790
  // src/ws/standalone-transport.ts
2593
2791
  import { io } from "socket.io-client";
2594
2792
 
@@ -2682,22 +2880,35 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2682
2880
  this.accessToken = null;
2683
2881
  this.reconnectAttempts = 0;
2684
2882
  this.reconnectTimer = null;
2685
- this.periodicReconnectInterval = null;
2686
- this.maxReconnectAttempts = 5;
2687
- this.reconnectDelay = 1e3;
2688
- this.reconnectDelayMax = 3e4;
2689
- // 5 minutes
2883
+ this.needsReconnect = false;
2884
+ this.visibilityHandler = null;
2690
2885
  this.wildcardHandlers = /* @__PURE__ */ new Set();
2691
2886
  this.eventHandlers = /* @__PURE__ */ new Map();
2692
2887
  this.registeredEvents = /* @__PURE__ */ new Set();
2693
2888
  this.wildcardRegistered = false;
2694
2889
  this.url = url;
2890
+ if (typeof document !== "undefined") {
2891
+ this.visibilityHandler = () => {
2892
+ if (document.visibilityState === "visible" && !this.connectionState.connected) {
2893
+ this.reconnectImmediately();
2894
+ }
2895
+ };
2896
+ document.addEventListener("visibilitychange", this.visibilityHandler);
2897
+ }
2695
2898
  }
2696
2899
  connect() {
2900
+ if (this.reconnectTimer !== null) {
2901
+ clearTimeout(this.reconnectTimer);
2902
+ this.reconnectTimer = null;
2903
+ }
2904
+ this.reconnectAttempts = 0;
2697
2905
  this.ensureSocket();
2698
2906
  }
2699
2907
  disconnect() {
2700
- this.clearPeriodicReconnect();
2908
+ if (this.visibilityHandler && typeof document !== "undefined") {
2909
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
2910
+ this.visibilityHandler = null;
2911
+ }
2701
2912
  if (!this.socket) return;
2702
2913
  this.socket.disconnect();
2703
2914
  this.socket = null;
@@ -2796,13 +3007,13 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2796
3007
  reconnection: false
2797
3008
  });
2798
3009
  socket.on("connect", () => {
2799
- this.clearPeriodicReconnect();
2800
3010
  this.updateConnectionState({
2801
3011
  connected: true,
2802
3012
  connecting: false,
2803
3013
  error: null
2804
3014
  });
2805
- const wasReconnect = this.reconnectAttempts > 0;
3015
+ const wasReconnect = this.needsReconnect;
3016
+ this.needsReconnect = false;
2806
3017
  this.reconnectAttempts = 0;
2807
3018
  this.onReconnect();
2808
3019
  if (wasReconnect) {
@@ -2813,6 +3024,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2813
3024
  });
2814
3025
  socket.on("disconnect", (reason) => {
2815
3026
  this.updateConnectionState({ connected: false, connecting: false });
3027
+ this.needsReconnect = true;
2816
3028
  this.clearSocketForReconnect();
2817
3029
  if (reason === "io server disconnect") {
2818
3030
  this.handleAuthFailure("io server disconnect");
@@ -2822,6 +3034,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2822
3034
  });
2823
3035
  socket.on("connect_error", (error) => {
2824
3036
  const message = error?.message || "connect_error";
3037
+ this.needsReconnect = true;
2825
3038
  this.clearSocketForReconnect();
2826
3039
  if (message.includes("unauthorized") || message.includes("401")) {
2827
3040
  this.handleAuthFailure(message);
@@ -2849,31 +3062,31 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2849
3062
  this.socket.removeAllListeners();
2850
3063
  this.socket = null;
2851
3064
  }
2852
- clearPeriodicReconnect() {
2853
- if (this.periodicReconnectInterval !== null) {
2854
- clearInterval(this.periodicReconnectInterval);
2855
- this.periodicReconnectInterval = null;
3065
+ reconnectImmediately() {
3066
+ if (this.reconnectTimer !== null) {
3067
+ clearTimeout(this.reconnectTimer);
3068
+ this.reconnectTimer = null;
2856
3069
  }
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);
3070
+ this.reconnectAttempts = 0;
3071
+ this.ensureSocket();
2865
3072
  }
2866
3073
  scheduleReconnect() {
2867
3074
  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
- );
3075
+ const attempt = this.reconnectAttempts;
2876
3076
  this.reconnectAttempts += 1;
3077
+ let delay;
3078
+ if (attempt < _StandaloneWsTransport.FAST_RETRY_LIMIT) {
3079
+ delay = _StandaloneWsTransport.FAST_RETRY_DELAY_MS;
3080
+ } else {
3081
+ const backoffAttempt = attempt - _StandaloneWsTransport.FAST_RETRY_LIMIT;
3082
+ delay = Math.min(
3083
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS * Math.pow(2, backoffAttempt),
3084
+ _StandaloneWsTransport.MAX_BACKOFF_MS
3085
+ );
3086
+ if (attempt === _StandaloneWsTransport.FAST_RETRY_LIMIT) {
3087
+ this.dispatchEvent("connection:slow-retry", { timestamp: Date.now() });
3088
+ }
3089
+ }
2877
3090
  this.reconnectTimer = setTimeout(() => {
2878
3091
  this.reconnectTimer = null;
2879
3092
  this.ensureSocket();
@@ -2934,7 +3147,9 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2934
3147
  this.wildcardRegistered = true;
2935
3148
  }
2936
3149
  };
2937
- _StandaloneWsTransport.PERIODIC_RECONNECT_MS = 5 * 60 * 1e3;
3150
+ _StandaloneWsTransport.FAST_RETRY_LIMIT = 60;
3151
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS = 1e3;
3152
+ _StandaloneWsTransport.MAX_BACKOFF_MS = 3e4;
2938
3153
  var StandaloneWsTransport = _StandaloneWsTransport;
2939
3154
 
2940
3155
  // src/stores/store-utils.ts
@@ -2961,14 +3176,16 @@ function createLobbyStore(transport) {
2961
3176
  depositStatusByLobbyId: {}
2962
3177
  });
2963
3178
  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
- }));
3179
+ store.updateState((state) => {
3180
+ const lobbiesById = { ...state.lobbiesById };
3181
+ for (const lobby of lobbies) {
3182
+ const existing = lobbiesById[lobby.id];
3183
+ if (!existing || lobby.updatedAt >= existing.updatedAt) {
3184
+ lobbiesById[lobby.id] = lobby;
3185
+ }
3186
+ }
3187
+ return { ...state, lobbiesById };
3188
+ });
2972
3189
  };
2973
3190
  const applyWsEvent = (event) => {
2974
3191
  switch (event.event) {
@@ -3100,12 +3317,23 @@ function createGameStore(transport) {
3100
3317
  gamesById: {},
3101
3318
  spectatorCounts: {}
3102
3319
  });
3320
+ const STATUS_RANK = {
3321
+ active: 0,
3322
+ completed: 1,
3323
+ abandoned: 1
3324
+ };
3103
3325
  const setBaseState = (games) => {
3104
- const gamesById = {};
3105
- for (const game of games) {
3106
- gamesById[game.gameId] = game;
3107
- }
3108
- store.updateState((state) => ({ ...state, gamesById }));
3326
+ store.updateState((state) => {
3327
+ const gamesById = { ...state.gamesById };
3328
+ for (const game of games) {
3329
+ const existing = gamesById[game.gameId];
3330
+ if (existing && STATUS_RANK[existing.status] > STATUS_RANK[game.status]) {
3331
+ continue;
3332
+ }
3333
+ gamesById[game.gameId] = game;
3334
+ }
3335
+ return { ...state, gamesById };
3336
+ });
3109
3337
  };
3110
3338
  const applyWsEvent = (event) => {
3111
3339
  switch (event.event) {
@@ -3851,7 +4079,38 @@ function createNotificationsStore() {
3851
4079
  appNotifications: []
3852
4080
  }));
3853
4081
  };
3854
- return { store, applyWsEvent, setListFromApi, clear };
4082
+ const markAsRead = (id) => {
4083
+ store.updateState((state) => ({
4084
+ ...state,
4085
+ appNotifications: state.appNotifications.map(
4086
+ (n) => n.id === id ? { ...n, read: true } : n
4087
+ )
4088
+ }));
4089
+ };
4090
+ const markAllAsRead = () => {
4091
+ store.updateState((state) => ({
4092
+ ...state,
4093
+ appNotifications: state.appNotifications.map((n) => ({
4094
+ ...n,
4095
+ read: true
4096
+ }))
4097
+ }));
4098
+ };
4099
+ const dismiss = (id) => {
4100
+ store.updateState((state) => ({
4101
+ ...state,
4102
+ appNotifications: state.appNotifications.filter((n) => n.id !== id)
4103
+ }));
4104
+ };
4105
+ return {
4106
+ store,
4107
+ applyWsEvent,
4108
+ setListFromApi,
4109
+ clear,
4110
+ markAsRead,
4111
+ markAllAsRead,
4112
+ dismiss
4113
+ };
3855
4114
  }
3856
4115
 
3857
4116
  // src/stores/friends-store.ts
@@ -4208,6 +4467,7 @@ var SDK = class {
4208
4467
  constructor(config) {
4209
4468
  const baseUrl = config.baseUrl || "http://localhost:3000";
4210
4469
  const logger2 = config.logger || logger;
4470
+ this.analytics = config.analytics ?? new NoopAnalyticsClient();
4211
4471
  this.http = config.httpClient || new HttpClient(
4212
4472
  baseUrl,
4213
4473
  config.storage,
@@ -4240,14 +4500,18 @@ var SDK = class {
4240
4500
  this.referrals = new Referrals(this.http, logger2);
4241
4501
  this.reports = new Reports(this.http, logger2);
4242
4502
  this.support = new Support(this.http, logger2);
4243
- this.markets = new Markets(this.http, logger2);
4503
+ this.markets = new Markets(this.http, logger2, this.wallet);
4244
4504
  this.wsTransport = config.wsTransport || new StandaloneWsTransport(baseUrl);
4245
4505
  this.wsTransport.setAppId(config.appId);
4246
4506
  this.lobbyStore = createLobbyStore(this.wsTransport);
4507
+ this.lobbies.setLobbyStore(this.lobbyStore);
4247
4508
  this.gameStore = createGameStore(this.wsTransport);
4509
+ this.games.setGameStore(this.gameStore);
4248
4510
  this.gameActionsStore = createGameActionsStore(this.wsTransport);
4511
+ this.games.setGameActionsStore(this.gameActionsStore);
4249
4512
  this.chatStore = createChatStore(this.wsTransport);
4250
4513
  this.dmThreadsStore = createDmThreadsStore();
4514
+ this.chat.setDmThreadsStore(this.dmThreadsStore);
4251
4515
  this.notificationsStore = createNotificationsStore();
4252
4516
  this.friendsStore = createFriendsStore();
4253
4517
  this.notifications = new Notifications(
@@ -4277,6 +4541,29 @@ var SDK = class {
4277
4541
  this.wsTransport.connect();
4278
4542
  await this.wsTransport.waitUntilConnected(timeoutMs);
4279
4543
  }
4544
+ /**
4545
+ * Handle the full deposit-to-queue lifecycle for a lobby.
4546
+ *
4547
+ * After the deposit is confirmed, the backend may not have processed the
4548
+ * `lobby.deposit.allConfirmed` event yet, so the lobby can still be in
4549
+ * 'preparing' status. This method re-fetches the lobby and explicitly
4550
+ * joins the queue if needed, then pushes the latest state to the store
4551
+ * so the UI transitions immediately.
4552
+ */
4553
+ async depositAndJoinQueue(lobbyId) {
4554
+ const result = await this.escrow.depositForLobby(lobbyId);
4555
+ if (!result.canProceedToQueue) {
4556
+ const lobby2 = await this.lobbies.getLobby(lobbyId);
4557
+ this.lobbyStore.setBaseState([lobby2]);
4558
+ return { ...result, lobby: lobby2 };
4559
+ }
4560
+ let lobby = await this.lobbies.getLobby(lobbyId);
4561
+ if (lobby.status === "preparing") {
4562
+ lobby = await this.lobbies.joinQueue(lobbyId);
4563
+ }
4564
+ this.lobbyStore.setBaseState([lobby]);
4565
+ return { ...result, lobby };
4566
+ }
4280
4567
  };
4281
4568
 
4282
4569
  // ../ws-shared-worker/dist/protocol.js
@@ -4384,6 +4671,26 @@ var SharedWorkerClient = class {
4384
4671
  }
4385
4672
  }
4386
4673
  }
4674
+ /**
4675
+ * Dispatch a synthetic local event directly to all registered handlers for
4676
+ * that event name (and wildcard handlers), without sending anything over the
4677
+ * wire. Used by SharedWorkerTransport to fire synthetic events like
4678
+ * `connection:reconnected` that originate from transport-layer logic rather
4679
+ * than from the server.
4680
+ */
4681
+ dispatchLocalEvent(eventName, payload) {
4682
+ const handlers = this.eventHandlers.get(eventName);
4683
+ if (handlers) {
4684
+ for (const handler of handlers) {
4685
+ handler(payload);
4686
+ }
4687
+ }
4688
+ if (this.wildcardHandlers.size > 0) {
4689
+ for (const handler of this.wildcardHandlers) {
4690
+ handler(eventName, payload);
4691
+ }
4692
+ }
4693
+ }
4387
4694
  postMessage(command) {
4388
4695
  this.port.postMessage(command);
4389
4696
  }
@@ -4393,6 +4700,10 @@ var SharedWorkerClient = class {
4393
4700
  var SharedWorkerTransport = class extends BaseWsTransport {
4394
4701
  constructor(workerUrl, socketUrl) {
4395
4702
  super();
4703
+ /** Tracks whether a successful connection has been established at least once,
4704
+ * so that subsequent ws_open events are treated as reconnects and the
4705
+ * `connection:reconnected` event is dispatched to subscribers. */
4706
+ this.hasConnectedBefore = false;
4396
4707
  this.client = new SharedWorkerClient(workerUrl);
4397
4708
  this.url = socketUrl;
4398
4709
  this.client.onMessage((event) => this.handleWorkerMessage(event));
@@ -4433,14 +4744,22 @@ var SharedWorkerTransport = class extends BaseWsTransport {
4433
4744
  }
4434
4745
  handleWorkerMessage(event) {
4435
4746
  switch (event.type) {
4436
- case "ws_open":
4747
+ case "ws_open": {
4748
+ const wasReconnect = this.hasConnectedBefore;
4749
+ this.hasConnectedBefore = true;
4437
4750
  this.updateConnectionState({
4438
4751
  connected: true,
4439
4752
  connecting: false,
4440
4753
  error: null
4441
4754
  });
4442
4755
  this.onReconnect();
4756
+ if (wasReconnect) {
4757
+ this.client.dispatchLocalEvent("connection:reconnected", {
4758
+ timestamp: Date.now()
4759
+ });
4760
+ }
4443
4761
  break;
4762
+ }
4444
4763
  case "ws_close":
4445
4764
  this.updateConnectionState({
4446
4765
  connected: false,
@@ -4479,6 +4798,7 @@ export {
4479
4798
  MIN_TRANSFER_AMOUNT,
4480
4799
  Markets,
4481
4800
  NodeStorage,
4801
+ NoopAnalyticsClient,
4482
4802
  Referrals,
4483
4803
  Reports,
4484
4804
  SDK,