@dimcool/dimclaw 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.
Files changed (2) hide show
  1. package/dist/index.js +1213 -152
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -32839,6 +32839,16 @@ var Auth = class {
32839
32839
  this.logger.debug("Token stored in storage", { tokenKey: TOKEN_KEY });
32840
32840
  return response;
32841
32841
  }
32842
+ async loginWithExternalSignature(address, signedMessage, options) {
32843
+ const response = await this.http.post("/auth/login-wallet", {
32844
+ signedMessage,
32845
+ address,
32846
+ referralCode: options?.referralCode,
32847
+ walletMeta: options?.walletMeta
32848
+ });
32849
+ this.storage.set(TOKEN_KEY, response.access_token);
32850
+ return response;
32851
+ }
32842
32852
  logout() {
32843
32853
  this.logger.debug("Auth.logout called");
32844
32854
  const hadToken = this.storage.get(TOKEN_KEY) !== null;
@@ -32852,6 +32862,10 @@ var Auth = class {
32852
32862
  });
32853
32863
  return isAuth;
32854
32864
  }
32865
+ async getSessionStats() {
32866
+ this.logger.debug("Auth.getSessionStats called");
32867
+ return this.http.get("/auth/sessions/stats");
32868
+ }
32855
32869
  async getLatestSessions(limit) {
32856
32870
  this.logger.debug("Auth.getLatestSessions called", { limit });
32857
32871
  const params = new URLSearchParams();
@@ -32870,6 +32884,9 @@ var Admin = class {
32870
32884
  this.http = http2;
32871
32885
  this.logger = logger2;
32872
32886
  }
32887
+ async getInternalBots() {
32888
+ return this.http.get("/admin/internal-bots");
32889
+ }
32873
32890
  async getUserById(id) {
32874
32891
  return this.http.get(`/admin/users/${id}`);
32875
32892
  }
@@ -33075,11 +33092,25 @@ var Users = class {
33075
33092
  async removeFriend(userId) {
33076
33093
  return this.http.delete(`/friends/${userId}`);
33077
33094
  }
33078
- async getIncomingFriendRequests() {
33079
- return this.http.get("/friends/requests/incoming");
33095
+ async getIncomingFriendRequests(opts) {
33096
+ const params = new URLSearchParams();
33097
+ if (opts?.limit !== void 0)
33098
+ params.append("limit", opts.limit.toString());
33099
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
33100
+ const qs = params.toString();
33101
+ return this.http.get(
33102
+ qs ? `/friends/requests/incoming?${qs}` : "/friends/requests/incoming"
33103
+ );
33080
33104
  }
33081
- async getOutgoingFriendRequests() {
33082
- return this.http.get("/friends/requests/outgoing");
33105
+ async getOutgoingFriendRequests(opts) {
33106
+ const params = new URLSearchParams();
33107
+ if (opts?.limit !== void 0)
33108
+ params.append("limit", opts.limit.toString());
33109
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
33110
+ const qs = params.toString();
33111
+ return this.http.get(
33112
+ qs ? `/friends/requests/outgoing?${qs}` : "/friends/requests/outgoing"
33113
+ );
33083
33114
  }
33084
33115
  async acceptFriendRequest(userId) {
33085
33116
  return this.http.post(
@@ -33166,6 +33197,9 @@ var FeatureFlags = class {
33166
33197
  this.loaded = true;
33167
33198
  return this.flags;
33168
33199
  }
33200
+ async getAdminFeatureFlags() {
33201
+ return this.http.get("/feature-flags/admin");
33202
+ }
33169
33203
  isEnabledFlag(name) {
33170
33204
  const flag = this.flags.find((f) => f.name === name);
33171
33205
  return flag?.enabled ?? false;
@@ -33185,10 +33219,11 @@ var FeatureFlags = class {
33185
33219
  }
33186
33220
  return flag;
33187
33221
  }
33188
- async createFeatureFlag(name, enabled) {
33222
+ async createFeatureFlag(name, enabled, description) {
33189
33223
  const flag = await this.http.post("/feature-flags", {
33190
33224
  name,
33191
- enabled
33225
+ enabled,
33226
+ ...description !== void 0 && { description }
33192
33227
  });
33193
33228
  this.flags.push(flag);
33194
33229
  return flag;
@@ -33199,11 +33234,17 @@ var Lobbies = class {
33199
33234
  this.http = http2;
33200
33235
  this.logger = logger2;
33201
33236
  }
33237
+ /** Called by SDK after the store is created so mutations can update state directly. */
33238
+ setLobbyStore(store) {
33239
+ this.lobbyStore = store;
33240
+ }
33202
33241
  async createLobby(gameType, betAmount) {
33203
33242
  return this.http.post("/lobbies", { gameType, betAmount });
33204
33243
  }
33205
33244
  async getLobby(lobbyId) {
33206
- return this.http.get(`/lobbies/${lobbyId}`);
33245
+ const lobby = await this.http.get(`/lobbies/${lobbyId}`);
33246
+ this.lobbyStore?.setBaseState([lobby]);
33247
+ return lobby;
33207
33248
  }
33208
33249
  async inviteFriend(lobbyId, friendId) {
33209
33250
  return this.http.post(`/lobbies/${lobbyId}/invite`, {
@@ -33214,7 +33255,9 @@ var Lobbies = class {
33214
33255
  return this.http.post(`/lobbies/${lobbyId}/accept-invite`, {});
33215
33256
  }
33216
33257
  async joinLobby(lobbyId) {
33217
- return this.http.post(`/lobbies/${lobbyId}/join`, {});
33258
+ const lobby = await this.http.post(`/lobbies/${lobbyId}/join`, {});
33259
+ this.lobbyStore?.setBaseState([lobby]);
33260
+ return lobby;
33218
33261
  }
33219
33262
  async removePlayer(lobbyId, userId) {
33220
33263
  return this.http.delete(
@@ -33231,12 +33274,17 @@ var Lobbies = class {
33231
33274
  return this.http.post(`/lobbies/${lobbyId}/join-queue`, {});
33232
33275
  }
33233
33276
  async cancelQueue(lobbyId) {
33234
- return this.http.delete(`/lobbies/${lobbyId}/queue`);
33277
+ const lobby = await this.http.delete(`/lobbies/${lobbyId}/queue`);
33278
+ this.lobbyStore?.setBaseState([lobby]);
33279
+ return lobby;
33235
33280
  }
33236
33281
  async updateBetAmount(lobbyId, betAmount) {
33237
- return this.http.patch(`/lobbies/${lobbyId}/bet-amount`, {
33238
- betAmount
33239
- });
33282
+ const lobby = await this.http.patch(
33283
+ `/lobbies/${lobbyId}/bet-amount`,
33284
+ { betAmount }
33285
+ );
33286
+ this.lobbyStore?.setBaseState([lobby]);
33287
+ return lobby;
33240
33288
  }
33241
33289
  async playSound(lobbyId, sound) {
33242
33290
  return this.http.post(`/lobbies/${lobbyId}/sound`, {
@@ -33275,6 +33323,12 @@ var Games = class {
33275
33323
  this.wallet = wallet;
33276
33324
  this.logger = logger2;
33277
33325
  }
33326
+ setGameStore(store) {
33327
+ this.gameStore = store;
33328
+ }
33329
+ setGameActionsStore(store) {
33330
+ this.gameActionsStore = store;
33331
+ }
33278
33332
  async getAvailableGames() {
33279
33333
  return this.http.get("/games/available");
33280
33334
  }
@@ -33287,7 +33341,11 @@ var Games = class {
33287
33341
  return this.http.get("/games/metrics");
33288
33342
  }
33289
33343
  async getGame(gameId) {
33290
- return this.http.get(`/games/${gameId}`);
33344
+ const existing = this.gameStore?.store.getState().gamesById[gameId];
33345
+ if (existing?.status === "completed") return existing;
33346
+ const game = await this.http.get(`/games/${gameId}`);
33347
+ this.gameStore?.setBaseState([game]);
33348
+ return game;
33291
33349
  }
33292
33350
  /**
33293
33351
  * Get list of currently active (live) games. Public endpoint for spectating.
@@ -33314,7 +33372,13 @@ var Games = class {
33314
33372
  * Get current game state with timer information
33315
33373
  */
33316
33374
  async getGameState(gameId) {
33317
- return this.http.get(`/games/${gameId}/state`);
33375
+ const existing = this.gameActionsStore?.store.getState().statesByGameId[gameId];
33376
+ if (existing?.status === "completed") return existing;
33377
+ const state = await this.http.get(
33378
+ `/games/${gameId}/state`
33379
+ );
33380
+ this.gameActionsStore?.setBaseState(gameId, state);
33381
+ return state;
33318
33382
  }
33319
33383
  /**
33320
33384
  * Request a rematch for a completed game
@@ -33350,8 +33414,18 @@ var Games = class {
33350
33414
  { amountMinor, signedTransaction }
33351
33415
  );
33352
33416
  }
33417
+ /**
33418
+ * Confirm a donation that was already sent by the client (signAndSendTransaction path).
33419
+ */
33420
+ async confirmGameDonationSignature(gameId, amountMinor, signature) {
33421
+ return this.http.post(
33422
+ `/games/${gameId}/donate/confirm-signature`,
33423
+ { amountMinor, signature }
33424
+ );
33425
+ }
33353
33426
  /**
33354
33427
  * One-call donation flow: prepare -> sign -> submit.
33428
+ * Automatically uses the right signing path (embedded vs injected wallet).
33355
33429
  */
33356
33430
  async sendDonation(gameId, amountMinor) {
33357
33431
  if (!this.wallet.hasSigner()) {
@@ -33360,16 +33434,10 @@ var Games = class {
33360
33434
  );
33361
33435
  }
33362
33436
  const prepared = await this.prepareGameDonation(gameId, amountMinor);
33363
- const unsignedTx = Transaction.from(base64ToBytes(prepared.transaction));
33364
- const signedTx = await this.wallet.signTransaction(unsignedTx);
33365
- const signedTransaction = bytesToBase64(
33366
- signedTx.serialize({ requireAllSignatures: false })
33367
- );
33368
- const result = await this.donateToGame(
33369
- gameId,
33370
- amountMinor,
33371
- signedTransaction
33372
- );
33437
+ const result = await this.wallet.signAndDispatch(prepared.transaction, {
33438
+ onSigned: (signedTxBase64) => this.donateToGame(gameId, amountMinor, signedTxBase64),
33439
+ onSignedAndSent: (sig) => this.confirmGameDonationSignature(gameId, amountMinor, sig)
33440
+ });
33373
33441
  return {
33374
33442
  ...result,
33375
33443
  escrowAddress: prepared.escrowAddress,
@@ -33462,6 +33530,9 @@ var Chat = class {
33462
33530
  this.logger = logger2;
33463
33531
  this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...retryOptions };
33464
33532
  }
33533
+ setDmThreadsStore(store) {
33534
+ this.dmThreadsStore = store;
33535
+ }
33465
33536
  encodeContextId(id) {
33466
33537
  return encodeURIComponent(id);
33467
33538
  }
@@ -33543,7 +33614,9 @@ var Chat = class {
33543
33614
  return response;
33544
33615
  }
33545
33616
  async listDmThreads() {
33546
- return this.http.get("/chat/dm/threads");
33617
+ const threads = await this.http.get("/chat/dm/threads");
33618
+ this.dmThreadsStore?.setBaseState(threads);
33619
+ return threads;
33547
33620
  }
33548
33621
  async getDmThread(dmKey) {
33549
33622
  return this.http.get(
@@ -33631,21 +33704,32 @@ var Tips = class {
33631
33704
  );
33632
33705
  }
33633
33706
  const { publicKey: senderAddress } = await this.wallet.loadWallet();
33634
- const prepared = await this.prepare({ recipientUsername, amount });
33635
- const unsignedTx = Transaction.from(base64ToBytes(prepared.transaction));
33636
- const signedTx = await this.wallet.signTransaction(unsignedTx);
33637
- const signedTxBase64 = bytesToBase64(
33638
- signedTx.serialize({ requireAllSignatures: false })
33639
- );
33640
- const transfer = await this.wallet.submitTransfer(
33641
- signedTxBase64,
33642
- senderAddress,
33643
- prepared.recipientAddress,
33644
- prepared.amount,
33645
- "USDC",
33646
- false,
33647
- prepared.recipientUsername
33648
- );
33707
+ const supportsPresign = !this.wallet.isSignAndSendMode();
33708
+ const prepared = await this.prepare({
33709
+ recipientUsername,
33710
+ amount,
33711
+ supportsPresign
33712
+ });
33713
+ const transfer = await this.wallet.signAndDispatch(prepared.transaction, {
33714
+ onSigned: (signedTxBase64) => this.wallet.submitTransfer(
33715
+ signedTxBase64,
33716
+ senderAddress,
33717
+ prepared.recipientAddress,
33718
+ prepared.amount,
33719
+ "USDC",
33720
+ false,
33721
+ prepared.recipientUsername
33722
+ ),
33723
+ onSignedAndSent: (sig) => this.wallet.confirmTransferSignature(
33724
+ sig,
33725
+ senderAddress,
33726
+ prepared.recipientAddress,
33727
+ prepared.amount,
33728
+ "USDC",
33729
+ false,
33730
+ prepared.recipientUsername
33731
+ )
33732
+ });
33649
33733
  const message = await this.chat.broadcastGlobalTip(
33650
33734
  prepared.recipientUserId,
33651
33735
  prepared.amount
@@ -33963,13 +34047,15 @@ var Wallet = class {
33963
34047
  );
33964
34048
  }
33965
34049
  try {
34050
+ const supportsPresign = !this.isSignAndSendMode();
33966
34051
  const response = await this.http.post(
33967
34052
  "/wallets/transfer/prepare",
33968
34053
  {
33969
34054
  senderAddress,
33970
34055
  recipient,
33971
34056
  amount: amountMinor,
33972
- token
34057
+ token,
34058
+ supportsPresign
33973
34059
  }
33974
34060
  );
33975
34061
  this.logger.debug("Transfer prepared", {
@@ -34053,6 +34139,29 @@ var Wallet = class {
34053
34139
  throw error;
34054
34140
  }
34055
34141
  }
34142
+ /**
34143
+ * Sign a prepared transaction and invoke the appropriate backend callback
34144
+ * based on the configured signer — without exposing the mode decision to callers.
34145
+ * - Embedded wallet (signAndSend): signs+sends, calls onSignedAndSent(signature)
34146
+ * - Injected wallet (signTransaction): signs locally, calls onSigned(signedTxBase64)
34147
+ */
34148
+ async signAndDispatch(unsignedTxBase64, handlers) {
34149
+ if (!this.signer) {
34150
+ throw new Error(
34151
+ "No signer configured. Call setSigner() with a WalletSigner implementation first."
34152
+ );
34153
+ }
34154
+ const unsignedTx = Transaction.from(base64ToBytes(unsignedTxBase64));
34155
+ if (this.isSignAndSendMode()) {
34156
+ const sig = await this.signAndSendTransaction(unsignedTx);
34157
+ return handlers.onSignedAndSent(sig);
34158
+ }
34159
+ const signedTx = await this.signTransaction(unsignedTx);
34160
+ const signedBase64 = bytesToBase64(
34161
+ signedTx.serialize({ requireAllSignatures: false })
34162
+ );
34163
+ return handlers.onSigned(signedBase64);
34164
+ }
34056
34165
  /**
34057
34166
  * Full transfer flow in one call: prepare -> sign -> submit
34058
34167
  * Recipient can be username, .sol domain, or Solana address (resolved by backend).
@@ -34134,12 +34243,32 @@ var Escrow = class {
34134
34243
  * initializes depositStatus, and prepares the unsigned transaction in one call.
34135
34244
  * Eliminates one HTTP round-trip vs startDeposits() + prepareDepositTransaction().
34136
34245
  */
34137
- async prepareAndStartDeposit(lobbyId) {
34246
+ async prepareAndStartDeposit(lobbyId, supportsPresign) {
34247
+ const presign = supportsPresign ?? !this.wallet.isSignAndSendMode();
34138
34248
  return this.http.post(
34139
34249
  `/escrow/lobby/${lobbyId}/deposit/prepare-and-start`,
34140
- {}
34250
+ { supportsPresign: presign }
34141
34251
  );
34142
34252
  }
34253
+ /**
34254
+ * Sign and submit a prepared (unsigned) deposit transaction using the
34255
+ * configured signer. Automatically picks the right path:
34256
+ * - Embedded wallet: signAndSendTransaction → confirmDepositSignature
34257
+ * - Injected wallet: signTransaction → submitDeposit
34258
+ * Returns the on-chain signature.
34259
+ */
34260
+ async signAndSubmitPreparedDeposit(lobbyId, unsignedTxBase64) {
34261
+ return this.wallet.signAndDispatch(unsignedTxBase64, {
34262
+ onSigned: async (signedTxBase64) => {
34263
+ const result = await this.submitDeposit(lobbyId, signedTxBase64);
34264
+ return result.signature;
34265
+ },
34266
+ onSignedAndSent: async (signature) => {
34267
+ await this.confirmDepositSignature(lobbyId, signature);
34268
+ return signature;
34269
+ }
34270
+ });
34271
+ }
34143
34272
  /**
34144
34273
  * Submit a signed deposit transaction
34145
34274
  * The transaction will be submitted to the Solana network and confirmed
@@ -34182,7 +34311,11 @@ var Escrow = class {
34182
34311
  "No signer configured. Use sdk.wallet.setSigner(...) first."
34183
34312
  );
34184
34313
  }
34185
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
34314
+ const supportsPresign = !this.wallet.isSignAndSendMode();
34315
+ const { transaction } = await this.prepareAndStartDeposit(
34316
+ lobbyId,
34317
+ supportsPresign
34318
+ );
34186
34319
  const unsignedTx = Transaction.from(base64ToBytes(transaction));
34187
34320
  let signature;
34188
34321
  if (this.wallet.isSignAndSendMode()) {
@@ -34217,8 +34350,13 @@ var Escrow = class {
34217
34350
  };
34218
34351
  }
34219
34352
  /**
34220
- * Zero-polling deposit variant using server-side awaitConfirmation.
34221
- * 2 HTTP calls total, 0 client-side polling. Ideal for agents.
34353
+ * Deposit for a lobby and wait until the calling user's own deposit is confirmed.
34354
+ * Ideal for agents: returns as soon as your deposit is on-chain without waiting
34355
+ * for the other player. The server auto-joins the queue when all players deposit.
34356
+ *
34357
+ * Confirmation is handled asynchronously by the transaction queue processor.
34358
+ * This method polls the deposit status endpoint until the signature appears as
34359
+ * confirmed (up to 60 seconds).
34222
34360
  *
34223
34361
  * Automatically uses signAndSendTransaction or signTransaction based on signer capability.
34224
34362
  */
@@ -34228,26 +34366,45 @@ var Escrow = class {
34228
34366
  "No signer configured. Use sdk.wallet.setSigner(...) first."
34229
34367
  );
34230
34368
  }
34231
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
34369
+ const supportsPresign2 = !this.wallet.isSignAndSendMode();
34370
+ const { transaction } = await this.prepareAndStartDeposit(
34371
+ lobbyId,
34372
+ supportsPresign2
34373
+ );
34232
34374
  const unsignedTx = Transaction.from(base64ToBytes(transaction));
34233
34375
  let signature;
34234
34376
  if (this.wallet.isSignAndSendMode()) {
34235
34377
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
34236
- await this.http.post(
34237
- `/escrow/lobby/${lobbyId}/deposit/confirm-signature?awaitConfirmation=true`,
34238
- { signature }
34239
- );
34378
+ await this.confirmDepositSignature(lobbyId, signature);
34240
34379
  } else {
34241
34380
  const signedTx = await this.wallet.signTransaction(unsignedTx);
34242
34381
  const signedBase64 = bytesToBase64(
34243
34382
  signedTx.serialize({ requireAllSignatures: false })
34244
34383
  );
34245
- const result = await this.http.post(`/escrow/lobby/${lobbyId}/deposit/submit?awaitConfirmation=true`, {
34246
- signedTransaction: signedBase64
34247
- });
34248
- signature = result.signature;
34384
+ const submitResult = await this.submitDeposit(lobbyId, signedBase64);
34385
+ signature = submitResult.signature;
34386
+ }
34387
+ const MAX_WAIT_MS = 6e4;
34388
+ const POLL_INTERVAL_MS = 1e3;
34389
+ const startTime = Date.now();
34390
+ while (Date.now() - startTime < MAX_WAIT_MS) {
34391
+ const status = await this.getDepositStatus(lobbyId);
34392
+ const myDeposit = status.deposits.find(
34393
+ (d) => d.transactionHash === signature
34394
+ );
34395
+ if (myDeposit?.status === "confirmed") {
34396
+ return {
34397
+ signature,
34398
+ status: "confirmed",
34399
+ canProceedToQueue: status.canProceedToQueue
34400
+ };
34401
+ }
34402
+ if (status.allConfirmed && status.canProceedToQueue) {
34403
+ return { signature, status: "confirmed", canProceedToQueue: true };
34404
+ }
34405
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
34249
34406
  }
34250
- return { signature, status: "confirmed", canProceedToQueue: true };
34407
+ return { signature, status: "pending", canProceedToQueue: false };
34251
34408
  }
34252
34409
  async claimLobbyDepositRefund(lobbyId, depositSignature) {
34253
34410
  return this.http.post(
@@ -34589,9 +34746,10 @@ var Support = class {
34589
34746
  }
34590
34747
  };
34591
34748
  var Markets = class {
34592
- constructor(http2, logger2) {
34749
+ constructor(http2, logger2, wallet) {
34593
34750
  this.http = http2;
34594
34751
  this.logger = logger2;
34752
+ this.wallet = wallet;
34595
34753
  }
34596
34754
  /**
34597
34755
  * Get the prediction market state for a game.
@@ -34633,6 +34791,35 @@ var Markets = class {
34633
34791
  { signedTransaction, outcomeId, amountMinor }
34634
34792
  );
34635
34793
  }
34794
+ /**
34795
+ * Confirm a buy order that was already broadcast by the client (signAndSendTransaction path).
34796
+ */
34797
+ async confirmBuyOrderSignature(gameId, depositSignature, outcomeId, amountMinor) {
34798
+ return this.http.post(
34799
+ `/games/${gameId}/market/orders/buy/confirm-signature`,
34800
+ { depositSignature, outcomeId, amountMinor }
34801
+ );
34802
+ }
34803
+ /**
34804
+ * One-call buy order: prepare → sign → submit, automatically choosing
34805
+ * the right signing path (embedded vs injected wallet).
34806
+ */
34807
+ async buy(gameId, outcomeId, amountMinor) {
34808
+ if (!this.wallet?.hasSigner()) {
34809
+ throw new Error(
34810
+ "No signer configured. Use sdk.wallet.setSigner(...) first."
34811
+ );
34812
+ }
34813
+ const { transaction } = await this.prepareBuyOrder(
34814
+ gameId,
34815
+ outcomeId,
34816
+ amountMinor
34817
+ );
34818
+ return this.wallet.signAndDispatch(transaction, {
34819
+ onSigned: (signedTxBase64) => this.submitBuyOrder(gameId, signedTxBase64, outcomeId, amountMinor),
34820
+ onSignedAndSent: (sig) => this.confirmBuyOrderSignature(gameId, sig, outcomeId, amountMinor)
34821
+ });
34822
+ }
34636
34823
  /**
34637
34824
  * Sell shares back to the AMM pool.
34638
34825
  * @param gameId - The game ID
@@ -34681,6 +34868,20 @@ var Markets = class {
34681
34868
  return this.http.get(`/games/admin/markets${qs ? `?${qs}` : ""}`);
34682
34869
  }
34683
34870
  };
34871
+ var NoopAnalyticsClient = class {
34872
+ userLoggedIn(_user, _meta) {
34873
+ }
34874
+ userLoggedOut() {
34875
+ }
34876
+ sessionRestored(_user) {
34877
+ }
34878
+ track(_event, _properties) {
34879
+ }
34880
+ setUserProperties(_properties) {
34881
+ }
34882
+ group(_groupType, _groupKey, _properties) {
34883
+ }
34884
+ };
34684
34885
  var BaseWsTransport = class {
34685
34886
  constructor() {
34686
34887
  this.roomRefs = /* @__PURE__ */ new Map();
@@ -34768,21 +34969,35 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
34768
34969
  this.accessToken = null;
34769
34970
  this.reconnectAttempts = 0;
34770
34971
  this.reconnectTimer = null;
34771
- this.periodicReconnectInterval = null;
34772
- this.maxReconnectAttempts = 5;
34773
- this.reconnectDelay = 1e3;
34774
- this.reconnectDelayMax = 3e4;
34972
+ this.needsReconnect = false;
34973
+ this.visibilityHandler = null;
34775
34974
  this.wildcardHandlers = /* @__PURE__ */ new Set();
34776
34975
  this.eventHandlers = /* @__PURE__ */ new Map();
34777
34976
  this.registeredEvents = /* @__PURE__ */ new Set();
34778
34977
  this.wildcardRegistered = false;
34779
34978
  this.url = url3;
34979
+ if (typeof document !== "undefined") {
34980
+ this.visibilityHandler = () => {
34981
+ if (document.visibilityState === "visible" && !this.connectionState.connected) {
34982
+ this.reconnectImmediately();
34983
+ }
34984
+ };
34985
+ document.addEventListener("visibilitychange", this.visibilityHandler);
34986
+ }
34780
34987
  }
34781
34988
  connect() {
34989
+ if (this.reconnectTimer !== null) {
34990
+ clearTimeout(this.reconnectTimer);
34991
+ this.reconnectTimer = null;
34992
+ }
34993
+ this.reconnectAttempts = 0;
34782
34994
  this.ensureSocket();
34783
34995
  }
34784
34996
  disconnect() {
34785
- this.clearPeriodicReconnect();
34997
+ if (this.visibilityHandler && typeof document !== "undefined") {
34998
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
34999
+ this.visibilityHandler = null;
35000
+ }
34786
35001
  if (!this.socket) return;
34787
35002
  this.socket.disconnect();
34788
35003
  this.socket = null;
@@ -34881,13 +35096,13 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
34881
35096
  reconnection: false
34882
35097
  });
34883
35098
  socket.on("connect", () => {
34884
- this.clearPeriodicReconnect();
34885
35099
  this.updateConnectionState({
34886
35100
  connected: true,
34887
35101
  connecting: false,
34888
35102
  error: null
34889
35103
  });
34890
- const wasReconnect = this.reconnectAttempts > 0;
35104
+ const wasReconnect = this.needsReconnect;
35105
+ this.needsReconnect = false;
34891
35106
  this.reconnectAttempts = 0;
34892
35107
  this.onReconnect();
34893
35108
  if (wasReconnect) {
@@ -34898,6 +35113,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
34898
35113
  });
34899
35114
  socket.on("disconnect", (reason) => {
34900
35115
  this.updateConnectionState({ connected: false, connecting: false });
35116
+ this.needsReconnect = true;
34901
35117
  this.clearSocketForReconnect();
34902
35118
  if (reason === "io server disconnect") {
34903
35119
  this.handleAuthFailure("io server disconnect");
@@ -34907,6 +35123,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
34907
35123
  });
34908
35124
  socket.on("connect_error", (error) => {
34909
35125
  const message = error?.message || "connect_error";
35126
+ this.needsReconnect = true;
34910
35127
  this.clearSocketForReconnect();
34911
35128
  if (message.includes("unauthorized") || message.includes("401")) {
34912
35129
  this.handleAuthFailure(message);
@@ -34934,31 +35151,31 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
34934
35151
  this.socket.removeAllListeners();
34935
35152
  this.socket = null;
34936
35153
  }
34937
- clearPeriodicReconnect() {
34938
- if (this.periodicReconnectInterval !== null) {
34939
- clearInterval(this.periodicReconnectInterval);
34940
- this.periodicReconnectInterval = null;
35154
+ reconnectImmediately() {
35155
+ if (this.reconnectTimer !== null) {
35156
+ clearTimeout(this.reconnectTimer);
35157
+ this.reconnectTimer = null;
34941
35158
  }
34942
- }
34943
- startPeriodicReconnect() {
34944
- if (this.periodicReconnectInterval !== null) return;
34945
- this.periodicReconnectInterval = setInterval(() => {
34946
- if (this.connectionState.connected) return;
34947
- this.reconnectAttempts = 0;
34948
- this.ensureSocket();
34949
- }, _StandaloneWsTransport2.PERIODIC_RECONNECT_MS);
35159
+ this.reconnectAttempts = 0;
35160
+ this.ensureSocket();
34950
35161
  }
34951
35162
  scheduleReconnect() {
34952
35163
  if (this.reconnectTimer !== null) return;
34953
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
34954
- this.startPeriodicReconnect();
34955
- return;
34956
- }
34957
- const delay = Math.min(
34958
- this.reconnectDelay * Math.pow(2, this.reconnectAttempts),
34959
- this.reconnectDelayMax
34960
- );
35164
+ const attempt = this.reconnectAttempts;
34961
35165
  this.reconnectAttempts += 1;
35166
+ let delay;
35167
+ if (attempt < _StandaloneWsTransport2.FAST_RETRY_LIMIT) {
35168
+ delay = _StandaloneWsTransport2.FAST_RETRY_DELAY_MS;
35169
+ } else {
35170
+ const backoffAttempt = attempt - _StandaloneWsTransport2.FAST_RETRY_LIMIT;
35171
+ delay = Math.min(
35172
+ _StandaloneWsTransport2.FAST_RETRY_DELAY_MS * Math.pow(2, backoffAttempt),
35173
+ _StandaloneWsTransport2.MAX_BACKOFF_MS
35174
+ );
35175
+ if (attempt === _StandaloneWsTransport2.FAST_RETRY_LIMIT) {
35176
+ this.dispatchEvent("connection:slow-retry", { timestamp: Date.now() });
35177
+ }
35178
+ }
34962
35179
  this.reconnectTimer = setTimeout(() => {
34963
35180
  this.reconnectTimer = null;
34964
35181
  this.ensureSocket();
@@ -35019,7 +35236,9 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
35019
35236
  this.wildcardRegistered = true;
35020
35237
  }
35021
35238
  };
35022
- _StandaloneWsTransport.PERIODIC_RECONNECT_MS = 5 * 60 * 1e3;
35239
+ _StandaloneWsTransport.FAST_RETRY_LIMIT = 60;
35240
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS = 1e3;
35241
+ _StandaloneWsTransport.MAX_BACKOFF_MS = 3e4;
35023
35242
  var StandaloneWsTransport = _StandaloneWsTransport;
35024
35243
  function createSdkStore(initial) {
35025
35244
  const store = createStore()(subscribeWithSelector(() => initial));
@@ -35040,14 +35259,16 @@ function createLobbyStore(transport) {
35040
35259
  depositStatusByLobbyId: {}
35041
35260
  });
35042
35261
  const setBaseState = (lobbies) => {
35043
- const lobbiesById = {};
35044
- for (const lobby of lobbies) {
35045
- lobbiesById[lobby.id] = lobby;
35046
- }
35047
- store.updateState((state) => ({
35048
- ...state,
35049
- lobbiesById
35050
- }));
35262
+ store.updateState((state) => {
35263
+ const lobbiesById = { ...state.lobbiesById };
35264
+ for (const lobby of lobbies) {
35265
+ const existing = lobbiesById[lobby.id];
35266
+ if (!existing || lobby.updatedAt >= existing.updatedAt) {
35267
+ lobbiesById[lobby.id] = lobby;
35268
+ }
35269
+ }
35270
+ return { ...state, lobbiesById };
35271
+ });
35051
35272
  };
35052
35273
  const applyWsEvent = (event) => {
35053
35274
  switch (event.event) {
@@ -35177,12 +35398,23 @@ function createGameStore(transport) {
35177
35398
  gamesById: {},
35178
35399
  spectatorCounts: {}
35179
35400
  });
35401
+ const STATUS_RANK = {
35402
+ active: 0,
35403
+ completed: 1,
35404
+ abandoned: 1
35405
+ };
35180
35406
  const setBaseState = (games) => {
35181
- const gamesById = {};
35182
- for (const game of games) {
35183
- gamesById[game.gameId] = game;
35184
- }
35185
- store.updateState((state) => ({ ...state, gamesById }));
35407
+ store.updateState((state) => {
35408
+ const gamesById = { ...state.gamesById };
35409
+ for (const game of games) {
35410
+ const existing = gamesById[game.gameId];
35411
+ if (existing && STATUS_RANK[existing.status] > STATUS_RANK[game.status]) {
35412
+ continue;
35413
+ }
35414
+ gamesById[game.gameId] = game;
35415
+ }
35416
+ return { ...state, gamesById };
35417
+ });
35186
35418
  };
35187
35419
  const applyWsEvent = (event) => {
35188
35420
  switch (event.event) {
@@ -35278,8 +35510,21 @@ function createGameActionsStore(transport) {
35278
35510
  });
35279
35511
  };
35280
35512
  const isNonRpsState = (state) => Boolean(state && !isRpsState(state));
35513
+ const pendingEvents = /* @__PURE__ */ new Map();
35514
+ function enqueue(gameId, event) {
35515
+ const q = pendingEvents.get(gameId) ?? [];
35516
+ q.push(event);
35517
+ pendingEvents.set(gameId, q);
35518
+ }
35519
+ function drainQueue(gameId) {
35520
+ const q = pendingEvents.get(gameId);
35521
+ if (!q?.length) return;
35522
+ pendingEvents.delete(gameId);
35523
+ for (const ev of q) applyWsEvent(ev);
35524
+ }
35281
35525
  const setBaseState = (gameId, state) => {
35282
35526
  updateState(gameId, state);
35527
+ drainQueue(gameId);
35283
35528
  };
35284
35529
  const clearState = (gameId) => {
35285
35530
  store.updateState((state) => {
@@ -35289,6 +35534,7 @@ function createGameActionsStore(transport) {
35289
35534
  const { [gameId]: _, ...rest } = state.statesByGameId;
35290
35535
  return { ...state, statesByGameId: rest };
35291
35536
  });
35537
+ pendingEvents.delete(gameId);
35292
35538
  };
35293
35539
  const applyWsEvent = (event) => {
35294
35540
  switch (event.event) {
@@ -35307,7 +35553,10 @@ function createGameActionsStore(transport) {
35307
35553
  }
35308
35554
  case "game:rps:starting": {
35309
35555
  const current = store.getState().statesByGameId[event.payload.gameId];
35310
- if (!current || !isRpsState(current)) return;
35556
+ if (!current || !isRpsState(current)) {
35557
+ enqueue(event.payload.gameId, event);
35558
+ return;
35559
+ }
35311
35560
  const betAmount = typeof event.payload.betAmount === "number" ? event.payload.betAmount : void 0;
35312
35561
  const startedAt = typeof event.payload.startedAt === "string" ? event.payload.startedAt : current.roundState.startedAt;
35313
35562
  const bufferEndsAt = typeof event.payload.bufferEndsAt === "string" ? event.payload.bufferEndsAt : current.roundState.selectionEndsAt;
@@ -35328,7 +35577,10 @@ function createGameActionsStore(transport) {
35328
35577
  }
35329
35578
  case "game:rps:round:started": {
35330
35579
  const current = store.getState().statesByGameId[event.payload.gameId];
35331
- if (!current || !isRpsState(current)) return;
35580
+ if (!current || !isRpsState(current)) {
35581
+ enqueue(event.payload.gameId, event);
35582
+ return;
35583
+ }
35332
35584
  const actions = {};
35333
35585
  const baseUsers = /* @__PURE__ */ new Set();
35334
35586
  Object.keys(current.roundState.actions).forEach(
@@ -35362,7 +35614,10 @@ function createGameActionsStore(transport) {
35362
35614
  }
35363
35615
  case "game:rps:action:received": {
35364
35616
  const current = store.getState().statesByGameId[event.payload.gameId];
35365
- if (!current || !isRpsState(current)) return;
35617
+ if (!current || !isRpsState(current)) {
35618
+ enqueue(event.payload.gameId, event);
35619
+ return;
35620
+ }
35366
35621
  const updated = {
35367
35622
  ...current,
35368
35623
  roundState: {
@@ -35381,7 +35636,10 @@ function createGameActionsStore(transport) {
35381
35636
  }
35382
35637
  case "game:rps:timer:cutoff": {
35383
35638
  const current = store.getState().statesByGameId[event.payload.gameId];
35384
- if (!current || !isRpsState(current)) return;
35639
+ if (!current || !isRpsState(current)) {
35640
+ enqueue(event.payload.gameId, event);
35641
+ return;
35642
+ }
35385
35643
  const updated = {
35386
35644
  ...current,
35387
35645
  roundState: {
@@ -35395,7 +35653,10 @@ function createGameActionsStore(transport) {
35395
35653
  }
35396
35654
  case "game:rps:round:reveal": {
35397
35655
  const current = store.getState().statesByGameId[event.payload.gameId];
35398
- if (!current || !isRpsState(current)) return;
35656
+ if (!current || !isRpsState(current)) {
35657
+ enqueue(event.payload.gameId, event);
35658
+ return;
35659
+ }
35399
35660
  const actions = {};
35400
35661
  const payloadActions = event.payload.actions;
35401
35662
  Object.keys(payloadActions || {}).forEach((userId) => {
@@ -35419,7 +35680,10 @@ function createGameActionsStore(transport) {
35419
35680
  }
35420
35681
  case "game:rps:round:completed": {
35421
35682
  const current = store.getState().statesByGameId[event.payload.gameId];
35422
- if (!current || !isRpsState(current)) return;
35683
+ if (!current || !isRpsState(current)) {
35684
+ enqueue(event.payload.gameId, event);
35685
+ return;
35686
+ }
35423
35687
  const roundHistory = [
35424
35688
  ...current.roundHistory || [],
35425
35689
  {
@@ -35444,7 +35708,10 @@ function createGameActionsStore(transport) {
35444
35708
  }
35445
35709
  case "game:rps:timeout": {
35446
35710
  const current = store.getState().statesByGameId[event.payload.gameId];
35447
- if (!current || !isRpsState(current)) return;
35711
+ if (!current || !isRpsState(current)) {
35712
+ enqueue(event.payload.gameId, event);
35713
+ return;
35714
+ }
35448
35715
  const timedOutUser = event.payload.playerId;
35449
35716
  const action = event.payload.action;
35450
35717
  const updated = {
@@ -35469,7 +35736,10 @@ function createGameActionsStore(transport) {
35469
35736
  const payload = event.payload;
35470
35737
  const { gameId } = payload;
35471
35738
  const current = store.getState().statesByGameId[gameId];
35472
- if (!current) return;
35739
+ if (!current) {
35740
+ enqueue(gameId, event);
35741
+ return;
35742
+ }
35473
35743
  const updated = isRpsCompletionPayload(payload) && isRpsState(current) ? {
35474
35744
  ...current,
35475
35745
  status: "completed",
@@ -35518,11 +35788,15 @@ function createGameActionsStore(transport) {
35518
35788
  const current = store.getState().statesByGameId[gameId];
35519
35789
  const updated = current ? { ...current, ...incoming } : incoming;
35520
35790
  updateState(gameId, updated);
35791
+ drainQueue(gameId);
35521
35792
  break;
35522
35793
  }
35523
35794
  case "game:rematch:requested": {
35524
35795
  const current = store.getState().statesByGameId[event.payload.gameId];
35525
- if (!current) return;
35796
+ if (!current) {
35797
+ enqueue(event.payload.gameId, event);
35798
+ return;
35799
+ }
35526
35800
  const requestedBy = event.payload.requestedBy;
35527
35801
  const userId = event.payload.userId;
35528
35802
  const requested = new Set(
@@ -35538,7 +35812,10 @@ function createGameActionsStore(transport) {
35538
35812
  }
35539
35813
  case "game:rematch:cancelled": {
35540
35814
  const current = store.getState().statesByGameId[event.payload.gameId];
35541
- if (!current) return;
35815
+ if (!current) {
35816
+ enqueue(event.payload.gameId, event);
35817
+ return;
35818
+ }
35542
35819
  const requestedBy = event.payload.requestedBy ?? [];
35543
35820
  const updated = {
35544
35821
  ...current,
@@ -35549,7 +35826,10 @@ function createGameActionsStore(transport) {
35549
35826
  }
35550
35827
  case "game:rematch:started": {
35551
35828
  const current = store.getState().statesByGameId[event.payload.gameId];
35552
- if (!current) return;
35829
+ if (!current) {
35830
+ enqueue(event.payload.gameId, event);
35831
+ return;
35832
+ }
35553
35833
  const updated = {
35554
35834
  ...current,
35555
35835
  rematchRequestedBy: event.payload.playerIds ?? []
@@ -35560,7 +35840,10 @@ function createGameActionsStore(transport) {
35560
35840
  case "game:pot:updated": {
35561
35841
  const { gameId, totalPotMinor } = event.payload;
35562
35842
  const current = store.getState().statesByGameId[gameId];
35563
- if (!current) return;
35843
+ if (!current) {
35844
+ enqueue(gameId, event);
35845
+ return;
35846
+ }
35564
35847
  const updated = {
35565
35848
  ...current,
35566
35849
  totalPotMinor
@@ -35573,12 +35856,123 @@ function createGameActionsStore(transport) {
35573
35856
  }
35574
35857
  };
35575
35858
  const joinGame = (gameId) => transport.joinRoom(`game:${gameId}`);
35859
+ const getCountdownDigit = (gameId, nowMs) => {
35860
+ const state = store.getState().statesByGameId[gameId];
35861
+ if (!state) return null;
35862
+ if (isRpsState(state)) {
35863
+ if (state.roundState.phase !== "starting") return null;
35864
+ const remaining = new Date(state.roundState.selectionEndsAt).getTime() - nowMs;
35865
+ if (remaining <= 0) return null;
35866
+ return Math.ceil(remaining / 1e3);
35867
+ }
35868
+ const bufferEndsAt = state.bufferEndsAt;
35869
+ if (bufferEndsAt) {
35870
+ const remaining = new Date(bufferEndsAt).getTime() - nowMs;
35871
+ if (remaining <= 0) return null;
35872
+ return Math.ceil(remaining / 1e3);
35873
+ }
35874
+ return null;
35875
+ };
35876
+ const getChessClockTimes = (gameId, nowMs) => {
35877
+ const state = store.getState().statesByGameId[gameId];
35878
+ if (!state || state.gameType !== "chess") return null;
35879
+ const s = state;
35880
+ let whiteMs = s.whiteTimeMs ?? 0;
35881
+ let blackMs = s.blackTimeMs ?? 0;
35882
+ if (s.status === "active" && s.currentPlayerId) {
35883
+ const startedAt = Date.parse(s.turnStartedAt);
35884
+ if (!Number.isNaN(startedAt)) {
35885
+ const elapsed = Math.max(0, nowMs - startedAt);
35886
+ if (s.currentPlayerId === s.whitePlayerId) {
35887
+ whiteMs = Math.max(0, whiteMs - elapsed);
35888
+ } else if (s.currentPlayerId === s.blackPlayerId) {
35889
+ blackMs = Math.max(0, blackMs - elapsed);
35890
+ }
35891
+ }
35892
+ }
35893
+ return { whiteTimeMs: whiteMs, blackTimeMs: blackMs };
35894
+ };
35895
+ const getChessCapturedPieces = (gameId) => {
35896
+ const state = store.getState().statesByGameId[gameId];
35897
+ if (!state || state.gameType !== "chess") return null;
35898
+ const fen = state.fen;
35899
+ if (!fen) return { capturedByWhite: [], capturedByBlack: [] };
35900
+ const placement = fen.split(" ")[0];
35901
+ const white = {};
35902
+ const black = {};
35903
+ for (const char of placement) {
35904
+ if (char === "/" || char >= "1" && char <= "8") continue;
35905
+ const lower = char.toLowerCase();
35906
+ if (char === lower) {
35907
+ black[lower] = (black[lower] ?? 0) + 1;
35908
+ } else {
35909
+ white[lower] = (white[lower] ?? 0) + 1;
35910
+ }
35911
+ }
35912
+ const INITIAL = {
35913
+ p: 8,
35914
+ r: 2,
35915
+ n: 2,
35916
+ b: 2,
35917
+ q: 1,
35918
+ k: 1
35919
+ };
35920
+ const capturedByWhite = [];
35921
+ const capturedByBlack = [];
35922
+ for (const [type2, initial] of Object.entries(INITIAL)) {
35923
+ const missingBlack = initial - (black[type2] ?? 0);
35924
+ for (let i = 0; i < missingBlack; i++) capturedByWhite.push(`b${type2}`);
35925
+ const missingWhite = initial - (white[type2] ?? 0);
35926
+ for (let i = 0; i < missingWhite; i++) capturedByBlack.push(`w${type2}`);
35927
+ }
35928
+ return { capturedByWhite, capturedByBlack };
35929
+ };
35930
+ const getTicTacToeClockTimes = (gameId, nowMs) => {
35931
+ const state = store.getState().statesByGameId[gameId];
35932
+ if (!state || state.gameType !== "tic-tac-toe") return null;
35933
+ const s = state;
35934
+ let xMs = s.xTimeMs ?? 0;
35935
+ let oMs = s.oTimeMs ?? 0;
35936
+ if (s.status === "active" && s.currentPlayerId) {
35937
+ const currentMark = s.playerMarks[s.currentPlayerId];
35938
+ const startedAt = Date.parse(s.turnStartedAt);
35939
+ if (!Number.isNaN(startedAt)) {
35940
+ const elapsed = Math.max(0, nowMs - startedAt);
35941
+ if (currentMark === "X") xMs = Math.max(0, xMs - elapsed);
35942
+ else if (currentMark === "O") oMs = Math.max(0, oMs - elapsed);
35943
+ }
35944
+ }
35945
+ return { xTimeMs: xMs, oTimeMs: oMs };
35946
+ };
35947
+ const getConnect4ClockTimes = (gameId, nowMs) => {
35948
+ const state = store.getState().statesByGameId[gameId];
35949
+ if (!state || state.gameType !== "connect-four") return null;
35950
+ const s = state;
35951
+ let redMs = s.redTimeMs ?? 0;
35952
+ let yellowMs = s.yellowTimeMs ?? 0;
35953
+ if (s.status === "active" && s.currentPlayerId) {
35954
+ const currentColor = s.playerColors[s.currentPlayerId];
35955
+ const startedAt = Date.parse(s.turnStartedAt);
35956
+ if (!Number.isNaN(startedAt)) {
35957
+ const elapsed = Math.max(0, nowMs - startedAt);
35958
+ if (currentColor === "RED") redMs = Math.max(0, redMs - elapsed);
35959
+ else if (currentColor === "YELLOW")
35960
+ yellowMs = Math.max(0, yellowMs - elapsed);
35961
+ }
35962
+ }
35963
+ return { redTimeMs: redMs, yellowTimeMs: yellowMs };
35964
+ };
35576
35965
  return {
35577
35966
  store,
35578
35967
  setBaseState,
35579
35968
  clearState,
35580
35969
  applyWsEvent,
35581
- joinGame
35970
+ joinGame,
35971
+ getCountdownDigit,
35972
+ getChessClockTimes,
35973
+ getChessCapturedPieces,
35974
+ getTicTacToeClockTimes,
35975
+ getConnect4ClockTimes
35582
35976
  };
35583
35977
  }
35584
35978
  function isRpsState(state) {
@@ -35920,7 +36314,38 @@ function createNotificationsStore() {
35920
36314
  appNotifications: []
35921
36315
  }));
35922
36316
  };
35923
- return { store, applyWsEvent, setListFromApi, clear };
36317
+ const markAsRead = (id) => {
36318
+ store.updateState((state) => ({
36319
+ ...state,
36320
+ appNotifications: state.appNotifications.map(
36321
+ (n) => n.id === id ? { ...n, read: true } : n
36322
+ )
36323
+ }));
36324
+ };
36325
+ const markAllAsRead = () => {
36326
+ store.updateState((state) => ({
36327
+ ...state,
36328
+ appNotifications: state.appNotifications.map((n) => ({
36329
+ ...n,
36330
+ read: true
36331
+ }))
36332
+ }));
36333
+ };
36334
+ const dismiss = (id) => {
36335
+ store.updateState((state) => ({
36336
+ ...state,
36337
+ appNotifications: state.appNotifications.filter((n) => n.id !== id)
36338
+ }));
36339
+ };
36340
+ return {
36341
+ store,
36342
+ applyWsEvent,
36343
+ setListFromApi,
36344
+ clear,
36345
+ markAsRead,
36346
+ markAllAsRead,
36347
+ dismiss
36348
+ };
35924
36349
  }
35925
36350
  function createFriendsStore() {
35926
36351
  const store = createSdkStore({
@@ -36205,12 +36630,14 @@ var WsRouter = class {
36205
36630
  }
36206
36631
  const decoded = decodeWsEvent(eventName, payload);
36207
36632
  if (!decoded) return;
36208
- this.deps.lobbyStore.applyWsEvent(decoded);
36209
- this.deps.gameStore.applyWsEvent(decoded);
36210
- this.deps.gameActionsStore.applyWsEvent(decoded);
36211
- this.deps.chatStore.applyWsEvent(decoded);
36212
- this.deps.dmThreadsStore.applyWsEvent(decoded);
36213
- this.deps.notificationsStore.applyWsEvent(decoded);
36633
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
36634
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
36635
+ this.deps.lobbyStore.applyWsEvent(event);
36636
+ this.deps.gameStore.applyWsEvent(event);
36637
+ this.deps.gameActionsStore.applyWsEvent(event);
36638
+ this.deps.chatStore.applyWsEvent(event);
36639
+ this.deps.dmThreadsStore.applyWsEvent(event);
36640
+ this.deps.notificationsStore.applyWsEvent(event);
36214
36641
  });
36215
36642
  }
36216
36643
  stop() {
@@ -36222,12 +36649,14 @@ var WsRouter = class {
36222
36649
  this.transport.subscribeEvent(eventName, (payload) => {
36223
36650
  const decoded = decodeWsEvent(eventName, payload);
36224
36651
  if (!decoded) return;
36225
- this.deps.lobbyStore.applyWsEvent(decoded);
36226
- this.deps.gameStore.applyWsEvent(decoded);
36227
- this.deps.gameActionsStore.applyWsEvent(decoded);
36228
- this.deps.chatStore.applyWsEvent(decoded);
36229
- this.deps.dmThreadsStore.applyWsEvent(decoded);
36230
- this.deps.notificationsStore.applyWsEvent(decoded);
36652
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
36653
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
36654
+ this.deps.lobbyStore.applyWsEvent(event);
36655
+ this.deps.gameStore.applyWsEvent(event);
36656
+ this.deps.gameActionsStore.applyWsEvent(event);
36657
+ this.deps.chatStore.applyWsEvent(event);
36658
+ this.deps.dmThreadsStore.applyWsEvent(event);
36659
+ this.deps.notificationsStore.applyWsEvent(event);
36231
36660
  });
36232
36661
  }
36233
36662
  }
@@ -36267,6 +36696,7 @@ var SDK = class {
36267
36696
  constructor(config) {
36268
36697
  const baseUrl = config.baseUrl || "http://localhost:3000";
36269
36698
  const logger2 = config.logger || logger;
36699
+ this.analytics = config.analytics ?? new NoopAnalyticsClient();
36270
36700
  this.http = config.httpClient || new HttpClient(
36271
36701
  baseUrl,
36272
36702
  config.storage,
@@ -36299,14 +36729,18 @@ var SDK = class {
36299
36729
  this.referrals = new Referrals(this.http, logger2);
36300
36730
  this.reports = new Reports(this.http, logger2);
36301
36731
  this.support = new Support(this.http, logger2);
36302
- this.markets = new Markets(this.http, logger2);
36732
+ this.markets = new Markets(this.http, logger2, this.wallet);
36303
36733
  this.wsTransport = config.wsTransport || new StandaloneWsTransport(baseUrl);
36304
36734
  this.wsTransport.setAppId(config.appId);
36305
36735
  this.lobbyStore = createLobbyStore(this.wsTransport);
36736
+ this.lobbies.setLobbyStore(this.lobbyStore);
36306
36737
  this.gameStore = createGameStore(this.wsTransport);
36738
+ this.games.setGameStore(this.gameStore);
36307
36739
  this.gameActionsStore = createGameActionsStore(this.wsTransport);
36740
+ this.games.setGameActionsStore(this.gameActionsStore);
36308
36741
  this.chatStore = createChatStore(this.wsTransport);
36309
36742
  this.dmThreadsStore = createDmThreadsStore();
36743
+ this.chat.setDmThreadsStore(this.dmThreadsStore);
36310
36744
  this.notificationsStore = createNotificationsStore();
36311
36745
  this.friendsStore = createFriendsStore();
36312
36746
  this.notifications = new Notifications(
@@ -36336,6 +36770,29 @@ var SDK = class {
36336
36770
  this.wsTransport.connect();
36337
36771
  await this.wsTransport.waitUntilConnected(timeoutMs);
36338
36772
  }
36773
+ /**
36774
+ * Handle the full deposit-to-queue lifecycle for a lobby.
36775
+ *
36776
+ * After the deposit is confirmed, the backend may not have processed the
36777
+ * `lobby.deposit.allConfirmed` event yet, so the lobby can still be in
36778
+ * 'preparing' status. This method re-fetches the lobby and explicitly
36779
+ * joins the queue if needed, then pushes the latest state to the store
36780
+ * so the UI transitions immediately.
36781
+ */
36782
+ async depositAndJoinQueue(lobbyId) {
36783
+ const result = await this.escrow.depositForLobby(lobbyId);
36784
+ if (!result.canProceedToQueue) {
36785
+ const lobby2 = await this.lobbies.getLobby(lobbyId);
36786
+ this.lobbyStore.setBaseState([lobby2]);
36787
+ return { ...result, lobby: lobby2 };
36788
+ }
36789
+ let lobby = await this.lobbies.getLobby(lobbyId);
36790
+ if (lobby.status === "preparing") {
36791
+ lobby = await this.lobbies.joinQueue(lobbyId);
36792
+ }
36793
+ this.lobbyStore.setBaseState([lobby]);
36794
+ return { ...result, lobby };
36795
+ }
36339
36796
  };
36340
36797
  var import_utils14 = __toESM2(require_dist(), 1);
36341
36798
  var export_MICRO_UNITS = import_utils14.MICRO_UNITS;
@@ -36479,10 +36936,34 @@ var esm_default4 = esm_default3(ALPHABET2);
36479
36936
 
36480
36937
  // ../dim-agent-core/src/client.ts
36481
36938
  var import_tweetnacl = __toESM(require_nacl_fast(), 1);
36939
+ function decodeJwtSub(token) {
36940
+ try {
36941
+ const parts2 = token.split(".");
36942
+ if (parts2.length < 2) return null;
36943
+ const base64 = parts2[1].replace(/-/g, "+").replace(/_/g, "/");
36944
+ const json = Buffer.from(base64, "base64").toString("utf-8");
36945
+ const parsed = JSON.parse(json);
36946
+ return typeof parsed.sub === "string" ? parsed.sub : null;
36947
+ } catch {
36948
+ return null;
36949
+ }
36950
+ }
36951
+ function signatureToBytes(sig) {
36952
+ if (sig.includes("-") || sig.includes("_")) {
36953
+ const padded = sig.replace(/-/g, "+").replace(/_/g, "/") + "==".slice(sig.length % 4 === 0 ? 4 : sig.length % 4);
36954
+ return new Uint8Array(Buffer.from(padded, "base64"));
36955
+ }
36956
+ if (/[+/=]/.test(sig)) {
36957
+ return new Uint8Array(Buffer.from(sig, "base64"));
36958
+ }
36959
+ return esm_default4.decode(sig);
36960
+ }
36482
36961
  var DimAgentClient = class {
36483
36962
  sdk;
36484
36963
  agentConfig;
36964
+ externalSignerMode;
36485
36965
  keypair;
36966
+ _externalAddress = null;
36486
36967
  config;
36487
36968
  authenticated = false;
36488
36969
  userId = null;
@@ -36496,21 +36977,35 @@ var DimAgentClient = class {
36496
36977
  constructor(config) {
36497
36978
  this.config = config;
36498
36979
  this.agentConfig = config.agentConfig;
36499
- const secretKeyBytes = esm_default4.decode(config.walletPrivateKey);
36500
- this.keypair = Keypair.fromSecretKey(secretKeyBytes);
36980
+ this.externalSignerMode = !config.walletPrivateKey;
36981
+ if (config.walletPrivateKey) {
36982
+ const secretKeyBytes = esm_default4.decode(config.walletPrivateKey);
36983
+ this.keypair = Keypair.fromSecretKey(secretKeyBytes);
36984
+ } else {
36985
+ this.keypair = null;
36986
+ }
36987
+ const storage = new NodeStorage();
36988
+ if (config.accessToken) {
36989
+ storage.set(TOKEN_KEY, config.accessToken);
36990
+ const sub = decodeJwtSub(config.accessToken);
36991
+ if (sub) {
36992
+ this.authenticated = true;
36993
+ this.userId = sub;
36994
+ }
36995
+ }
36501
36996
  this.sdk = new SDK({
36502
36997
  appId: "dim-agents",
36503
36998
  baseUrl: config.apiUrl || "https://api.dim.cool",
36504
- storage: new NodeStorage(),
36999
+ storage,
36505
37000
  autoPay: {
36506
- enabled: true,
37001
+ enabled: !this.externalSignerMode,
36507
37002
  maxAmountMinor: 25e3,
36508
37003
  maxRetries: 1
36509
37004
  }
36510
37005
  });
36511
37006
  }
36512
37007
  get walletAddress() {
36513
- return this.keypair.publicKey.toBase58();
37008
+ return this.keypair?.publicKey.toBase58() ?? this._externalAddress ?? "";
36514
37009
  }
36515
37010
  get isAuthenticated() {
36516
37011
  return this.authenticated;
@@ -36519,17 +37014,23 @@ var DimAgentClient = class {
36519
37014
  return this.userId;
36520
37015
  }
36521
37016
  async authenticate() {
37017
+ if (!this.keypair) {
37018
+ throw new Error(
37019
+ "No keypair configured. Use dim_request_auth_message + dim_complete_login for external signer mode."
37020
+ );
37021
+ }
37022
+ const keypair = this.keypair;
36522
37023
  this.sdk.wallet.setSigner({
36523
37024
  address: this.walletAddress,
36524
37025
  signMessage: async (message) => {
36525
37026
  const signature = import_tweetnacl.default.sign.detached(
36526
37027
  new TextEncoder().encode(message),
36527
- this.keypair.secretKey
37028
+ keypair.secretKey
36528
37029
  );
36529
37030
  return Buffer.from(signature).toString("base64");
36530
37031
  },
36531
37032
  signTransaction: async (transaction) => {
36532
- transaction.partialSign(this.keypair);
37033
+ transaction.partialSign(keypair);
36533
37034
  return transaction;
36534
37035
  }
36535
37036
  });
@@ -36583,10 +37084,45 @@ var DimAgentClient = class {
36583
37084
  get pendingEventCount() {
36584
37085
  return this.eventQueue.length;
36585
37086
  }
36586
- /** Get the Keypair for transaction signing. */
37087
+ /** Get the Keypair for transaction signing (keypair mode only). */
36587
37088
  getKeypair() {
37089
+ if (!this.keypair) {
37090
+ throw new Error(
37091
+ "No keypair configured \u2014 running in external signer mode."
37092
+ );
37093
+ }
36588
37094
  return this.keypair;
36589
37095
  }
37096
+ /** Request the auth handshake message for a given address (external signer mode). */
37097
+ async requestAuthMessage(address) {
37098
+ return this.sdk.auth.generateHandshake(address);
37099
+ }
37100
+ /**
37101
+ * Complete authentication using an externally provided signature (external signer mode).
37102
+ * @param address - Solana wallet address (base58)
37103
+ * @param signature - Detached ed25519 signature in base58, base64, or base64url (from sign_solana_message)
37104
+ */
37105
+ async completeAuth(address, signature) {
37106
+ const signatureBytes = signatureToBytes(signature);
37107
+ const signedMessage = Buffer.from(signatureBytes).toString("base64");
37108
+ const response = await this.sdk.auth.loginWithExternalSignature(
37109
+ address,
37110
+ signedMessage,
37111
+ {
37112
+ referralCode: this.config.referralCode,
37113
+ walletMeta: { type: "phantom-embedded", provider: "injected" }
37114
+ }
37115
+ );
37116
+ this.sdk.wsTransport.setAccessToken(response.access_token);
37117
+ this._externalAddress = address;
37118
+ this.authenticated = true;
37119
+ this.userId = response.user.id;
37120
+ return {
37121
+ userId: response.user.id,
37122
+ username: response.user.username,
37123
+ accessToken: response.access_token
37124
+ };
37125
+ }
36590
37126
  // ── Game chat cursors ────────────────────────────────────────────────
36591
37127
  /** Get the last-seen chat timestamp for a game. */
36592
37128
  getGameChatCursor(gameId) {
@@ -36714,13 +37250,84 @@ async function login(client) {
36714
37250
  };
36715
37251
  }
36716
37252
  }
36717
- async function getProfile(client) {
37253
+ async function requestAuthMessage(client, args) {
36718
37254
  try {
36719
- if (!client.currentUserId) {
36720
- return {
36721
- error: "Not authenticated. Call dim_login first.",
36722
- isError: true
36723
- };
37255
+ const { message } = await client.requestAuthMessage(args.address);
37256
+ return {
37257
+ data: {
37258
+ message,
37259
+ address: args.address,
37260
+ nextStep: 'Sign this message with sign_solana_message (networkId: "solana:mainnet"), then call dim_complete_login with the address and the signature. Accepted formats: base58, base64, or base64url (all formats returned by wallet MCPs are accepted).'
37261
+ }
37262
+ };
37263
+ } catch (error) {
37264
+ return {
37265
+ error: `Failed to get auth message: ${error instanceof Error ? error.message : String(error)}`,
37266
+ isError: true
37267
+ };
37268
+ }
37269
+ }
37270
+ async function completeLogin(client, args) {
37271
+ try {
37272
+ const result = await client.completeAuth(args.address, args.signature);
37273
+ client.startEventListeners();
37274
+ const nextSteps = [];
37275
+ if (result.username == null || result.username === "") {
37276
+ nextSteps.push("No username set \u2014 call dim_set_username to claim one");
37277
+ }
37278
+ nextSteps.push("Check your balance with dim_get_balance");
37279
+ nextSteps.push("Explore available games with dim_list_games");
37280
+ try {
37281
+ const summary = await client.sdk.referrals.getSummary();
37282
+ if (!summary.hasReferrer) {
37283
+ nextSteps.push(
37284
+ "No referrer yet \u2014 call dim_apply_referral_code for 10% fee discount"
37285
+ );
37286
+ }
37287
+ if (result.username) {
37288
+ nextSteps.push(
37289
+ `Share your referral code "${result.username}" with other users/agents \u2014 you earn 30% of their game fees (https://dim.cool/?ref=${result.username})`
37290
+ );
37291
+ }
37292
+ } catch {
37293
+ }
37294
+ const response = {
37295
+ success: true,
37296
+ userId: result.userId,
37297
+ username: result.username ?? null,
37298
+ walletAddress: client.walletAddress,
37299
+ walletNote: "DIM is noncustodial. Signing is handled by your external wallet for this session.",
37300
+ safetyRules: SAFETY_RULES,
37301
+ nextSteps
37302
+ };
37303
+ const ac = client.agentConfig;
37304
+ const dailyLimit = ac?.dailySpendLimit ?? 20;
37305
+ response.agentConfig = {
37306
+ autoAcceptFriendRequests: ac?.autoAcceptFriendRequests ?? false,
37307
+ autoReplyDms: ac?.autoReplyDms ?? false,
37308
+ autoPlayGames: ac?.autoPlayGames ?? false,
37309
+ maxBetPerGame: ac?.maxBetPerGame ?? 1,
37310
+ dailySpendLimit: dailyLimit,
37311
+ autoJoinGlobalChat: ac?.autoJoinGlobalChat ?? false,
37312
+ autoPromoteReferrals: ac?.autoPromoteReferrals ?? false,
37313
+ dailySpentSoFar: client.dailySpentDollars,
37314
+ dailyRemaining: dailyLimit - client.dailySpentDollars
37315
+ };
37316
+ return { data: response };
37317
+ } catch (error) {
37318
+ return {
37319
+ error: `Login failed: ${error instanceof Error ? error.message : String(error)}`,
37320
+ isError: true
37321
+ };
37322
+ }
37323
+ }
37324
+ async function getProfile(client) {
37325
+ try {
37326
+ if (!client.currentUserId) {
37327
+ return {
37328
+ error: "Not authenticated. Call dim_login first.",
37329
+ isError: true
37330
+ };
36724
37331
  }
36725
37332
  const user = await client.sdk.users.getUserById(client.currentUserId);
36726
37333
  const profile = {
@@ -36842,10 +37449,19 @@ async function listFriends(client, args) {
36842
37449
  };
36843
37450
  }
36844
37451
  }
36845
- async function getIncomingFriendRequests(client) {
37452
+ async function getIncomingFriendRequests(client, args) {
36846
37453
  try {
36847
- const result = await client.sdk.users.getIncomingFriendRequests();
36848
- return { data: result };
37454
+ const result = await client.sdk.users.getIncomingFriendRequests({
37455
+ limit: args?.limit,
37456
+ cursor: args?.cursor
37457
+ });
37458
+ return {
37459
+ data: {
37460
+ items: result.items,
37461
+ nextCursor: result.nextCursor,
37462
+ hasMore: !!result.nextCursor
37463
+ }
37464
+ };
36849
37465
  } catch (error) {
36850
37466
  return {
36851
37467
  error: `Failed to get requests: ${error instanceof Error ? error.message : String(error)}`,
@@ -36966,6 +37582,42 @@ async function sendUsdc(client, args) {
36966
37582
  const spendErr = client.checkSpendLimit(args.amount);
36967
37583
  if (spendErr) return { error: spendErr, isError: true };
36968
37584
  const amountMinor = Math.round(args.amount * 1e6);
37585
+ if (client.externalSignerMode) {
37586
+ const senderAddress = client.walletAddress;
37587
+ if (!senderAddress) {
37588
+ return {
37589
+ error: "Wallet address not set. Call dim_complete_login first.",
37590
+ isError: true
37591
+ };
37592
+ }
37593
+ const prepared = await client.sdk.wallet.prepareTransfer(
37594
+ senderAddress,
37595
+ args.recipient,
37596
+ amountMinor
37597
+ );
37598
+ return {
37599
+ data: {
37600
+ needsSigning: true,
37601
+ unsignedTx: prepared.transaction,
37602
+ fee: prepared.fee,
37603
+ totalAmount: prepared.totalAmount,
37604
+ recipientAddress: prepared.recipientAddress,
37605
+ ataCreated: prepared.ataCreated ?? false,
37606
+ confirmWith: {
37607
+ tool: "dim_confirm_send_usdc",
37608
+ params: {
37609
+ recipientAddress: prepared.recipientAddress,
37610
+ amount: amountMinor,
37611
+ fee: prepared.fee,
37612
+ token: "USDC",
37613
+ ataCreated: prepared.ataCreated ?? false,
37614
+ recipientInput: args.recipient
37615
+ }
37616
+ },
37617
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_send_usdc with the signature and the confirmWith.params."
37618
+ }
37619
+ };
37620
+ }
36969
37621
  const result = await client.sdk.wallet.send(args.recipient, amountMinor);
36970
37622
  client.recordSpend(amountMinor);
36971
37623
  return {
@@ -36986,11 +37638,74 @@ async function sendUsdc(client, args) {
36986
37638
  };
36987
37639
  }
36988
37640
  }
37641
+ async function confirmSendUsdc(client, args) {
37642
+ try {
37643
+ const senderAddress = client.walletAddress;
37644
+ const result = await client.sdk.wallet.confirmTransferSignature(
37645
+ args.signature,
37646
+ senderAddress,
37647
+ args.recipientAddress,
37648
+ args.amount,
37649
+ args.token || "USDC",
37650
+ args.ataCreated,
37651
+ args.recipientInput
37652
+ );
37653
+ client.recordSpend(args.amount);
37654
+ return {
37655
+ data: {
37656
+ success: true,
37657
+ signature: result.signature,
37658
+ status: result.status,
37659
+ recipientAddress: args.recipientAddress
37660
+ }
37661
+ };
37662
+ } catch (error) {
37663
+ return {
37664
+ error: `Failed to confirm USDC transfer: ${error instanceof Error ? error.message : String(error)}`,
37665
+ isError: true
37666
+ };
37667
+ }
37668
+ }
36989
37669
  async function tipUser(client, args) {
36990
37670
  try {
36991
37671
  const spendErr = client.checkSpendLimit(args.amount);
36992
37672
  if (spendErr) return { error: spendErr, isError: true };
36993
37673
  const amountMinor = Math.round(args.amount * 1e6);
37674
+ if (client.externalSignerMode) {
37675
+ const senderAddress = client.walletAddress;
37676
+ if (!senderAddress) {
37677
+ return {
37678
+ error: "Wallet address not set. Call dim_complete_login first.",
37679
+ isError: true
37680
+ };
37681
+ }
37682
+ const prepared = await client.sdk.tips.prepare({
37683
+ recipientUsername: args.recipientUsername,
37684
+ amount: amountMinor,
37685
+ supportsPresign: false
37686
+ });
37687
+ return {
37688
+ data: {
37689
+ needsSigning: true,
37690
+ unsignedTx: prepared.transaction,
37691
+ fee: prepared.fee,
37692
+ totalAmount: prepared.totalAmount,
37693
+ recipientAddress: prepared.recipientAddress,
37694
+ confirmWith: {
37695
+ tool: "dim_confirm_tip_user",
37696
+ params: {
37697
+ recipientAddress: prepared.recipientAddress,
37698
+ recipientUserId: prepared.recipientUserId,
37699
+ recipientUsername: prepared.recipientUsername,
37700
+ amount: prepared.amount,
37701
+ fee: prepared.fee,
37702
+ ataCreated: false
37703
+ }
37704
+ },
37705
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_tip_user with the signature and the confirmWith.params."
37706
+ }
37707
+ };
37708
+ }
36994
37709
  const result = await client.sdk.tips.send(
36995
37710
  args.recipientUsername,
36996
37711
  amountMinor
@@ -37013,6 +37728,38 @@ async function tipUser(client, args) {
37013
37728
  };
37014
37729
  }
37015
37730
  }
37731
+ async function confirmTipUser(client, args) {
37732
+ try {
37733
+ const senderAddress = client.walletAddress;
37734
+ await client.sdk.wallet.confirmTransferSignature(
37735
+ args.signature,
37736
+ senderAddress,
37737
+ args.recipientAddress,
37738
+ args.amount,
37739
+ "USDC",
37740
+ args.ataCreated ?? false,
37741
+ args.recipientUsername
37742
+ );
37743
+ await client.sdk.tips.broadcast({
37744
+ recipientUserId: args.recipientUserId,
37745
+ amount: args.amount
37746
+ });
37747
+ client.recordSpend(args.amount);
37748
+ return {
37749
+ data: {
37750
+ success: true,
37751
+ signature: args.signature,
37752
+ recipient: args.recipientUsername,
37753
+ broadcastedToGlobalChat: true
37754
+ }
37755
+ };
37756
+ } catch (error) {
37757
+ return {
37758
+ error: `Failed to confirm tip: ${error instanceof Error ? error.message : String(error)}`,
37759
+ isError: true
37760
+ };
37761
+ }
37762
+ }
37016
37763
  async function getWalletActivity(client, args) {
37017
37764
  try {
37018
37765
  const activity = await client.sdk.wallet.getActivity({
@@ -37119,6 +37866,7 @@ function buildRpsContext(state, agentUserId) {
37119
37866
  roundHistory: state.roundHistory,
37120
37867
  phase: roundState?.phase,
37121
37868
  timeRemaining: roundState?.timeRemaining,
37869
+ ...state.bufferEndsAt != null && { bufferEndsAt: state.bufferEndsAt },
37122
37870
  moveFormat: 'dim_submit_action: gameType="rock-paper-scissors", action="play", payload={ action: "rock"|"paper"|"scissors" }'
37123
37871
  };
37124
37872
  }
@@ -37131,6 +37879,7 @@ function buildChessContext(state, agentUserId) {
37131
37879
  yourColor,
37132
37880
  whiteTimeMs: state.whiteTimeMs,
37133
37881
  blackTimeMs: state.blackTimeMs,
37882
+ ...state.bufferEndsAt != null && { bufferEndsAt: state.bufferEndsAt },
37134
37883
  moveFormat: 'dim_submit_action: gameType="chess", action="move", payload={ from: "e2", to: "e4" }'
37135
37884
  };
37136
37885
  }
@@ -37340,6 +38089,23 @@ async function createLobby(client, args) {
37340
38089
  }
37341
38090
  async function depositForLobby(client, args) {
37342
38091
  try {
38092
+ if (client.externalSignerMode) {
38093
+ const { transaction } = await client.sdk.escrow.prepareAndStartDeposit(
38094
+ args.lobbyId,
38095
+ false
38096
+ );
38097
+ return {
38098
+ data: {
38099
+ needsSigning: true,
38100
+ unsignedTx: transaction,
38101
+ confirmWith: {
38102
+ tool: "dim_confirm_lobby_deposit",
38103
+ params: { lobbyId: args.lobbyId }
38104
+ },
38105
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_lobby_deposit with the lobbyId and signature."
38106
+ }
38107
+ };
38108
+ }
37343
38109
  const result = await client.sdk.escrow.depositForLobbySync(args.lobbyId);
37344
38110
  return {
37345
38111
  data: {
@@ -37356,6 +38122,44 @@ async function depositForLobby(client, args) {
37356
38122
  };
37357
38123
  }
37358
38124
  }
38125
+ async function confirmLobbyDeposit(client, args) {
38126
+ try {
38127
+ await client.sdk.escrow.confirmDepositSignature(
38128
+ args.lobbyId,
38129
+ args.signature
38130
+ );
38131
+ const MAX_WAIT_MS = 6e4;
38132
+ const POLL_INTERVAL_MS = 1e3;
38133
+ const startTime = Date.now();
38134
+ while (Date.now() - startTime < MAX_WAIT_MS) {
38135
+ const status = await client.sdk.escrow.getDepositStatus(args.lobbyId);
38136
+ if (status.canProceedToQueue) {
38137
+ return {
38138
+ data: {
38139
+ signature: args.signature,
38140
+ status: "confirmed",
38141
+ canProceedToQueue: true,
38142
+ hint: "Deposit confirmed. Call dim_join_queue to enter matchmaking."
38143
+ }
38144
+ };
38145
+ }
38146
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
38147
+ }
38148
+ return {
38149
+ data: {
38150
+ signature: args.signature,
38151
+ status: "pending",
38152
+ canProceedToQueue: false,
38153
+ hint: "Deposit submitted but not yet confirmed on-chain. Try dim_confirm_lobby_deposit again in a few seconds."
38154
+ }
38155
+ };
38156
+ } catch (error) {
38157
+ return {
38158
+ error: `Failed to confirm deposit: ${error instanceof Error ? error.message : String(error)}`,
38159
+ isError: true
38160
+ };
38161
+ }
38162
+ }
37359
38163
  async function leaveLobby(client, args) {
37360
38164
  try {
37361
38165
  const result = await client.sdk.lobbies.leaveLobby(args.lobbyId);
@@ -37425,7 +38229,7 @@ async function joinQueue(client, args) {
37425
38229
  gameId,
37426
38230
  matched: !!matched,
37427
38231
  ...spectateUrl && { spectateUrl },
37428
- hint: matched ? `Game started! Call dim_game_loop with gameId "${gameId}" to play.${spectateUrl ? ` Tell the user they can spectate at ${spectateUrl}` : ""}` : "No match found within timeout. Call dim_join_queue again to resume waiting."
38232
+ hint: matched ? `Game matched! Call dim_game_loop with gameId "${gameId}" IMMEDIATELY \u2014 do NOT wait for user input. The game has a start buffer; dim_game_loop will block until your first turn begins.${spectateUrl ? ` Tell the user they can spectate at ${spectateUrl}` : ""}` : "No match found within timeout. Call dim_join_queue again to resume waiting."
37429
38233
  }
37430
38234
  };
37431
38235
  } catch (error) {
@@ -37500,6 +38304,23 @@ async function donateToPot(client, args) {
37500
38304
  const spendErr = client.checkSpendLimit(args.amount);
37501
38305
  if (spendErr) return { error: spendErr, isError: true };
37502
38306
  const amountMinor = Math.round(args.amount * 1e6);
38307
+ if (client.externalSignerMode) {
38308
+ const prepared = await client.sdk.games.prepareGameDonation(
38309
+ args.gameId,
38310
+ amountMinor
38311
+ );
38312
+ return {
38313
+ data: {
38314
+ needsSigning: true,
38315
+ unsignedTx: prepared.transaction,
38316
+ confirmWith: {
38317
+ tool: "dim_confirm_donate_to_pot",
38318
+ params: { gameId: args.gameId, amount: amountMinor }
38319
+ },
38320
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_donate_to_pot with the signature."
38321
+ }
38322
+ };
38323
+ }
37503
38324
  const result = await client.sdk.games.sendDonation(
37504
38325
  args.gameId,
37505
38326
  amountMinor
@@ -37523,6 +38344,29 @@ async function donateToPot(client, args) {
37523
38344
  };
37524
38345
  }
37525
38346
  }
38347
+ async function confirmDonateToPot(client, args) {
38348
+ try {
38349
+ const result = await client.sdk.games.confirmGameDonationSignature(
38350
+ args.gameId,
38351
+ args.amount,
38352
+ args.signature
38353
+ );
38354
+ client.recordSpend(args.amount);
38355
+ return {
38356
+ data: {
38357
+ success: true,
38358
+ gameId: args.gameId,
38359
+ signature: result.signature,
38360
+ status: result.status
38361
+ }
38362
+ };
38363
+ } catch (error) {
38364
+ return {
38365
+ error: `Failed to confirm donation: ${error instanceof Error ? error.message : String(error)}`,
38366
+ isError: true
38367
+ };
38368
+ }
38369
+ }
37526
38370
  async function getGame(client, args) {
37527
38371
  try {
37528
38372
  const game = await client.sdk.games.getGame(args.gameId);
@@ -37534,6 +38378,12 @@ async function getGame(client, args) {
37534
38378
  };
37535
38379
  }
37536
38380
  }
38381
+ function isInStartingPhase(state) {
38382
+ const roundState = state.roundState;
38383
+ if (roundState?.phase === "starting") return true;
38384
+ if (state.status === "active" && state.currentPlayerId == null) return true;
38385
+ return false;
38386
+ }
37537
38387
  async function gameLoop(client, args) {
37538
38388
  try {
37539
38389
  const { gameId } = args;
@@ -37551,7 +38401,7 @@ async function gameLoop(client, args) {
37551
38401
  if (state?.status === "completed") {
37552
38402
  return buildGameLoopReturn(client, gameId, state, "completed");
37553
38403
  }
37554
- if (state?.currentPlayerId === client.currentUserId) {
38404
+ if (state?.currentPlayerId === client.currentUserId && !isInStartingPhase(state)) {
37555
38405
  return buildGameLoopReturn(client, gameId, state, "your-turn");
37556
38406
  }
37557
38407
  await raceTimeout(
@@ -38177,10 +39027,10 @@ async function checkNotifications(client) {
38177
39027
  isError: true
38178
39028
  };
38179
39029
  }
38180
- const [notifications, dmThreads, friendRequests] = await Promise.all([
39030
+ const [notifications, dmThreads, friendRequestsPage] = await Promise.all([
38181
39031
  client.sdk.notifications.list({ page: 1, limit: 20 }),
38182
39032
  client.sdk.chat.listDmThreads(),
38183
- client.sdk.users.getIncomingFriendRequests()
39033
+ client.sdk.users.getIncomingFriendRequests({ limit: 20 })
38184
39034
  ]);
38185
39035
  const unreadDms = dmThreads.filter(
38186
39036
  (t) => (t.unreadCount ?? 0) > 0
@@ -38192,9 +39042,10 @@ async function checkNotifications(client) {
38192
39042
  unreadNotificationCount: notifications.unreadCount,
38193
39043
  notifications: notifications.notifications.filter((n) => !n.read),
38194
39044
  unreadDmThreads: unreadDms,
38195
- incomingFriendRequests: friendRequests,
39045
+ incomingFriendRequests: friendRequestsPage.items,
39046
+ hasMoreIncomingFriendRequests: !!friendRequestsPage.nextCursor,
38196
39047
  pendingWsEvents: client.pendingEventCount,
38197
- hint: "Use dim_get_pending_events to drain buffered real-time events."
39048
+ hint: "Use dim_get_pending_events to drain buffered real-time events. Use dim_get_incoming_friend_requests with cursor for more friend requests if hasMoreIncomingFriendRequests is true."
38198
39049
  }
38199
39050
  };
38200
39051
  } catch (error) {
@@ -38580,6 +39431,22 @@ async function getGameHistory(client, args) {
38580
39431
  };
38581
39432
  }
38582
39433
  }
39434
+ async function reportUser(client, args) {
39435
+ try {
39436
+ await client.sdk.reports.create(args.userId, args.reason);
39437
+ return {
39438
+ data: {
39439
+ success: true,
39440
+ hint: "Report submitted. The DIM moderation team will review it."
39441
+ }
39442
+ };
39443
+ } catch (error) {
39444
+ return {
39445
+ error: `Failed to report user: ${error instanceof Error ? error.message : String(error)}`,
39446
+ isError: true
39447
+ };
39448
+ }
39449
+ }
38583
39450
  async function getMyStats(client) {
38584
39451
  try {
38585
39452
  if (!client.currentUserId) {
@@ -38610,6 +39477,36 @@ async function getMyStats(client) {
38610
39477
 
38611
39478
  // ../dim-agent-core/src/tools/index.ts
38612
39479
  var TOOL_DEFINITIONS = [
39480
+ // ── External signer mode (auth) ──────────────────────────────────────
39481
+ {
39482
+ name: "dim_request_auth_message",
39483
+ description: "External wallet login step 1: given a Solana wallet address, returns the message to sign. Sign it with sign_solana_message on your wallet MCP (e.g. Phantom), then call dim_complete_login.",
39484
+ params: {
39485
+ address: {
39486
+ type: "string",
39487
+ description: "Your Solana wallet address (base58)",
39488
+ required: true
39489
+ }
39490
+ },
39491
+ execute: (c, a) => requestAuthMessage(c, a)
39492
+ },
39493
+ {
39494
+ name: "dim_complete_login",
39495
+ description: "External wallet login step 2: provide the wallet address and signature from sign_solana_message to complete authentication with DIM.",
39496
+ params: {
39497
+ address: {
39498
+ type: "string",
39499
+ description: "Your Solana wallet address (base58)",
39500
+ required: true
39501
+ },
39502
+ signature: {
39503
+ type: "string",
39504
+ description: "Signature from sign_solana_message \u2014 base58, base64, or base64url accepted",
39505
+ required: true
39506
+ }
39507
+ },
39508
+ execute: (c, a) => completeLogin(c, a)
39509
+ },
38613
39510
  // ── Auth ─────────────────────────────────────────────────────────────
38614
39511
  {
38615
39512
  name: "dim_login",
@@ -38763,9 +39660,23 @@ var TOOL_DEFINITIONS = [
38763
39660
  },
38764
39661
  {
38765
39662
  name: "dim_get_incoming_friend_requests",
38766
- description: "List pending incoming friend requests.",
38767
- params: {},
38768
- execute: (c) => getIncomingFriendRequests(c)
39663
+ description: "List pending incoming friend requests. Returns { items, nextCursor, hasMore }. Pass cursor from a previous response to page through results.",
39664
+ params: {
39665
+ limit: {
39666
+ type: "number",
39667
+ description: "Maximum number of requests to return (1\u2013100, default 50).",
39668
+ required: false
39669
+ },
39670
+ cursor: {
39671
+ type: "string",
39672
+ description: "Pagination cursor from a previous response nextCursor field. Omit for the first page.",
39673
+ required: false
39674
+ }
39675
+ },
39676
+ execute: (c, a) => getIncomingFriendRequests(
39677
+ c,
39678
+ a
39679
+ )
38769
39680
  },
38770
39681
  // ── Chat ─────────────────────────────────────────────────────────────
38771
39682
  {
@@ -38891,6 +39802,90 @@ var TOOL_DEFINITIONS = [
38891
39802
  },
38892
39803
  execute: (c, a) => tipUser(c, a)
38893
39804
  },
39805
+ {
39806
+ name: "dim_confirm_send_usdc",
39807
+ description: "External wallet: confirm a USDC transfer after signing and broadcasting the transaction. Provide the on-chain signature and the params from the confirmWith object returned by dim_send_usdc.",
39808
+ params: {
39809
+ signature: {
39810
+ type: "string",
39811
+ description: "On-chain transaction signature returned by send_solana_transaction",
39812
+ required: true
39813
+ },
39814
+ recipientAddress: {
39815
+ type: "string",
39816
+ description: "Recipient Solana address (from confirmWith.params)",
39817
+ required: true
39818
+ },
39819
+ amount: {
39820
+ type: "number",
39821
+ description: "Amount in USDC minor units (from confirmWith.params)",
39822
+ required: true
39823
+ },
39824
+ fee: {
39825
+ type: "number",
39826
+ description: "Fee in minor units (from confirmWith.params)"
39827
+ },
39828
+ token: {
39829
+ type: "string",
39830
+ description: "Token type: USDC or SOL (default: USDC)"
39831
+ },
39832
+ ataCreated: {
39833
+ type: "string",
39834
+ description: "Whether a new ATA was created (from confirmWith.params)"
39835
+ },
39836
+ recipientInput: {
39837
+ type: "string",
39838
+ description: "Original recipient input (from confirmWith.params)"
39839
+ }
39840
+ },
39841
+ execute: (c, a) => confirmSendUsdc(
39842
+ c,
39843
+ a
39844
+ )
39845
+ },
39846
+ {
39847
+ name: "dim_confirm_tip_user",
39848
+ description: "External wallet: confirm a tip after signing and broadcasting the transaction. Provide the on-chain signature and the params from the confirmWith object returned by dim_tip_user.",
39849
+ params: {
39850
+ signature: {
39851
+ type: "string",
39852
+ description: "On-chain transaction signature returned by send_solana_transaction",
39853
+ required: true
39854
+ },
39855
+ recipientAddress: {
39856
+ type: "string",
39857
+ description: "Recipient Solana address (from confirmWith.params)",
39858
+ required: true
39859
+ },
39860
+ recipientUserId: {
39861
+ type: "string",
39862
+ description: "Recipient user ID (from confirmWith.params)",
39863
+ required: true
39864
+ },
39865
+ recipientUsername: {
39866
+ type: "string",
39867
+ description: "Recipient username (from confirmWith.params)",
39868
+ required: true
39869
+ },
39870
+ amount: {
39871
+ type: "number",
39872
+ description: "Amount in USDC minor units (from confirmWith.params)",
39873
+ required: true
39874
+ },
39875
+ fee: {
39876
+ type: "number",
39877
+ description: "Fee in minor units (from confirmWith.params)"
39878
+ },
39879
+ ataCreated: {
39880
+ type: "string",
39881
+ description: "Whether a new ATA was created (from confirmWith.params)"
39882
+ }
39883
+ },
39884
+ execute: (c, a) => confirmTipUser(
39885
+ c,
39886
+ a
39887
+ )
39888
+ },
38894
39889
  {
38895
39890
  name: "dim_get_wallet_activity",
38896
39891
  description: "Get recent wallet transaction activity (deposits, payouts, transfers, refunds) and your DIM wallet address. Highlights any claimable items.",
@@ -38950,6 +39945,23 @@ var TOOL_DEFINITIONS = [
38950
39945
  params: {},
38951
39946
  execute: (c) => getMyStats(c)
38952
39947
  },
39948
+ {
39949
+ name: "dim_report_user",
39950
+ description: "Report a user for cheating, harassment, or other violations. Rate limited to 5 reports per hour.",
39951
+ params: {
39952
+ userId: {
39953
+ type: "string",
39954
+ description: "The ID of the user to report",
39955
+ required: true
39956
+ },
39957
+ reason: {
39958
+ type: "string",
39959
+ description: "Reason for the report (max 300 characters)",
39960
+ required: true
39961
+ }
39962
+ },
39963
+ execute: (c, a) => reportUser(c, a)
39964
+ },
38953
39965
  {
38954
39966
  name: "dim_create_lobby",
38955
39967
  description: "Create a new game lobby. Bet amount is in USDC dollars (e.g., 1.00 for $1.00). A 1% fee (min 1 cent) is charged per player.",
@@ -38987,6 +39999,23 @@ var TOOL_DEFINITIONS = [
38987
39999
  },
38988
40000
  execute: (c, a) => depositForLobby(c, a)
38989
40001
  },
40002
+ {
40003
+ name: "dim_confirm_lobby_deposit",
40004
+ description: "External wallet: confirm a lobby deposit after signing and broadcasting the transaction. Polls until the deposit is confirmed on-chain, then returns canProceedToQueue.",
40005
+ params: {
40006
+ lobbyId: {
40007
+ type: "string",
40008
+ description: "The lobby ID (from confirmWith.params)",
40009
+ required: true
40010
+ },
40011
+ signature: {
40012
+ type: "string",
40013
+ description: "On-chain transaction signature returned by send_solana_transaction",
40014
+ required: true
40015
+ }
40016
+ },
40017
+ execute: (c, a) => confirmLobbyDeposit(c, a)
40018
+ },
38990
40019
  {
38991
40020
  name: "dim_leave_lobby",
38992
40021
  description: "Leave a lobby. Use this to exit a lobby you created or joined. Returns when you have left.",
@@ -39079,6 +40108,31 @@ var TOOL_DEFINITIONS = [
39079
40108
  },
39080
40109
  execute: (c, a) => gameLoop(c, a)
39081
40110
  },
40111
+ {
40112
+ name: "dim_confirm_donate_to_pot",
40113
+ description: "External wallet: confirm a game pot donation after signing and broadcasting the transaction.",
40114
+ params: {
40115
+ signature: {
40116
+ type: "string",
40117
+ description: "On-chain transaction signature returned by send_solana_transaction",
40118
+ required: true
40119
+ },
40120
+ gameId: {
40121
+ type: "string",
40122
+ description: "The game ID (from confirmWith.params)",
40123
+ required: true
40124
+ },
40125
+ amount: {
40126
+ type: "number",
40127
+ description: "Amount in USDC minor units (from confirmWith.params)",
40128
+ required: true
40129
+ }
40130
+ },
40131
+ execute: (c, a) => confirmDonateToPot(
40132
+ c,
40133
+ a
40134
+ )
40135
+ },
39082
40136
  {
39083
40137
  name: "dim_request_rematch",
39084
40138
  description: "Request a rematch after a completed game. If both players request, a lobby is created automatically server-side.",
@@ -39472,11 +40526,18 @@ async function executeWithAuthRetry(client, tool, params) {
39472
40526
  if (tool.name === "dim_login") {
39473
40527
  return tool.execute(client, params);
39474
40528
  }
40529
+ if (client.externalSignerMode) {
40530
+ return tool.execute(client, params);
40531
+ }
39475
40532
  try {
39476
40533
  const result = await tool.execute(client, params);
39477
40534
  if (!result.error && !result.isError) return result;
39478
40535
  if (!isUnauthorizedResult(result)) return result;
39479
- await client.authenticate();
40536
+ try {
40537
+ await client.authenticate();
40538
+ } catch {
40539
+ return { error: UNAUTHORIZED_MESSAGE, isError: true };
40540
+ }
39480
40541
  const retryResult = await tool.execute(client, params);
39481
40542
  if (retryResult.isError || retryResult.error) {
39482
40543
  if (isUnauthorizedResult(retryResult)) {