@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.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,
@@ -748,6 +749,16 @@ var Auth = class {
748
749
  this.logger.debug("Token stored in storage", { tokenKey: TOKEN_KEY });
749
750
  return response;
750
751
  }
752
+ async loginWithExternalSignature(address, signedMessage, options) {
753
+ const response = await this.http.post("/auth/login-wallet", {
754
+ signedMessage,
755
+ address,
756
+ referralCode: options?.referralCode,
757
+ walletMeta: options?.walletMeta
758
+ });
759
+ this.storage.set(TOKEN_KEY, response.access_token);
760
+ return response;
761
+ }
751
762
  logout() {
752
763
  this.logger.debug("Auth.logout called");
753
764
  const hadToken = this.storage.get(TOKEN_KEY) !== null;
@@ -761,6 +772,10 @@ var Auth = class {
761
772
  });
762
773
  return isAuth;
763
774
  }
775
+ async getSessionStats() {
776
+ this.logger.debug("Auth.getSessionStats called");
777
+ return this.http.get("/auth/sessions/stats");
778
+ }
764
779
  async getLatestSessions(limit) {
765
780
  this.logger.debug("Auth.getLatestSessions called", { limit });
766
781
  const params = new URLSearchParams();
@@ -988,11 +1003,25 @@ var Users = class {
988
1003
  async removeFriend(userId) {
989
1004
  return this.http.delete(`/friends/${userId}`);
990
1005
  }
991
- async getIncomingFriendRequests() {
992
- return this.http.get("/friends/requests/incoming");
1006
+ async getIncomingFriendRequests(opts) {
1007
+ const params = new URLSearchParams();
1008
+ if (opts?.limit !== void 0)
1009
+ params.append("limit", opts.limit.toString());
1010
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
1011
+ const qs = params.toString();
1012
+ return this.http.get(
1013
+ qs ? `/friends/requests/incoming?${qs}` : "/friends/requests/incoming"
1014
+ );
993
1015
  }
994
- async getOutgoingFriendRequests() {
995
- return this.http.get("/friends/requests/outgoing");
1016
+ async getOutgoingFriendRequests(opts) {
1017
+ const params = new URLSearchParams();
1018
+ if (opts?.limit !== void 0)
1019
+ params.append("limit", opts.limit.toString());
1020
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
1021
+ const qs = params.toString();
1022
+ return this.http.get(
1023
+ qs ? `/friends/requests/outgoing?${qs}` : "/friends/requests/outgoing"
1024
+ );
996
1025
  }
997
1026
  async acceptFriendRequest(userId) {
998
1027
  return this.http.post(
@@ -1081,6 +1110,9 @@ var FeatureFlags = class {
1081
1110
  this.loaded = true;
1082
1111
  return this.flags;
1083
1112
  }
1113
+ async getAdminFeatureFlags() {
1114
+ return this.http.get("/feature-flags/admin");
1115
+ }
1084
1116
  isEnabledFlag(name) {
1085
1117
  const flag = this.flags.find((f) => f.name === name);
1086
1118
  return flag?.enabled ?? false;
@@ -1100,10 +1132,11 @@ var FeatureFlags = class {
1100
1132
  }
1101
1133
  return flag;
1102
1134
  }
1103
- async createFeatureFlag(name, enabled) {
1135
+ async createFeatureFlag(name, enabled, description) {
1104
1136
  const flag = await this.http.post("/feature-flags", {
1105
1137
  name,
1106
- enabled
1138
+ enabled,
1139
+ ...description !== void 0 && { description }
1107
1140
  });
1108
1141
  this.flags.push(flag);
1109
1142
  return flag;
@@ -1116,11 +1149,17 @@ var Lobbies = class {
1116
1149
  this.http = http;
1117
1150
  this.logger = logger2;
1118
1151
  }
1152
+ /** Called by SDK after the store is created so mutations can update state directly. */
1153
+ setLobbyStore(store) {
1154
+ this.lobbyStore = store;
1155
+ }
1119
1156
  async createLobby(gameType, betAmount) {
1120
1157
  return this.http.post("/lobbies", { gameType, betAmount });
1121
1158
  }
1122
1159
  async getLobby(lobbyId) {
1123
- return this.http.get(`/lobbies/${lobbyId}`);
1160
+ const lobby = await this.http.get(`/lobbies/${lobbyId}`);
1161
+ this.lobbyStore?.setBaseState([lobby]);
1162
+ return lobby;
1124
1163
  }
1125
1164
  async inviteFriend(lobbyId, friendId) {
1126
1165
  return this.http.post(`/lobbies/${lobbyId}/invite`, {
@@ -1131,7 +1170,9 @@ var Lobbies = class {
1131
1170
  return this.http.post(`/lobbies/${lobbyId}/accept-invite`, {});
1132
1171
  }
1133
1172
  async joinLobby(lobbyId) {
1134
- return this.http.post(`/lobbies/${lobbyId}/join`, {});
1173
+ const lobby = await this.http.post(`/lobbies/${lobbyId}/join`, {});
1174
+ this.lobbyStore?.setBaseState([lobby]);
1175
+ return lobby;
1135
1176
  }
1136
1177
  async removePlayer(lobbyId, userId) {
1137
1178
  return this.http.delete(
@@ -1148,12 +1189,17 @@ var Lobbies = class {
1148
1189
  return this.http.post(`/lobbies/${lobbyId}/join-queue`, {});
1149
1190
  }
1150
1191
  async cancelQueue(lobbyId) {
1151
- return this.http.delete(`/lobbies/${lobbyId}/queue`);
1192
+ const lobby = await this.http.delete(`/lobbies/${lobbyId}/queue`);
1193
+ this.lobbyStore?.setBaseState([lobby]);
1194
+ return lobby;
1152
1195
  }
1153
1196
  async updateBetAmount(lobbyId, betAmount) {
1154
- return this.http.patch(`/lobbies/${lobbyId}/bet-amount`, {
1155
- betAmount
1156
- });
1197
+ const lobby = await this.http.patch(
1198
+ `/lobbies/${lobbyId}/bet-amount`,
1199
+ { betAmount }
1200
+ );
1201
+ this.lobbyStore?.setBaseState([lobby]);
1202
+ return lobby;
1157
1203
  }
1158
1204
  async playSound(lobbyId, sound) {
1159
1205
  return this.http.post(`/lobbies/${lobbyId}/sound`, {
@@ -1188,13 +1234,18 @@ var Lobbies = class {
1188
1234
  };
1189
1235
 
1190
1236
  // src/games.ts
1191
- var import_web32 = require("@solana/web3.js");
1192
1237
  var Games = class {
1193
1238
  constructor(http, wallet, logger2) {
1194
1239
  this.http = http;
1195
1240
  this.wallet = wallet;
1196
1241
  this.logger = logger2;
1197
1242
  }
1243
+ setGameStore(store) {
1244
+ this.gameStore = store;
1245
+ }
1246
+ setGameActionsStore(store) {
1247
+ this.gameActionsStore = store;
1248
+ }
1198
1249
  async getAvailableGames() {
1199
1250
  return this.http.get("/games/available");
1200
1251
  }
@@ -1207,7 +1258,11 @@ var Games = class {
1207
1258
  return this.http.get("/games/metrics");
1208
1259
  }
1209
1260
  async getGame(gameId) {
1210
- return this.http.get(`/games/${gameId}`);
1261
+ const existing = this.gameStore?.store.getState().gamesById[gameId];
1262
+ if (existing?.status === "completed") return existing;
1263
+ const game = await this.http.get(`/games/${gameId}`);
1264
+ this.gameStore?.setBaseState([game]);
1265
+ return game;
1211
1266
  }
1212
1267
  /**
1213
1268
  * Get list of currently active (live) games. Public endpoint for spectating.
@@ -1234,7 +1289,13 @@ var Games = class {
1234
1289
  * Get current game state with timer information
1235
1290
  */
1236
1291
  async getGameState(gameId) {
1237
- return this.http.get(`/games/${gameId}/state`);
1292
+ const existing = this.gameActionsStore?.store.getState().statesByGameId[gameId];
1293
+ if (existing?.status === "completed") return existing;
1294
+ const state = await this.http.get(
1295
+ `/games/${gameId}/state`
1296
+ );
1297
+ this.gameActionsStore?.setBaseState(gameId, state);
1298
+ return state;
1238
1299
  }
1239
1300
  /**
1240
1301
  * Request a rematch for a completed game
@@ -1270,8 +1331,18 @@ var Games = class {
1270
1331
  { amountMinor, signedTransaction }
1271
1332
  );
1272
1333
  }
1334
+ /**
1335
+ * Confirm a donation that was already sent by the client (signAndSendTransaction path).
1336
+ */
1337
+ async confirmGameDonationSignature(gameId, amountMinor, signature) {
1338
+ return this.http.post(
1339
+ `/games/${gameId}/donate/confirm-signature`,
1340
+ { amountMinor, signature }
1341
+ );
1342
+ }
1273
1343
  /**
1274
1344
  * One-call donation flow: prepare -> sign -> submit.
1345
+ * Automatically uses the right signing path (embedded vs injected wallet).
1275
1346
  */
1276
1347
  async sendDonation(gameId, amountMinor) {
1277
1348
  if (!this.wallet.hasSigner()) {
@@ -1280,16 +1351,10 @@ var Games = class {
1280
1351
  );
1281
1352
  }
1282
1353
  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
- );
1354
+ const result = await this.wallet.signAndDispatch(prepared.transaction, {
1355
+ onSigned: (signedTxBase64) => this.donateToGame(gameId, amountMinor, signedTxBase64),
1356
+ onSignedAndSent: (sig) => this.confirmGameDonationSignature(gameId, amountMinor, sig)
1357
+ });
1293
1358
  return {
1294
1359
  ...result,
1295
1360
  escrowAddress: prepared.escrowAddress,
@@ -1388,6 +1453,9 @@ var Chat = class {
1388
1453
  this.logger = logger2;
1389
1454
  this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...retryOptions };
1390
1455
  }
1456
+ setDmThreadsStore(store) {
1457
+ this.dmThreadsStore = store;
1458
+ }
1391
1459
  encodeContextId(id) {
1392
1460
  return encodeURIComponent(id);
1393
1461
  }
@@ -1469,7 +1537,9 @@ var Chat = class {
1469
1537
  return response;
1470
1538
  }
1471
1539
  async listDmThreads() {
1472
- return this.http.get("/chat/dm/threads");
1540
+ const threads = await this.http.get("/chat/dm/threads");
1541
+ this.dmThreadsStore?.setBaseState(threads);
1542
+ return threads;
1473
1543
  }
1474
1544
  async getDmThread(dmKey) {
1475
1545
  return this.http.get(
@@ -1535,7 +1605,6 @@ var Challenges = class {
1535
1605
  };
1536
1606
 
1537
1607
  // src/tips.ts
1538
- var import_web33 = require("@solana/web3.js");
1539
1608
  var Tips = class {
1540
1609
  constructor(http, wallet, chat, logger2) {
1541
1610
  this.http = http;
@@ -1562,21 +1631,32 @@ var Tips = class {
1562
1631
  );
1563
1632
  }
1564
1633
  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
- );
1634
+ const supportsPresign = !this.wallet.isSignAndSendMode();
1635
+ const prepared = await this.prepare({
1636
+ recipientUsername,
1637
+ amount,
1638
+ supportsPresign
1639
+ });
1640
+ const transfer = await this.wallet.signAndDispatch(prepared.transaction, {
1641
+ onSigned: (signedTxBase64) => this.wallet.submitTransfer(
1642
+ signedTxBase64,
1643
+ senderAddress,
1644
+ prepared.recipientAddress,
1645
+ prepared.amount,
1646
+ "USDC",
1647
+ false,
1648
+ prepared.recipientUsername
1649
+ ),
1650
+ onSignedAndSent: (sig) => this.wallet.confirmTransferSignature(
1651
+ sig,
1652
+ senderAddress,
1653
+ prepared.recipientAddress,
1654
+ prepared.amount,
1655
+ "USDC",
1656
+ false,
1657
+ prepared.recipientUsername
1658
+ )
1659
+ });
1580
1660
  const message = await this.chat.broadcastGlobalTip(
1581
1661
  prepared.recipientUserId,
1582
1662
  prepared.amount
@@ -1688,7 +1768,7 @@ var Achievements = class {
1688
1768
  };
1689
1769
 
1690
1770
  // src/wallet.ts
1691
- var import_web34 = require("@solana/web3.js");
1771
+ var import_web32 = require("@solana/web3.js");
1692
1772
  var TRANSFER_FEE_MINOR = 1e4;
1693
1773
  var MIN_TRANSFER_AMOUNT = 5e4;
1694
1774
  var MIN_SOL_TRANSFER_AMOUNT = 1e6;
@@ -1905,13 +1985,15 @@ var Wallet = class {
1905
1985
  );
1906
1986
  }
1907
1987
  try {
1988
+ const supportsPresign = !this.isSignAndSendMode();
1908
1989
  const response = await this.http.post(
1909
1990
  "/wallets/transfer/prepare",
1910
1991
  {
1911
1992
  senderAddress,
1912
1993
  recipient,
1913
1994
  amount: amountMinor,
1914
- token
1995
+ token,
1996
+ supportsPresign
1915
1997
  }
1916
1998
  );
1917
1999
  this.logger.debug("Transfer prepared", {
@@ -1995,6 +2077,29 @@ var Wallet = class {
1995
2077
  throw error;
1996
2078
  }
1997
2079
  }
2080
+ /**
2081
+ * Sign a prepared transaction and invoke the appropriate backend callback
2082
+ * based on the configured signer — without exposing the mode decision to callers.
2083
+ * - Embedded wallet (signAndSend): signs+sends, calls onSignedAndSent(signature)
2084
+ * - Injected wallet (signTransaction): signs locally, calls onSigned(signedTxBase64)
2085
+ */
2086
+ async signAndDispatch(unsignedTxBase64, handlers) {
2087
+ if (!this.signer) {
2088
+ throw new Error(
2089
+ "No signer configured. Call setSigner() with a WalletSigner implementation first."
2090
+ );
2091
+ }
2092
+ const unsignedTx = import_web32.Transaction.from(base64ToBytes(unsignedTxBase64));
2093
+ if (this.isSignAndSendMode()) {
2094
+ const sig = await this.signAndSendTransaction(unsignedTx);
2095
+ return handlers.onSignedAndSent(sig);
2096
+ }
2097
+ const signedTx = await this.signTransaction(unsignedTx);
2098
+ const signedBase64 = bytesToBase64(
2099
+ signedTx.serialize({ requireAllSignatures: false })
2100
+ );
2101
+ return handlers.onSigned(signedBase64);
2102
+ }
1998
2103
  /**
1999
2104
  * Full transfer flow in one call: prepare -> sign -> submit
2000
2105
  * Recipient can be username, .sol domain, or Solana address (resolved by backend).
@@ -2011,7 +2116,7 @@ var Wallet = class {
2011
2116
  amount,
2012
2117
  token
2013
2118
  );
2014
- const unsignedTx = import_web34.Transaction.from(base64ToBytes(prepared.transaction));
2119
+ const unsignedTx = import_web32.Transaction.from(base64ToBytes(prepared.transaction));
2015
2120
  let submitted;
2016
2121
  if (this.isSignAndSendMode()) {
2017
2122
  const signature = await this.signAndSendTransaction(unsignedTx);
@@ -2051,7 +2156,7 @@ var Wallet = class {
2051
2156
  };
2052
2157
 
2053
2158
  // src/escrow.ts
2054
- var import_web35 = require("@solana/web3.js");
2159
+ var import_web33 = require("@solana/web3.js");
2055
2160
  var Escrow = class {
2056
2161
  constructor(http, wallet, logger2) {
2057
2162
  this.http = http;
@@ -2079,12 +2184,32 @@ var Escrow = class {
2079
2184
  * initializes depositStatus, and prepares the unsigned transaction in one call.
2080
2185
  * Eliminates one HTTP round-trip vs startDeposits() + prepareDepositTransaction().
2081
2186
  */
2082
- async prepareAndStartDeposit(lobbyId) {
2187
+ async prepareAndStartDeposit(lobbyId, supportsPresign) {
2188
+ const presign = supportsPresign ?? !this.wallet.isSignAndSendMode();
2083
2189
  return this.http.post(
2084
2190
  `/escrow/lobby/${lobbyId}/deposit/prepare-and-start`,
2085
- {}
2191
+ { supportsPresign: presign }
2086
2192
  );
2087
2193
  }
2194
+ /**
2195
+ * Sign and submit a prepared (unsigned) deposit transaction using the
2196
+ * configured signer. Automatically picks the right path:
2197
+ * - Embedded wallet: signAndSendTransaction → confirmDepositSignature
2198
+ * - Injected wallet: signTransaction → submitDeposit
2199
+ * Returns the on-chain signature.
2200
+ */
2201
+ async signAndSubmitPreparedDeposit(lobbyId, unsignedTxBase64) {
2202
+ return this.wallet.signAndDispatch(unsignedTxBase64, {
2203
+ onSigned: async (signedTxBase64) => {
2204
+ const result = await this.submitDeposit(lobbyId, signedTxBase64);
2205
+ return result.signature;
2206
+ },
2207
+ onSignedAndSent: async (signature) => {
2208
+ await this.confirmDepositSignature(lobbyId, signature);
2209
+ return signature;
2210
+ }
2211
+ });
2212
+ }
2088
2213
  /**
2089
2214
  * Submit a signed deposit transaction
2090
2215
  * The transaction will be submitted to the Solana network and confirmed
@@ -2127,8 +2252,12 @@ var Escrow = class {
2127
2252
  "No signer configured. Use sdk.wallet.setSigner(...) first."
2128
2253
  );
2129
2254
  }
2130
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
2131
- const unsignedTx = import_web35.Transaction.from(base64ToBytes(transaction));
2255
+ const supportsPresign = !this.wallet.isSignAndSendMode();
2256
+ const { transaction } = await this.prepareAndStartDeposit(
2257
+ lobbyId,
2258
+ supportsPresign
2259
+ );
2260
+ const unsignedTx = import_web33.Transaction.from(base64ToBytes(transaction));
2132
2261
  let signature;
2133
2262
  if (this.wallet.isSignAndSendMode()) {
2134
2263
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
@@ -2162,8 +2291,13 @@ var Escrow = class {
2162
2291
  };
2163
2292
  }
2164
2293
  /**
2165
- * Zero-polling deposit variant using server-side awaitConfirmation.
2166
- * 2 HTTP calls total, 0 client-side polling. Ideal for agents.
2294
+ * Deposit for a lobby and wait until the calling user's own deposit is confirmed.
2295
+ * Ideal for agents: returns as soon as your deposit is on-chain without waiting
2296
+ * for the other player. The server auto-joins the queue when all players deposit.
2297
+ *
2298
+ * Confirmation is handled asynchronously by the transaction queue processor.
2299
+ * This method polls the deposit status endpoint until the signature appears as
2300
+ * confirmed (up to 60 seconds).
2167
2301
  *
2168
2302
  * Automatically uses signAndSendTransaction or signTransaction based on signer capability.
2169
2303
  */
@@ -2173,26 +2307,45 @@ var Escrow = class {
2173
2307
  "No signer configured. Use sdk.wallet.setSigner(...) first."
2174
2308
  );
2175
2309
  }
2176
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
2177
- const unsignedTx = import_web35.Transaction.from(base64ToBytes(transaction));
2310
+ const supportsPresign2 = !this.wallet.isSignAndSendMode();
2311
+ const { transaction } = await this.prepareAndStartDeposit(
2312
+ lobbyId,
2313
+ supportsPresign2
2314
+ );
2315
+ const unsignedTx = import_web33.Transaction.from(base64ToBytes(transaction));
2178
2316
  let signature;
2179
2317
  if (this.wallet.isSignAndSendMode()) {
2180
2318
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
2181
- await this.http.post(
2182
- `/escrow/lobby/${lobbyId}/deposit/confirm-signature?awaitConfirmation=true`,
2183
- { signature }
2184
- );
2319
+ await this.confirmDepositSignature(lobbyId, signature);
2185
2320
  } else {
2186
2321
  const signedTx = await this.wallet.signTransaction(unsignedTx);
2187
2322
  const signedBase64 = bytesToBase64(
2188
2323
  signedTx.serialize({ requireAllSignatures: false })
2189
2324
  );
2190
- const result = await this.http.post(`/escrow/lobby/${lobbyId}/deposit/submit?awaitConfirmation=true`, {
2191
- signedTransaction: signedBase64
2192
- });
2193
- signature = result.signature;
2325
+ const submitResult = await this.submitDeposit(lobbyId, signedBase64);
2326
+ signature = submitResult.signature;
2327
+ }
2328
+ const MAX_WAIT_MS = 6e4;
2329
+ const POLL_INTERVAL_MS = 1e3;
2330
+ const startTime = Date.now();
2331
+ while (Date.now() - startTime < MAX_WAIT_MS) {
2332
+ const status = await this.getDepositStatus(lobbyId);
2333
+ const myDeposit = status.deposits.find(
2334
+ (d) => d.transactionHash === signature
2335
+ );
2336
+ if (myDeposit?.status === "confirmed") {
2337
+ return {
2338
+ signature,
2339
+ status: "confirmed",
2340
+ canProceedToQueue: status.canProceedToQueue
2341
+ };
2342
+ }
2343
+ if (status.allConfirmed && status.canProceedToQueue) {
2344
+ return { signature, status: "confirmed", canProceedToQueue: true };
2345
+ }
2346
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
2194
2347
  }
2195
- return { signature, status: "confirmed", canProceedToQueue: true };
2348
+ return { signature, status: "pending", canProceedToQueue: false };
2196
2349
  }
2197
2350
  async claimLobbyDepositRefund(lobbyId, depositSignature) {
2198
2351
  return this.http.post(
@@ -2548,9 +2701,10 @@ var Support = class {
2548
2701
 
2549
2702
  // src/markets.ts
2550
2703
  var Markets = class {
2551
- constructor(http, logger2) {
2704
+ constructor(http, logger2, wallet) {
2552
2705
  this.http = http;
2553
2706
  this.logger = logger2;
2707
+ this.wallet = wallet;
2554
2708
  }
2555
2709
  /**
2556
2710
  * Get the prediction market state for a game.
@@ -2592,6 +2746,35 @@ var Markets = class {
2592
2746
  { signedTransaction, outcomeId, amountMinor }
2593
2747
  );
2594
2748
  }
2749
+ /**
2750
+ * Confirm a buy order that was already broadcast by the client (signAndSendTransaction path).
2751
+ */
2752
+ async confirmBuyOrderSignature(gameId, depositSignature, outcomeId, amountMinor) {
2753
+ return this.http.post(
2754
+ `/games/${gameId}/market/orders/buy/confirm-signature`,
2755
+ { depositSignature, outcomeId, amountMinor }
2756
+ );
2757
+ }
2758
+ /**
2759
+ * One-call buy order: prepare → sign → submit, automatically choosing
2760
+ * the right signing path (embedded vs injected wallet).
2761
+ */
2762
+ async buy(gameId, outcomeId, amountMinor) {
2763
+ if (!this.wallet?.hasSigner()) {
2764
+ throw new Error(
2765
+ "No signer configured. Use sdk.wallet.setSigner(...) first."
2766
+ );
2767
+ }
2768
+ const { transaction } = await this.prepareBuyOrder(
2769
+ gameId,
2770
+ outcomeId,
2771
+ amountMinor
2772
+ );
2773
+ return this.wallet.signAndDispatch(transaction, {
2774
+ onSigned: (signedTxBase64) => this.submitBuyOrder(gameId, signedTxBase64, outcomeId, amountMinor),
2775
+ onSignedAndSent: (sig) => this.confirmBuyOrderSignature(gameId, sig, outcomeId, amountMinor)
2776
+ });
2777
+ }
2595
2778
  /**
2596
2779
  * Sell shares back to the AMM pool.
2597
2780
  * @param gameId - The game ID
@@ -2641,6 +2824,22 @@ var Markets = class {
2641
2824
  }
2642
2825
  };
2643
2826
 
2827
+ // src/analytics.ts
2828
+ var NoopAnalyticsClient = class {
2829
+ userLoggedIn(_user, _meta) {
2830
+ }
2831
+ userLoggedOut() {
2832
+ }
2833
+ sessionRestored(_user) {
2834
+ }
2835
+ track(_event, _properties) {
2836
+ }
2837
+ setUserProperties(_properties) {
2838
+ }
2839
+ group(_groupType, _groupKey, _properties) {
2840
+ }
2841
+ };
2842
+
2644
2843
  // src/ws/standalone-transport.ts
2645
2844
  var import_socket = require("socket.io-client");
2646
2845
 
@@ -2734,22 +2933,35 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2734
2933
  this.accessToken = null;
2735
2934
  this.reconnectAttempts = 0;
2736
2935
  this.reconnectTimer = null;
2737
- this.periodicReconnectInterval = null;
2738
- this.maxReconnectAttempts = 5;
2739
- this.reconnectDelay = 1e3;
2740
- this.reconnectDelayMax = 3e4;
2741
- // 5 minutes
2936
+ this.needsReconnect = false;
2937
+ this.visibilityHandler = null;
2742
2938
  this.wildcardHandlers = /* @__PURE__ */ new Set();
2743
2939
  this.eventHandlers = /* @__PURE__ */ new Map();
2744
2940
  this.registeredEvents = /* @__PURE__ */ new Set();
2745
2941
  this.wildcardRegistered = false;
2746
2942
  this.url = url;
2943
+ if (typeof document !== "undefined") {
2944
+ this.visibilityHandler = () => {
2945
+ if (document.visibilityState === "visible" && !this.connectionState.connected) {
2946
+ this.reconnectImmediately();
2947
+ }
2948
+ };
2949
+ document.addEventListener("visibilitychange", this.visibilityHandler);
2950
+ }
2747
2951
  }
2748
2952
  connect() {
2953
+ if (this.reconnectTimer !== null) {
2954
+ clearTimeout(this.reconnectTimer);
2955
+ this.reconnectTimer = null;
2956
+ }
2957
+ this.reconnectAttempts = 0;
2749
2958
  this.ensureSocket();
2750
2959
  }
2751
2960
  disconnect() {
2752
- this.clearPeriodicReconnect();
2961
+ if (this.visibilityHandler && typeof document !== "undefined") {
2962
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
2963
+ this.visibilityHandler = null;
2964
+ }
2753
2965
  if (!this.socket) return;
2754
2966
  this.socket.disconnect();
2755
2967
  this.socket = null;
@@ -2848,13 +3060,13 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2848
3060
  reconnection: false
2849
3061
  });
2850
3062
  socket.on("connect", () => {
2851
- this.clearPeriodicReconnect();
2852
3063
  this.updateConnectionState({
2853
3064
  connected: true,
2854
3065
  connecting: false,
2855
3066
  error: null
2856
3067
  });
2857
- const wasReconnect = this.reconnectAttempts > 0;
3068
+ const wasReconnect = this.needsReconnect;
3069
+ this.needsReconnect = false;
2858
3070
  this.reconnectAttempts = 0;
2859
3071
  this.onReconnect();
2860
3072
  if (wasReconnect) {
@@ -2865,6 +3077,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2865
3077
  });
2866
3078
  socket.on("disconnect", (reason) => {
2867
3079
  this.updateConnectionState({ connected: false, connecting: false });
3080
+ this.needsReconnect = true;
2868
3081
  this.clearSocketForReconnect();
2869
3082
  if (reason === "io server disconnect") {
2870
3083
  this.handleAuthFailure("io server disconnect");
@@ -2874,6 +3087,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2874
3087
  });
2875
3088
  socket.on("connect_error", (error) => {
2876
3089
  const message = error?.message || "connect_error";
3090
+ this.needsReconnect = true;
2877
3091
  this.clearSocketForReconnect();
2878
3092
  if (message.includes("unauthorized") || message.includes("401")) {
2879
3093
  this.handleAuthFailure(message);
@@ -2901,31 +3115,31 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2901
3115
  this.socket.removeAllListeners();
2902
3116
  this.socket = null;
2903
3117
  }
2904
- clearPeriodicReconnect() {
2905
- if (this.periodicReconnectInterval !== null) {
2906
- clearInterval(this.periodicReconnectInterval);
2907
- this.periodicReconnectInterval = null;
3118
+ reconnectImmediately() {
3119
+ if (this.reconnectTimer !== null) {
3120
+ clearTimeout(this.reconnectTimer);
3121
+ this.reconnectTimer = null;
2908
3122
  }
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);
3123
+ this.reconnectAttempts = 0;
3124
+ this.ensureSocket();
2917
3125
  }
2918
3126
  scheduleReconnect() {
2919
3127
  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
- );
3128
+ const attempt = this.reconnectAttempts;
2928
3129
  this.reconnectAttempts += 1;
3130
+ let delay;
3131
+ if (attempt < _StandaloneWsTransport.FAST_RETRY_LIMIT) {
3132
+ delay = _StandaloneWsTransport.FAST_RETRY_DELAY_MS;
3133
+ } else {
3134
+ const backoffAttempt = attempt - _StandaloneWsTransport.FAST_RETRY_LIMIT;
3135
+ delay = Math.min(
3136
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS * Math.pow(2, backoffAttempt),
3137
+ _StandaloneWsTransport.MAX_BACKOFF_MS
3138
+ );
3139
+ if (attempt === _StandaloneWsTransport.FAST_RETRY_LIMIT) {
3140
+ this.dispatchEvent("connection:slow-retry", { timestamp: Date.now() });
3141
+ }
3142
+ }
2929
3143
  this.reconnectTimer = setTimeout(() => {
2930
3144
  this.reconnectTimer = null;
2931
3145
  this.ensureSocket();
@@ -2986,7 +3200,9 @@ var _StandaloneWsTransport = class _StandaloneWsTransport extends BaseWsTranspor
2986
3200
  this.wildcardRegistered = true;
2987
3201
  }
2988
3202
  };
2989
- _StandaloneWsTransport.PERIODIC_RECONNECT_MS = 5 * 60 * 1e3;
3203
+ _StandaloneWsTransport.FAST_RETRY_LIMIT = 60;
3204
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS = 1e3;
3205
+ _StandaloneWsTransport.MAX_BACKOFF_MS = 3e4;
2990
3206
  var StandaloneWsTransport = _StandaloneWsTransport;
2991
3207
 
2992
3208
  // src/stores/store-utils.ts
@@ -3013,14 +3229,16 @@ function createLobbyStore(transport) {
3013
3229
  depositStatusByLobbyId: {}
3014
3230
  });
3015
3231
  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
- }));
3232
+ store.updateState((state) => {
3233
+ const lobbiesById = { ...state.lobbiesById };
3234
+ for (const lobby of lobbies) {
3235
+ const existing = lobbiesById[lobby.id];
3236
+ if (!existing || lobby.updatedAt >= existing.updatedAt) {
3237
+ lobbiesById[lobby.id] = lobby;
3238
+ }
3239
+ }
3240
+ return { ...state, lobbiesById };
3241
+ });
3024
3242
  };
3025
3243
  const applyWsEvent = (event) => {
3026
3244
  switch (event.event) {
@@ -3152,12 +3370,23 @@ function createGameStore(transport) {
3152
3370
  gamesById: {},
3153
3371
  spectatorCounts: {}
3154
3372
  });
3373
+ const STATUS_RANK = {
3374
+ active: 0,
3375
+ completed: 1,
3376
+ abandoned: 1
3377
+ };
3155
3378
  const setBaseState = (games) => {
3156
- const gamesById = {};
3157
- for (const game of games) {
3158
- gamesById[game.gameId] = game;
3159
- }
3160
- store.updateState((state) => ({ ...state, gamesById }));
3379
+ store.updateState((state) => {
3380
+ const gamesById = { ...state.gamesById };
3381
+ for (const game of games) {
3382
+ const existing = gamesById[game.gameId];
3383
+ if (existing && STATUS_RANK[existing.status] > STATUS_RANK[game.status]) {
3384
+ continue;
3385
+ }
3386
+ gamesById[game.gameId] = game;
3387
+ }
3388
+ return { ...state, gamesById };
3389
+ });
3161
3390
  };
3162
3391
  const applyWsEvent = (event) => {
3163
3392
  switch (event.event) {
@@ -3903,7 +4132,38 @@ function createNotificationsStore() {
3903
4132
  appNotifications: []
3904
4133
  }));
3905
4134
  };
3906
- return { store, applyWsEvent, setListFromApi, clear };
4135
+ const markAsRead = (id) => {
4136
+ store.updateState((state) => ({
4137
+ ...state,
4138
+ appNotifications: state.appNotifications.map(
4139
+ (n) => n.id === id ? { ...n, read: true } : n
4140
+ )
4141
+ }));
4142
+ };
4143
+ const markAllAsRead = () => {
4144
+ store.updateState((state) => ({
4145
+ ...state,
4146
+ appNotifications: state.appNotifications.map((n) => ({
4147
+ ...n,
4148
+ read: true
4149
+ }))
4150
+ }));
4151
+ };
4152
+ const dismiss = (id) => {
4153
+ store.updateState((state) => ({
4154
+ ...state,
4155
+ appNotifications: state.appNotifications.filter((n) => n.id !== id)
4156
+ }));
4157
+ };
4158
+ return {
4159
+ store,
4160
+ applyWsEvent,
4161
+ setListFromApi,
4162
+ clear,
4163
+ markAsRead,
4164
+ markAllAsRead,
4165
+ dismiss
4166
+ };
3907
4167
  }
3908
4168
 
3909
4169
  // src/stores/friends-store.ts
@@ -4260,6 +4520,7 @@ var SDK = class {
4260
4520
  constructor(config) {
4261
4521
  const baseUrl = config.baseUrl || "http://localhost:3000";
4262
4522
  const logger2 = config.logger || logger;
4523
+ this.analytics = config.analytics ?? new NoopAnalyticsClient();
4263
4524
  this.http = config.httpClient || new HttpClient(
4264
4525
  baseUrl,
4265
4526
  config.storage,
@@ -4292,14 +4553,18 @@ var SDK = class {
4292
4553
  this.referrals = new Referrals(this.http, logger2);
4293
4554
  this.reports = new Reports(this.http, logger2);
4294
4555
  this.support = new Support(this.http, logger2);
4295
- this.markets = new Markets(this.http, logger2);
4556
+ this.markets = new Markets(this.http, logger2, this.wallet);
4296
4557
  this.wsTransport = config.wsTransport || new StandaloneWsTransport(baseUrl);
4297
4558
  this.wsTransport.setAppId(config.appId);
4298
4559
  this.lobbyStore = createLobbyStore(this.wsTransport);
4560
+ this.lobbies.setLobbyStore(this.lobbyStore);
4299
4561
  this.gameStore = createGameStore(this.wsTransport);
4562
+ this.games.setGameStore(this.gameStore);
4300
4563
  this.gameActionsStore = createGameActionsStore(this.wsTransport);
4564
+ this.games.setGameActionsStore(this.gameActionsStore);
4301
4565
  this.chatStore = createChatStore(this.wsTransport);
4302
4566
  this.dmThreadsStore = createDmThreadsStore();
4567
+ this.chat.setDmThreadsStore(this.dmThreadsStore);
4303
4568
  this.notificationsStore = createNotificationsStore();
4304
4569
  this.friendsStore = createFriendsStore();
4305
4570
  this.notifications = new Notifications(
@@ -4329,6 +4594,29 @@ var SDK = class {
4329
4594
  this.wsTransport.connect();
4330
4595
  await this.wsTransport.waitUntilConnected(timeoutMs);
4331
4596
  }
4597
+ /**
4598
+ * Handle the full deposit-to-queue lifecycle for a lobby.
4599
+ *
4600
+ * After the deposit is confirmed, the backend may not have processed the
4601
+ * `lobby.deposit.allConfirmed` event yet, so the lobby can still be in
4602
+ * 'preparing' status. This method re-fetches the lobby and explicitly
4603
+ * joins the queue if needed, then pushes the latest state to the store
4604
+ * so the UI transitions immediately.
4605
+ */
4606
+ async depositAndJoinQueue(lobbyId) {
4607
+ const result = await this.escrow.depositForLobby(lobbyId);
4608
+ if (!result.canProceedToQueue) {
4609
+ const lobby2 = await this.lobbies.getLobby(lobbyId);
4610
+ this.lobbyStore.setBaseState([lobby2]);
4611
+ return { ...result, lobby: lobby2 };
4612
+ }
4613
+ let lobby = await this.lobbies.getLobby(lobbyId);
4614
+ if (lobby.status === "preparing") {
4615
+ lobby = await this.lobbies.joinQueue(lobbyId);
4616
+ }
4617
+ this.lobbyStore.setBaseState([lobby]);
4618
+ return { ...result, lobby };
4619
+ }
4332
4620
  };
4333
4621
 
4334
4622
  // ../ws-shared-worker/dist/protocol.js
@@ -4436,6 +4724,26 @@ var SharedWorkerClient = class {
4436
4724
  }
4437
4725
  }
4438
4726
  }
4727
+ /**
4728
+ * Dispatch a synthetic local event directly to all registered handlers for
4729
+ * that event name (and wildcard handlers), without sending anything over the
4730
+ * wire. Used by SharedWorkerTransport to fire synthetic events like
4731
+ * `connection:reconnected` that originate from transport-layer logic rather
4732
+ * than from the server.
4733
+ */
4734
+ dispatchLocalEvent(eventName, payload) {
4735
+ const handlers = this.eventHandlers.get(eventName);
4736
+ if (handlers) {
4737
+ for (const handler of handlers) {
4738
+ handler(payload);
4739
+ }
4740
+ }
4741
+ if (this.wildcardHandlers.size > 0) {
4742
+ for (const handler of this.wildcardHandlers) {
4743
+ handler(eventName, payload);
4744
+ }
4745
+ }
4746
+ }
4439
4747
  postMessage(command) {
4440
4748
  this.port.postMessage(command);
4441
4749
  }
@@ -4445,6 +4753,10 @@ var SharedWorkerClient = class {
4445
4753
  var SharedWorkerTransport = class extends BaseWsTransport {
4446
4754
  constructor(workerUrl, socketUrl) {
4447
4755
  super();
4756
+ /** Tracks whether a successful connection has been established at least once,
4757
+ * so that subsequent ws_open events are treated as reconnects and the
4758
+ * `connection:reconnected` event is dispatched to subscribers. */
4759
+ this.hasConnectedBefore = false;
4448
4760
  this.client = new SharedWorkerClient(workerUrl);
4449
4761
  this.url = socketUrl;
4450
4762
  this.client.onMessage((event) => this.handleWorkerMessage(event));
@@ -4485,14 +4797,22 @@ var SharedWorkerTransport = class extends BaseWsTransport {
4485
4797
  }
4486
4798
  handleWorkerMessage(event) {
4487
4799
  switch (event.type) {
4488
- case "ws_open":
4800
+ case "ws_open": {
4801
+ const wasReconnect = this.hasConnectedBefore;
4802
+ this.hasConnectedBefore = true;
4489
4803
  this.updateConnectionState({
4490
4804
  connected: true,
4491
4805
  connecting: false,
4492
4806
  error: null
4493
4807
  });
4494
4808
  this.onReconnect();
4809
+ if (wasReconnect) {
4810
+ this.client.dispatchLocalEvent("connection:reconnected", {
4811
+ timestamp: Date.now()
4812
+ });
4813
+ }
4495
4814
  break;
4815
+ }
4496
4816
  case "ws_close":
4497
4817
  this.updateConnectionState({
4498
4818
  connected: false,
@@ -4529,6 +4849,7 @@ var import_utils = __toESM(require_dist(), 1);
4529
4849
  MIN_TRANSFER_AMOUNT,
4530
4850
  Markets,
4531
4851
  NodeStorage,
4852
+ NoopAnalyticsClient,
4532
4853
  Referrals,
4533
4854
  Reports,
4534
4855
  SDK,