@dimcool/mcp 0.1.29 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7328,8 +7328,6 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7328
7328
  import { Transaction } from "@solana/web3.js";
7329
7329
  import { Transaction as Transaction2 } from "@solana/web3.js";
7330
7330
  import { Transaction as Transaction3 } from "@solana/web3.js";
7331
- import { Transaction as Transaction4 } from "@solana/web3.js";
7332
- import { Transaction as Transaction5 } from "@solana/web3.js";
7333
7331
 
7334
7332
  // ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
7335
7333
  var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
@@ -11587,6 +11585,16 @@ var Auth = class {
11587
11585
  this.logger.debug("Token stored in storage", { tokenKey: TOKEN_KEY });
11588
11586
  return response;
11589
11587
  }
11588
+ async loginWithExternalSignature(address, signedMessage, options) {
11589
+ const response = await this.http.post("/auth/login-wallet", {
11590
+ signedMessage,
11591
+ address,
11592
+ referralCode: options?.referralCode,
11593
+ walletMeta: options?.walletMeta
11594
+ });
11595
+ this.storage.set(TOKEN_KEY, response.access_token);
11596
+ return response;
11597
+ }
11590
11598
  logout() {
11591
11599
  this.logger.debug("Auth.logout called");
11592
11600
  const hadToken = this.storage.get(TOKEN_KEY) !== null;
@@ -11600,6 +11608,10 @@ var Auth = class {
11600
11608
  });
11601
11609
  return isAuth;
11602
11610
  }
11611
+ async getSessionStats() {
11612
+ this.logger.debug("Auth.getSessionStats called");
11613
+ return this.http.get("/auth/sessions/stats");
11614
+ }
11603
11615
  async getLatestSessions(limit) {
11604
11616
  this.logger.debug("Auth.getLatestSessions called", { limit });
11605
11617
  const params = new URLSearchParams();
@@ -11618,6 +11630,9 @@ var Admin = class {
11618
11630
  this.http = http;
11619
11631
  this.logger = logger2;
11620
11632
  }
11633
+ async getInternalBots() {
11634
+ return this.http.get("/admin/internal-bots");
11635
+ }
11621
11636
  async getUserById(id) {
11622
11637
  return this.http.get(`/admin/users/${id}`);
11623
11638
  }
@@ -11823,11 +11838,25 @@ var Users = class {
11823
11838
  async removeFriend(userId) {
11824
11839
  return this.http.delete(`/friends/${userId}`);
11825
11840
  }
11826
- async getIncomingFriendRequests() {
11827
- return this.http.get("/friends/requests/incoming");
11841
+ async getIncomingFriendRequests(opts) {
11842
+ const params = new URLSearchParams();
11843
+ if (opts?.limit !== void 0)
11844
+ params.append("limit", opts.limit.toString());
11845
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
11846
+ const qs = params.toString();
11847
+ return this.http.get(
11848
+ qs ? `/friends/requests/incoming?${qs}` : "/friends/requests/incoming"
11849
+ );
11828
11850
  }
11829
- async getOutgoingFriendRequests() {
11830
- return this.http.get("/friends/requests/outgoing");
11851
+ async getOutgoingFriendRequests(opts) {
11852
+ const params = new URLSearchParams();
11853
+ if (opts?.limit !== void 0)
11854
+ params.append("limit", opts.limit.toString());
11855
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
11856
+ const qs = params.toString();
11857
+ return this.http.get(
11858
+ qs ? `/friends/requests/outgoing?${qs}` : "/friends/requests/outgoing"
11859
+ );
11831
11860
  }
11832
11861
  async acceptFriendRequest(userId) {
11833
11862
  return this.http.post(
@@ -11914,6 +11943,9 @@ var FeatureFlags = class {
11914
11943
  this.loaded = true;
11915
11944
  return this.flags;
11916
11945
  }
11946
+ async getAdminFeatureFlags() {
11947
+ return this.http.get("/feature-flags/admin");
11948
+ }
11917
11949
  isEnabledFlag(name) {
11918
11950
  const flag = this.flags.find((f) => f.name === name);
11919
11951
  return flag?.enabled ?? false;
@@ -11933,10 +11965,11 @@ var FeatureFlags = class {
11933
11965
  }
11934
11966
  return flag;
11935
11967
  }
11936
- async createFeatureFlag(name, enabled) {
11968
+ async createFeatureFlag(name, enabled, description) {
11937
11969
  const flag = await this.http.post("/feature-flags", {
11938
11970
  name,
11939
- enabled
11971
+ enabled,
11972
+ ...description !== void 0 && { description }
11940
11973
  });
11941
11974
  this.flags.push(flag);
11942
11975
  return flag;
@@ -11947,11 +11980,17 @@ var Lobbies = class {
11947
11980
  this.http = http;
11948
11981
  this.logger = logger2;
11949
11982
  }
11983
+ /** Called by SDK after the store is created so mutations can update state directly. */
11984
+ setLobbyStore(store) {
11985
+ this.lobbyStore = store;
11986
+ }
11950
11987
  async createLobby(gameType, betAmount) {
11951
11988
  return this.http.post("/lobbies", { gameType, betAmount });
11952
11989
  }
11953
11990
  async getLobby(lobbyId) {
11954
- return this.http.get(`/lobbies/${lobbyId}`);
11991
+ const lobby = await this.http.get(`/lobbies/${lobbyId}`);
11992
+ this.lobbyStore?.setBaseState([lobby]);
11993
+ return lobby;
11955
11994
  }
11956
11995
  async inviteFriend(lobbyId, friendId) {
11957
11996
  return this.http.post(`/lobbies/${lobbyId}/invite`, {
@@ -11962,7 +12001,9 @@ var Lobbies = class {
11962
12001
  return this.http.post(`/lobbies/${lobbyId}/accept-invite`, {});
11963
12002
  }
11964
12003
  async joinLobby(lobbyId) {
11965
- return this.http.post(`/lobbies/${lobbyId}/join`, {});
12004
+ const lobby = await this.http.post(`/lobbies/${lobbyId}/join`, {});
12005
+ this.lobbyStore?.setBaseState([lobby]);
12006
+ return lobby;
11966
12007
  }
11967
12008
  async removePlayer(lobbyId, userId) {
11968
12009
  return this.http.delete(
@@ -11979,12 +12020,17 @@ var Lobbies = class {
11979
12020
  return this.http.post(`/lobbies/${lobbyId}/join-queue`, {});
11980
12021
  }
11981
12022
  async cancelQueue(lobbyId) {
11982
- return this.http.delete(`/lobbies/${lobbyId}/queue`);
12023
+ const lobby = await this.http.delete(`/lobbies/${lobbyId}/queue`);
12024
+ this.lobbyStore?.setBaseState([lobby]);
12025
+ return lobby;
11983
12026
  }
11984
12027
  async updateBetAmount(lobbyId, betAmount) {
11985
- return this.http.patch(`/lobbies/${lobbyId}/bet-amount`, {
11986
- betAmount
11987
- });
12028
+ const lobby = await this.http.patch(
12029
+ `/lobbies/${lobbyId}/bet-amount`,
12030
+ { betAmount }
12031
+ );
12032
+ this.lobbyStore?.setBaseState([lobby]);
12033
+ return lobby;
11988
12034
  }
11989
12035
  async playSound(lobbyId, sound) {
11990
12036
  return this.http.post(`/lobbies/${lobbyId}/sound`, {
@@ -12023,6 +12069,12 @@ var Games = class {
12023
12069
  this.wallet = wallet;
12024
12070
  this.logger = logger2;
12025
12071
  }
12072
+ setGameStore(store) {
12073
+ this.gameStore = store;
12074
+ }
12075
+ setGameActionsStore(store) {
12076
+ this.gameActionsStore = store;
12077
+ }
12026
12078
  async getAvailableGames() {
12027
12079
  return this.http.get("/games/available");
12028
12080
  }
@@ -12035,7 +12087,11 @@ var Games = class {
12035
12087
  return this.http.get("/games/metrics");
12036
12088
  }
12037
12089
  async getGame(gameId) {
12038
- return this.http.get(`/games/${gameId}`);
12090
+ const existing = this.gameStore?.store.getState().gamesById[gameId];
12091
+ if (existing?.status === "completed") return existing;
12092
+ const game = await this.http.get(`/games/${gameId}`);
12093
+ this.gameStore?.setBaseState([game]);
12094
+ return game;
12039
12095
  }
12040
12096
  /**
12041
12097
  * Get list of currently active (live) games. Public endpoint for spectating.
@@ -12062,7 +12118,13 @@ var Games = class {
12062
12118
  * Get current game state with timer information
12063
12119
  */
12064
12120
  async getGameState(gameId) {
12065
- return this.http.get(`/games/${gameId}/state`);
12121
+ const existing = this.gameActionsStore?.store.getState().statesByGameId[gameId];
12122
+ if (existing?.status === "completed") return existing;
12123
+ const state = await this.http.get(
12124
+ `/games/${gameId}/state`
12125
+ );
12126
+ this.gameActionsStore?.setBaseState(gameId, state);
12127
+ return state;
12066
12128
  }
12067
12129
  /**
12068
12130
  * Request a rematch for a completed game
@@ -12098,8 +12160,18 @@ var Games = class {
12098
12160
  { amountMinor, signedTransaction }
12099
12161
  );
12100
12162
  }
12163
+ /**
12164
+ * Confirm a donation that was already sent by the client (signAndSendTransaction path).
12165
+ */
12166
+ async confirmGameDonationSignature(gameId, amountMinor, signature) {
12167
+ return this.http.post(
12168
+ `/games/${gameId}/donate/confirm-signature`,
12169
+ { amountMinor, signature }
12170
+ );
12171
+ }
12101
12172
  /**
12102
12173
  * One-call donation flow: prepare -> sign -> submit.
12174
+ * Automatically uses the right signing path (embedded vs injected wallet).
12103
12175
  */
12104
12176
  async sendDonation(gameId, amountMinor) {
12105
12177
  if (!this.wallet.hasSigner()) {
@@ -12108,16 +12180,10 @@ var Games = class {
12108
12180
  );
12109
12181
  }
12110
12182
  const prepared = await this.prepareGameDonation(gameId, amountMinor);
12111
- const unsignedTx = Transaction2.from(base64ToBytes(prepared.transaction));
12112
- const signedTx = await this.wallet.signTransaction(unsignedTx);
12113
- const signedTransaction = bytesToBase64(
12114
- signedTx.serialize({ requireAllSignatures: false })
12115
- );
12116
- const result = await this.donateToGame(
12117
- gameId,
12118
- amountMinor,
12119
- signedTransaction
12120
- );
12183
+ const result = await this.wallet.signAndDispatch(prepared.transaction, {
12184
+ onSigned: (signedTxBase64) => this.donateToGame(gameId, amountMinor, signedTxBase64),
12185
+ onSignedAndSent: (sig) => this.confirmGameDonationSignature(gameId, amountMinor, sig)
12186
+ });
12121
12187
  return {
12122
12188
  ...result,
12123
12189
  escrowAddress: prepared.escrowAddress,
@@ -12210,6 +12276,9 @@ var Chat = class {
12210
12276
  this.logger = logger2;
12211
12277
  this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...retryOptions };
12212
12278
  }
12279
+ setDmThreadsStore(store) {
12280
+ this.dmThreadsStore = store;
12281
+ }
12213
12282
  encodeContextId(id) {
12214
12283
  return encodeURIComponent(id);
12215
12284
  }
@@ -12291,7 +12360,9 @@ var Chat = class {
12291
12360
  return response;
12292
12361
  }
12293
12362
  async listDmThreads() {
12294
- return this.http.get("/chat/dm/threads");
12363
+ const threads = await this.http.get("/chat/dm/threads");
12364
+ this.dmThreadsStore?.setBaseState(threads);
12365
+ return threads;
12295
12366
  }
12296
12367
  async getDmThread(dmKey) {
12297
12368
  return this.http.get(
@@ -12379,21 +12450,32 @@ var Tips = class {
12379
12450
  );
12380
12451
  }
12381
12452
  const { publicKey: senderAddress } = await this.wallet.loadWallet();
12382
- const prepared = await this.prepare({ recipientUsername, amount });
12383
- const unsignedTx = Transaction3.from(base64ToBytes(prepared.transaction));
12384
- const signedTx = await this.wallet.signTransaction(unsignedTx);
12385
- const signedTxBase64 = bytesToBase64(
12386
- signedTx.serialize({ requireAllSignatures: false })
12387
- );
12388
- const transfer = await this.wallet.submitTransfer(
12389
- signedTxBase64,
12390
- senderAddress,
12391
- prepared.recipientAddress,
12392
- prepared.amount,
12393
- "USDC",
12394
- false,
12395
- prepared.recipientUsername
12396
- );
12453
+ const supportsPresign = !this.wallet.isSignAndSendMode();
12454
+ const prepared = await this.prepare({
12455
+ recipientUsername,
12456
+ amount,
12457
+ supportsPresign
12458
+ });
12459
+ const transfer = await this.wallet.signAndDispatch(prepared.transaction, {
12460
+ onSigned: (signedTxBase64) => this.wallet.submitTransfer(
12461
+ signedTxBase64,
12462
+ senderAddress,
12463
+ prepared.recipientAddress,
12464
+ prepared.amount,
12465
+ "USDC",
12466
+ false,
12467
+ prepared.recipientUsername
12468
+ ),
12469
+ onSignedAndSent: (sig) => this.wallet.confirmTransferSignature(
12470
+ sig,
12471
+ senderAddress,
12472
+ prepared.recipientAddress,
12473
+ prepared.amount,
12474
+ "USDC",
12475
+ false,
12476
+ prepared.recipientUsername
12477
+ )
12478
+ });
12397
12479
  const message = await this.chat.broadcastGlobalTip(
12398
12480
  prepared.recipientUserId,
12399
12481
  prepared.amount
@@ -12711,13 +12793,15 @@ var Wallet = class {
12711
12793
  );
12712
12794
  }
12713
12795
  try {
12796
+ const supportsPresign = !this.isSignAndSendMode();
12714
12797
  const response = await this.http.post(
12715
12798
  "/wallets/transfer/prepare",
12716
12799
  {
12717
12800
  senderAddress,
12718
12801
  recipient,
12719
12802
  amount: amountMinor,
12720
- token
12803
+ token,
12804
+ supportsPresign
12721
12805
  }
12722
12806
  );
12723
12807
  this.logger.debug("Transfer prepared", {
@@ -12801,6 +12885,29 @@ var Wallet = class {
12801
12885
  throw error;
12802
12886
  }
12803
12887
  }
12888
+ /**
12889
+ * Sign a prepared transaction and invoke the appropriate backend callback
12890
+ * based on the configured signer — without exposing the mode decision to callers.
12891
+ * - Embedded wallet (signAndSend): signs+sends, calls onSignedAndSent(signature)
12892
+ * - Injected wallet (signTransaction): signs locally, calls onSigned(signedTxBase64)
12893
+ */
12894
+ async signAndDispatch(unsignedTxBase64, handlers) {
12895
+ if (!this.signer) {
12896
+ throw new Error(
12897
+ "No signer configured. Call setSigner() with a WalletSigner implementation first."
12898
+ );
12899
+ }
12900
+ const unsignedTx = Transaction2.from(base64ToBytes(unsignedTxBase64));
12901
+ if (this.isSignAndSendMode()) {
12902
+ const sig = await this.signAndSendTransaction(unsignedTx);
12903
+ return handlers.onSignedAndSent(sig);
12904
+ }
12905
+ const signedTx = await this.signTransaction(unsignedTx);
12906
+ const signedBase64 = bytesToBase64(
12907
+ signedTx.serialize({ requireAllSignatures: false })
12908
+ );
12909
+ return handlers.onSigned(signedBase64);
12910
+ }
12804
12911
  /**
12805
12912
  * Full transfer flow in one call: prepare -> sign -> submit
12806
12913
  * Recipient can be username, .sol domain, or Solana address (resolved by backend).
@@ -12817,7 +12924,7 @@ var Wallet = class {
12817
12924
  amount,
12818
12925
  token
12819
12926
  );
12820
- const unsignedTx = Transaction4.from(base64ToBytes(prepared.transaction));
12927
+ const unsignedTx = Transaction2.from(base64ToBytes(prepared.transaction));
12821
12928
  let submitted;
12822
12929
  if (this.isSignAndSendMode()) {
12823
12930
  const signature = await this.signAndSendTransaction(unsignedTx);
@@ -12882,12 +12989,32 @@ var Escrow = class {
12882
12989
  * initializes depositStatus, and prepares the unsigned transaction in one call.
12883
12990
  * Eliminates one HTTP round-trip vs startDeposits() + prepareDepositTransaction().
12884
12991
  */
12885
- async prepareAndStartDeposit(lobbyId) {
12992
+ async prepareAndStartDeposit(lobbyId, supportsPresign) {
12993
+ const presign = supportsPresign ?? !this.wallet.isSignAndSendMode();
12886
12994
  return this.http.post(
12887
12995
  `/escrow/lobby/${lobbyId}/deposit/prepare-and-start`,
12888
- {}
12996
+ { supportsPresign: presign }
12889
12997
  );
12890
12998
  }
12999
+ /**
13000
+ * Sign and submit a prepared (unsigned) deposit transaction using the
13001
+ * configured signer. Automatically picks the right path:
13002
+ * - Embedded wallet: signAndSendTransaction → confirmDepositSignature
13003
+ * - Injected wallet: signTransaction → submitDeposit
13004
+ * Returns the on-chain signature.
13005
+ */
13006
+ async signAndSubmitPreparedDeposit(lobbyId, unsignedTxBase64) {
13007
+ return this.wallet.signAndDispatch(unsignedTxBase64, {
13008
+ onSigned: async (signedTxBase64) => {
13009
+ const result = await this.submitDeposit(lobbyId, signedTxBase64);
13010
+ return result.signature;
13011
+ },
13012
+ onSignedAndSent: async (signature) => {
13013
+ await this.confirmDepositSignature(lobbyId, signature);
13014
+ return signature;
13015
+ }
13016
+ });
13017
+ }
12891
13018
  /**
12892
13019
  * Submit a signed deposit transaction
12893
13020
  * The transaction will be submitted to the Solana network and confirmed
@@ -12930,8 +13057,12 @@ var Escrow = class {
12930
13057
  "No signer configured. Use sdk.wallet.setSigner(...) first."
12931
13058
  );
12932
13059
  }
12933
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
12934
- const unsignedTx = Transaction5.from(base64ToBytes(transaction));
13060
+ const supportsPresign = !this.wallet.isSignAndSendMode();
13061
+ const { transaction } = await this.prepareAndStartDeposit(
13062
+ lobbyId,
13063
+ supportsPresign
13064
+ );
13065
+ const unsignedTx = Transaction3.from(base64ToBytes(transaction));
12935
13066
  let signature;
12936
13067
  if (this.wallet.isSignAndSendMode()) {
12937
13068
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
@@ -12965,8 +13096,13 @@ var Escrow = class {
12965
13096
  };
12966
13097
  }
12967
13098
  /**
12968
- * Zero-polling deposit variant using server-side awaitConfirmation.
12969
- * 2 HTTP calls total, 0 client-side polling. Ideal for agents.
13099
+ * Deposit for a lobby and wait until the calling user's own deposit is confirmed.
13100
+ * Ideal for agents: returns as soon as your deposit is on-chain without waiting
13101
+ * for the other player. The server auto-joins the queue when all players deposit.
13102
+ *
13103
+ * Confirmation is handled asynchronously by the transaction queue processor.
13104
+ * This method polls the deposit status endpoint until the signature appears as
13105
+ * confirmed (up to 60 seconds).
12970
13106
  *
12971
13107
  * Automatically uses signAndSendTransaction or signTransaction based on signer capability.
12972
13108
  */
@@ -12976,26 +13112,45 @@ var Escrow = class {
12976
13112
  "No signer configured. Use sdk.wallet.setSigner(...) first."
12977
13113
  );
12978
13114
  }
12979
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
12980
- const unsignedTx = Transaction5.from(base64ToBytes(transaction));
13115
+ const supportsPresign2 = !this.wallet.isSignAndSendMode();
13116
+ const { transaction } = await this.prepareAndStartDeposit(
13117
+ lobbyId,
13118
+ supportsPresign2
13119
+ );
13120
+ const unsignedTx = Transaction3.from(base64ToBytes(transaction));
12981
13121
  let signature;
12982
13122
  if (this.wallet.isSignAndSendMode()) {
12983
13123
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
12984
- await this.http.post(
12985
- `/escrow/lobby/${lobbyId}/deposit/confirm-signature?awaitConfirmation=true`,
12986
- { signature }
12987
- );
13124
+ await this.confirmDepositSignature(lobbyId, signature);
12988
13125
  } else {
12989
13126
  const signedTx = await this.wallet.signTransaction(unsignedTx);
12990
13127
  const signedBase64 = bytesToBase64(
12991
13128
  signedTx.serialize({ requireAllSignatures: false })
12992
13129
  );
12993
- const result = await this.http.post(`/escrow/lobby/${lobbyId}/deposit/submit?awaitConfirmation=true`, {
12994
- signedTransaction: signedBase64
12995
- });
12996
- signature = result.signature;
13130
+ const submitResult = await this.submitDeposit(lobbyId, signedBase64);
13131
+ signature = submitResult.signature;
13132
+ }
13133
+ const MAX_WAIT_MS = 6e4;
13134
+ const POLL_INTERVAL_MS = 1e3;
13135
+ const startTime = Date.now();
13136
+ while (Date.now() - startTime < MAX_WAIT_MS) {
13137
+ const status = await this.getDepositStatus(lobbyId);
13138
+ const myDeposit = status.deposits.find(
13139
+ (d) => d.transactionHash === signature
13140
+ );
13141
+ if (myDeposit?.status === "confirmed") {
13142
+ return {
13143
+ signature,
13144
+ status: "confirmed",
13145
+ canProceedToQueue: status.canProceedToQueue
13146
+ };
13147
+ }
13148
+ if (status.allConfirmed && status.canProceedToQueue) {
13149
+ return { signature, status: "confirmed", canProceedToQueue: true };
13150
+ }
13151
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
12997
13152
  }
12998
- return { signature, status: "confirmed", canProceedToQueue: true };
13153
+ return { signature, status: "pending", canProceedToQueue: false };
12999
13154
  }
13000
13155
  async claimLobbyDepositRefund(lobbyId, depositSignature) {
13001
13156
  return this.http.post(
@@ -13337,9 +13492,10 @@ var Support = class {
13337
13492
  }
13338
13493
  };
13339
13494
  var Markets = class {
13340
- constructor(http, logger2) {
13495
+ constructor(http, logger2, wallet) {
13341
13496
  this.http = http;
13342
13497
  this.logger = logger2;
13498
+ this.wallet = wallet;
13343
13499
  }
13344
13500
  /**
13345
13501
  * Get the prediction market state for a game.
@@ -13381,6 +13537,35 @@ var Markets = class {
13381
13537
  { signedTransaction, outcomeId, amountMinor }
13382
13538
  );
13383
13539
  }
13540
+ /**
13541
+ * Confirm a buy order that was already broadcast by the client (signAndSendTransaction path).
13542
+ */
13543
+ async confirmBuyOrderSignature(gameId, depositSignature, outcomeId, amountMinor) {
13544
+ return this.http.post(
13545
+ `/games/${gameId}/market/orders/buy/confirm-signature`,
13546
+ { depositSignature, outcomeId, amountMinor }
13547
+ );
13548
+ }
13549
+ /**
13550
+ * One-call buy order: prepare → sign → submit, automatically choosing
13551
+ * the right signing path (embedded vs injected wallet).
13552
+ */
13553
+ async buy(gameId, outcomeId, amountMinor) {
13554
+ if (!this.wallet?.hasSigner()) {
13555
+ throw new Error(
13556
+ "No signer configured. Use sdk.wallet.setSigner(...) first."
13557
+ );
13558
+ }
13559
+ const { transaction } = await this.prepareBuyOrder(
13560
+ gameId,
13561
+ outcomeId,
13562
+ amountMinor
13563
+ );
13564
+ return this.wallet.signAndDispatch(transaction, {
13565
+ onSigned: (signedTxBase64) => this.submitBuyOrder(gameId, signedTxBase64, outcomeId, amountMinor),
13566
+ onSignedAndSent: (sig) => this.confirmBuyOrderSignature(gameId, sig, outcomeId, amountMinor)
13567
+ });
13568
+ }
13384
13569
  /**
13385
13570
  * Sell shares back to the AMM pool.
13386
13571
  * @param gameId - The game ID
@@ -13429,6 +13614,20 @@ var Markets = class {
13429
13614
  return this.http.get(`/games/admin/markets${qs ? `?${qs}` : ""}`);
13430
13615
  }
13431
13616
  };
13617
+ var NoopAnalyticsClient = class {
13618
+ userLoggedIn(_user, _meta) {
13619
+ }
13620
+ userLoggedOut() {
13621
+ }
13622
+ sessionRestored(_user) {
13623
+ }
13624
+ track(_event, _properties) {
13625
+ }
13626
+ setUserProperties(_properties) {
13627
+ }
13628
+ group(_groupType, _groupKey, _properties) {
13629
+ }
13630
+ };
13432
13631
  var BaseWsTransport = class {
13433
13632
  constructor() {
13434
13633
  this.roomRefs = /* @__PURE__ */ new Map();
@@ -13516,21 +13715,35 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
13516
13715
  this.accessToken = null;
13517
13716
  this.reconnectAttempts = 0;
13518
13717
  this.reconnectTimer = null;
13519
- this.periodicReconnectInterval = null;
13520
- this.maxReconnectAttempts = 5;
13521
- this.reconnectDelay = 1e3;
13522
- this.reconnectDelayMax = 3e4;
13718
+ this.needsReconnect = false;
13719
+ this.visibilityHandler = null;
13523
13720
  this.wildcardHandlers = /* @__PURE__ */ new Set();
13524
13721
  this.eventHandlers = /* @__PURE__ */ new Map();
13525
13722
  this.registeredEvents = /* @__PURE__ */ new Set();
13526
13723
  this.wildcardRegistered = false;
13527
13724
  this.url = url2;
13725
+ if (typeof document !== "undefined") {
13726
+ this.visibilityHandler = () => {
13727
+ if (document.visibilityState === "visible" && !this.connectionState.connected) {
13728
+ this.reconnectImmediately();
13729
+ }
13730
+ };
13731
+ document.addEventListener("visibilitychange", this.visibilityHandler);
13732
+ }
13528
13733
  }
13529
13734
  connect() {
13735
+ if (this.reconnectTimer !== null) {
13736
+ clearTimeout(this.reconnectTimer);
13737
+ this.reconnectTimer = null;
13738
+ }
13739
+ this.reconnectAttempts = 0;
13530
13740
  this.ensureSocket();
13531
13741
  }
13532
13742
  disconnect() {
13533
- this.clearPeriodicReconnect();
13743
+ if (this.visibilityHandler && typeof document !== "undefined") {
13744
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
13745
+ this.visibilityHandler = null;
13746
+ }
13534
13747
  if (!this.socket) return;
13535
13748
  this.socket.disconnect();
13536
13749
  this.socket = null;
@@ -13629,13 +13842,13 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
13629
13842
  reconnection: false
13630
13843
  });
13631
13844
  socket.on("connect", () => {
13632
- this.clearPeriodicReconnect();
13633
13845
  this.updateConnectionState({
13634
13846
  connected: true,
13635
13847
  connecting: false,
13636
13848
  error: null
13637
13849
  });
13638
- const wasReconnect = this.reconnectAttempts > 0;
13850
+ const wasReconnect = this.needsReconnect;
13851
+ this.needsReconnect = false;
13639
13852
  this.reconnectAttempts = 0;
13640
13853
  this.onReconnect();
13641
13854
  if (wasReconnect) {
@@ -13646,6 +13859,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
13646
13859
  });
13647
13860
  socket.on("disconnect", (reason) => {
13648
13861
  this.updateConnectionState({ connected: false, connecting: false });
13862
+ this.needsReconnect = true;
13649
13863
  this.clearSocketForReconnect();
13650
13864
  if (reason === "io server disconnect") {
13651
13865
  this.handleAuthFailure("io server disconnect");
@@ -13655,6 +13869,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
13655
13869
  });
13656
13870
  socket.on("connect_error", (error) => {
13657
13871
  const message = error?.message || "connect_error";
13872
+ this.needsReconnect = true;
13658
13873
  this.clearSocketForReconnect();
13659
13874
  if (message.includes("unauthorized") || message.includes("401")) {
13660
13875
  this.handleAuthFailure(message);
@@ -13682,31 +13897,31 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
13682
13897
  this.socket.removeAllListeners();
13683
13898
  this.socket = null;
13684
13899
  }
13685
- clearPeriodicReconnect() {
13686
- if (this.periodicReconnectInterval !== null) {
13687
- clearInterval(this.periodicReconnectInterval);
13688
- this.periodicReconnectInterval = null;
13900
+ reconnectImmediately() {
13901
+ if (this.reconnectTimer !== null) {
13902
+ clearTimeout(this.reconnectTimer);
13903
+ this.reconnectTimer = null;
13689
13904
  }
13690
- }
13691
- startPeriodicReconnect() {
13692
- if (this.periodicReconnectInterval !== null) return;
13693
- this.periodicReconnectInterval = setInterval(() => {
13694
- if (this.connectionState.connected) return;
13695
- this.reconnectAttempts = 0;
13696
- this.ensureSocket();
13697
- }, _StandaloneWsTransport2.PERIODIC_RECONNECT_MS);
13905
+ this.reconnectAttempts = 0;
13906
+ this.ensureSocket();
13698
13907
  }
13699
13908
  scheduleReconnect() {
13700
13909
  if (this.reconnectTimer !== null) return;
13701
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
13702
- this.startPeriodicReconnect();
13703
- return;
13704
- }
13705
- const delay = Math.min(
13706
- this.reconnectDelay * Math.pow(2, this.reconnectAttempts),
13707
- this.reconnectDelayMax
13708
- );
13910
+ const attempt = this.reconnectAttempts;
13709
13911
  this.reconnectAttempts += 1;
13912
+ let delay;
13913
+ if (attempt < _StandaloneWsTransport2.FAST_RETRY_LIMIT) {
13914
+ delay = _StandaloneWsTransport2.FAST_RETRY_DELAY_MS;
13915
+ } else {
13916
+ const backoffAttempt = attempt - _StandaloneWsTransport2.FAST_RETRY_LIMIT;
13917
+ delay = Math.min(
13918
+ _StandaloneWsTransport2.FAST_RETRY_DELAY_MS * Math.pow(2, backoffAttempt),
13919
+ _StandaloneWsTransport2.MAX_BACKOFF_MS
13920
+ );
13921
+ if (attempt === _StandaloneWsTransport2.FAST_RETRY_LIMIT) {
13922
+ this.dispatchEvent("connection:slow-retry", { timestamp: Date.now() });
13923
+ }
13924
+ }
13710
13925
  this.reconnectTimer = setTimeout(() => {
13711
13926
  this.reconnectTimer = null;
13712
13927
  this.ensureSocket();
@@ -13767,7 +13982,9 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
13767
13982
  this.wildcardRegistered = true;
13768
13983
  }
13769
13984
  };
13770
- _StandaloneWsTransport.PERIODIC_RECONNECT_MS = 5 * 60 * 1e3;
13985
+ _StandaloneWsTransport.FAST_RETRY_LIMIT = 60;
13986
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS = 1e3;
13987
+ _StandaloneWsTransport.MAX_BACKOFF_MS = 3e4;
13771
13988
  var StandaloneWsTransport = _StandaloneWsTransport;
13772
13989
  function createSdkStore(initial) {
13773
13990
  const store = createStore()(subscribeWithSelector(() => initial));
@@ -13788,14 +14005,16 @@ function createLobbyStore(transport2) {
13788
14005
  depositStatusByLobbyId: {}
13789
14006
  });
13790
14007
  const setBaseState = (lobbies) => {
13791
- const lobbiesById = {};
13792
- for (const lobby of lobbies) {
13793
- lobbiesById[lobby.id] = lobby;
13794
- }
13795
- store.updateState((state) => ({
13796
- ...state,
13797
- lobbiesById
13798
- }));
14008
+ store.updateState((state) => {
14009
+ const lobbiesById = { ...state.lobbiesById };
14010
+ for (const lobby of lobbies) {
14011
+ const existing = lobbiesById[lobby.id];
14012
+ if (!existing || lobby.updatedAt >= existing.updatedAt) {
14013
+ lobbiesById[lobby.id] = lobby;
14014
+ }
14015
+ }
14016
+ return { ...state, lobbiesById };
14017
+ });
13799
14018
  };
13800
14019
  const applyWsEvent = (event) => {
13801
14020
  switch (event.event) {
@@ -13925,12 +14144,23 @@ function createGameStore(transport2) {
13925
14144
  gamesById: {},
13926
14145
  spectatorCounts: {}
13927
14146
  });
14147
+ const STATUS_RANK = {
14148
+ active: 0,
14149
+ completed: 1,
14150
+ abandoned: 1
14151
+ };
13928
14152
  const setBaseState = (games) => {
13929
- const gamesById = {};
13930
- for (const game of games) {
13931
- gamesById[game.gameId] = game;
13932
- }
13933
- store.updateState((state) => ({ ...state, gamesById }));
14153
+ store.updateState((state) => {
14154
+ const gamesById = { ...state.gamesById };
14155
+ for (const game of games) {
14156
+ const existing = gamesById[game.gameId];
14157
+ if (existing && STATUS_RANK[existing.status] > STATUS_RANK[game.status]) {
14158
+ continue;
14159
+ }
14160
+ gamesById[game.gameId] = game;
14161
+ }
14162
+ return { ...state, gamesById };
14163
+ });
13934
14164
  };
13935
14165
  const applyWsEvent = (event) => {
13936
14166
  switch (event.event) {
@@ -14026,8 +14256,21 @@ function createGameActionsStore(transport2) {
14026
14256
  });
14027
14257
  };
14028
14258
  const isNonRpsState = (state) => Boolean(state && !isRpsState(state));
14259
+ const pendingEvents = /* @__PURE__ */ new Map();
14260
+ function enqueue(gameId, event) {
14261
+ const q = pendingEvents.get(gameId) ?? [];
14262
+ q.push(event);
14263
+ pendingEvents.set(gameId, q);
14264
+ }
14265
+ function drainQueue(gameId) {
14266
+ const q = pendingEvents.get(gameId);
14267
+ if (!q?.length) return;
14268
+ pendingEvents.delete(gameId);
14269
+ for (const ev of q) applyWsEvent(ev);
14270
+ }
14029
14271
  const setBaseState = (gameId, state) => {
14030
14272
  updateState(gameId, state);
14273
+ drainQueue(gameId);
14031
14274
  };
14032
14275
  const clearState = (gameId) => {
14033
14276
  store.updateState((state) => {
@@ -14037,6 +14280,7 @@ function createGameActionsStore(transport2) {
14037
14280
  const { [gameId]: _, ...rest } = state.statesByGameId;
14038
14281
  return { ...state, statesByGameId: rest };
14039
14282
  });
14283
+ pendingEvents.delete(gameId);
14040
14284
  };
14041
14285
  const applyWsEvent = (event) => {
14042
14286
  switch (event.event) {
@@ -14055,7 +14299,10 @@ function createGameActionsStore(transport2) {
14055
14299
  }
14056
14300
  case "game:rps:starting": {
14057
14301
  const current = store.getState().statesByGameId[event.payload.gameId];
14058
- if (!current || !isRpsState(current)) return;
14302
+ if (!current || !isRpsState(current)) {
14303
+ enqueue(event.payload.gameId, event);
14304
+ return;
14305
+ }
14059
14306
  const betAmount = typeof event.payload.betAmount === "number" ? event.payload.betAmount : void 0;
14060
14307
  const startedAt = typeof event.payload.startedAt === "string" ? event.payload.startedAt : current.roundState.startedAt;
14061
14308
  const bufferEndsAt = typeof event.payload.bufferEndsAt === "string" ? event.payload.bufferEndsAt : current.roundState.selectionEndsAt;
@@ -14076,7 +14323,10 @@ function createGameActionsStore(transport2) {
14076
14323
  }
14077
14324
  case "game:rps:round:started": {
14078
14325
  const current = store.getState().statesByGameId[event.payload.gameId];
14079
- if (!current || !isRpsState(current)) return;
14326
+ if (!current || !isRpsState(current)) {
14327
+ enqueue(event.payload.gameId, event);
14328
+ return;
14329
+ }
14080
14330
  const actions = {};
14081
14331
  const baseUsers = /* @__PURE__ */ new Set();
14082
14332
  Object.keys(current.roundState.actions).forEach(
@@ -14110,7 +14360,10 @@ function createGameActionsStore(transport2) {
14110
14360
  }
14111
14361
  case "game:rps:action:received": {
14112
14362
  const current = store.getState().statesByGameId[event.payload.gameId];
14113
- if (!current || !isRpsState(current)) return;
14363
+ if (!current || !isRpsState(current)) {
14364
+ enqueue(event.payload.gameId, event);
14365
+ return;
14366
+ }
14114
14367
  const updated = {
14115
14368
  ...current,
14116
14369
  roundState: {
@@ -14129,7 +14382,10 @@ function createGameActionsStore(transport2) {
14129
14382
  }
14130
14383
  case "game:rps:timer:cutoff": {
14131
14384
  const current = store.getState().statesByGameId[event.payload.gameId];
14132
- if (!current || !isRpsState(current)) return;
14385
+ if (!current || !isRpsState(current)) {
14386
+ enqueue(event.payload.gameId, event);
14387
+ return;
14388
+ }
14133
14389
  const updated = {
14134
14390
  ...current,
14135
14391
  roundState: {
@@ -14143,7 +14399,10 @@ function createGameActionsStore(transport2) {
14143
14399
  }
14144
14400
  case "game:rps:round:reveal": {
14145
14401
  const current = store.getState().statesByGameId[event.payload.gameId];
14146
- if (!current || !isRpsState(current)) return;
14402
+ if (!current || !isRpsState(current)) {
14403
+ enqueue(event.payload.gameId, event);
14404
+ return;
14405
+ }
14147
14406
  const actions = {};
14148
14407
  const payloadActions = event.payload.actions;
14149
14408
  Object.keys(payloadActions || {}).forEach((userId) => {
@@ -14167,7 +14426,10 @@ function createGameActionsStore(transport2) {
14167
14426
  }
14168
14427
  case "game:rps:round:completed": {
14169
14428
  const current = store.getState().statesByGameId[event.payload.gameId];
14170
- if (!current || !isRpsState(current)) return;
14429
+ if (!current || !isRpsState(current)) {
14430
+ enqueue(event.payload.gameId, event);
14431
+ return;
14432
+ }
14171
14433
  const roundHistory = [
14172
14434
  ...current.roundHistory || [],
14173
14435
  {
@@ -14192,7 +14454,10 @@ function createGameActionsStore(transport2) {
14192
14454
  }
14193
14455
  case "game:rps:timeout": {
14194
14456
  const current = store.getState().statesByGameId[event.payload.gameId];
14195
- if (!current || !isRpsState(current)) return;
14457
+ if (!current || !isRpsState(current)) {
14458
+ enqueue(event.payload.gameId, event);
14459
+ return;
14460
+ }
14196
14461
  const timedOutUser = event.payload.playerId;
14197
14462
  const action = event.payload.action;
14198
14463
  const updated = {
@@ -14217,7 +14482,10 @@ function createGameActionsStore(transport2) {
14217
14482
  const payload = event.payload;
14218
14483
  const { gameId } = payload;
14219
14484
  const current = store.getState().statesByGameId[gameId];
14220
- if (!current) return;
14485
+ if (!current) {
14486
+ enqueue(gameId, event);
14487
+ return;
14488
+ }
14221
14489
  const updated = isRpsCompletionPayload(payload) && isRpsState(current) ? {
14222
14490
  ...current,
14223
14491
  status: "completed",
@@ -14266,11 +14534,15 @@ function createGameActionsStore(transport2) {
14266
14534
  const current = store.getState().statesByGameId[gameId];
14267
14535
  const updated = current ? { ...current, ...incoming } : incoming;
14268
14536
  updateState(gameId, updated);
14537
+ drainQueue(gameId);
14269
14538
  break;
14270
14539
  }
14271
14540
  case "game:rematch:requested": {
14272
14541
  const current = store.getState().statesByGameId[event.payload.gameId];
14273
- if (!current) return;
14542
+ if (!current) {
14543
+ enqueue(event.payload.gameId, event);
14544
+ return;
14545
+ }
14274
14546
  const requestedBy = event.payload.requestedBy;
14275
14547
  const userId = event.payload.userId;
14276
14548
  const requested = new Set(
@@ -14286,7 +14558,10 @@ function createGameActionsStore(transport2) {
14286
14558
  }
14287
14559
  case "game:rematch:cancelled": {
14288
14560
  const current = store.getState().statesByGameId[event.payload.gameId];
14289
- if (!current) return;
14561
+ if (!current) {
14562
+ enqueue(event.payload.gameId, event);
14563
+ return;
14564
+ }
14290
14565
  const requestedBy = event.payload.requestedBy ?? [];
14291
14566
  const updated = {
14292
14567
  ...current,
@@ -14297,7 +14572,10 @@ function createGameActionsStore(transport2) {
14297
14572
  }
14298
14573
  case "game:rematch:started": {
14299
14574
  const current = store.getState().statesByGameId[event.payload.gameId];
14300
- if (!current) return;
14575
+ if (!current) {
14576
+ enqueue(event.payload.gameId, event);
14577
+ return;
14578
+ }
14301
14579
  const updated = {
14302
14580
  ...current,
14303
14581
  rematchRequestedBy: event.payload.playerIds ?? []
@@ -14308,7 +14586,10 @@ function createGameActionsStore(transport2) {
14308
14586
  case "game:pot:updated": {
14309
14587
  const { gameId, totalPotMinor } = event.payload;
14310
14588
  const current = store.getState().statesByGameId[gameId];
14311
- if (!current) return;
14589
+ if (!current) {
14590
+ enqueue(gameId, event);
14591
+ return;
14592
+ }
14312
14593
  const updated = {
14313
14594
  ...current,
14314
14595
  totalPotMinor
@@ -14321,12 +14602,123 @@ function createGameActionsStore(transport2) {
14321
14602
  }
14322
14603
  };
14323
14604
  const joinGame = (gameId) => transport2.joinRoom(`game:${gameId}`);
14605
+ const getCountdownDigit = (gameId, nowMs) => {
14606
+ const state = store.getState().statesByGameId[gameId];
14607
+ if (!state) return null;
14608
+ if (isRpsState(state)) {
14609
+ if (state.roundState.phase !== "starting") return null;
14610
+ const remaining = new Date(state.roundState.selectionEndsAt).getTime() - nowMs;
14611
+ if (remaining <= 0) return null;
14612
+ return Math.ceil(remaining / 1e3);
14613
+ }
14614
+ const bufferEndsAt = state.bufferEndsAt;
14615
+ if (bufferEndsAt) {
14616
+ const remaining = new Date(bufferEndsAt).getTime() - nowMs;
14617
+ if (remaining <= 0) return null;
14618
+ return Math.ceil(remaining / 1e3);
14619
+ }
14620
+ return null;
14621
+ };
14622
+ const getChessClockTimes = (gameId, nowMs) => {
14623
+ const state = store.getState().statesByGameId[gameId];
14624
+ if (!state || state.gameType !== "chess") return null;
14625
+ const s = state;
14626
+ let whiteMs = s.whiteTimeMs ?? 0;
14627
+ let blackMs = s.blackTimeMs ?? 0;
14628
+ if (s.status === "active" && s.currentPlayerId) {
14629
+ const startedAt = Date.parse(s.turnStartedAt);
14630
+ if (!Number.isNaN(startedAt)) {
14631
+ const elapsed = Math.max(0, nowMs - startedAt);
14632
+ if (s.currentPlayerId === s.whitePlayerId) {
14633
+ whiteMs = Math.max(0, whiteMs - elapsed);
14634
+ } else if (s.currentPlayerId === s.blackPlayerId) {
14635
+ blackMs = Math.max(0, blackMs - elapsed);
14636
+ }
14637
+ }
14638
+ }
14639
+ return { whiteTimeMs: whiteMs, blackTimeMs: blackMs };
14640
+ };
14641
+ const getChessCapturedPieces = (gameId) => {
14642
+ const state = store.getState().statesByGameId[gameId];
14643
+ if (!state || state.gameType !== "chess") return null;
14644
+ const fen = state.fen;
14645
+ if (!fen) return { capturedByWhite: [], capturedByBlack: [] };
14646
+ const placement = fen.split(" ")[0];
14647
+ const white = {};
14648
+ const black = {};
14649
+ for (const char of placement) {
14650
+ if (char === "/" || char >= "1" && char <= "8") continue;
14651
+ const lower = char.toLowerCase();
14652
+ if (char === lower) {
14653
+ black[lower] = (black[lower] ?? 0) + 1;
14654
+ } else {
14655
+ white[lower] = (white[lower] ?? 0) + 1;
14656
+ }
14657
+ }
14658
+ const INITIAL = {
14659
+ p: 8,
14660
+ r: 2,
14661
+ n: 2,
14662
+ b: 2,
14663
+ q: 1,
14664
+ k: 1
14665
+ };
14666
+ const capturedByWhite = [];
14667
+ const capturedByBlack = [];
14668
+ for (const [type, initial] of Object.entries(INITIAL)) {
14669
+ const missingBlack = initial - (black[type] ?? 0);
14670
+ for (let i = 0; i < missingBlack; i++) capturedByWhite.push(`b${type}`);
14671
+ const missingWhite = initial - (white[type] ?? 0);
14672
+ for (let i = 0; i < missingWhite; i++) capturedByBlack.push(`w${type}`);
14673
+ }
14674
+ return { capturedByWhite, capturedByBlack };
14675
+ };
14676
+ const getTicTacToeClockTimes = (gameId, nowMs) => {
14677
+ const state = store.getState().statesByGameId[gameId];
14678
+ if (!state || state.gameType !== "tic-tac-toe") return null;
14679
+ const s = state;
14680
+ let xMs = s.xTimeMs ?? 0;
14681
+ let oMs = s.oTimeMs ?? 0;
14682
+ if (s.status === "active" && s.currentPlayerId) {
14683
+ const currentMark = s.playerMarks[s.currentPlayerId];
14684
+ const startedAt = Date.parse(s.turnStartedAt);
14685
+ if (!Number.isNaN(startedAt)) {
14686
+ const elapsed = Math.max(0, nowMs - startedAt);
14687
+ if (currentMark === "X") xMs = Math.max(0, xMs - elapsed);
14688
+ else if (currentMark === "O") oMs = Math.max(0, oMs - elapsed);
14689
+ }
14690
+ }
14691
+ return { xTimeMs: xMs, oTimeMs: oMs };
14692
+ };
14693
+ const getConnect4ClockTimes = (gameId, nowMs) => {
14694
+ const state = store.getState().statesByGameId[gameId];
14695
+ if (!state || state.gameType !== "connect-four") return null;
14696
+ const s = state;
14697
+ let redMs = s.redTimeMs ?? 0;
14698
+ let yellowMs = s.yellowTimeMs ?? 0;
14699
+ if (s.status === "active" && s.currentPlayerId) {
14700
+ const currentColor = s.playerColors[s.currentPlayerId];
14701
+ const startedAt = Date.parse(s.turnStartedAt);
14702
+ if (!Number.isNaN(startedAt)) {
14703
+ const elapsed = Math.max(0, nowMs - startedAt);
14704
+ if (currentColor === "RED") redMs = Math.max(0, redMs - elapsed);
14705
+ else if (currentColor === "YELLOW")
14706
+ yellowMs = Math.max(0, yellowMs - elapsed);
14707
+ }
14708
+ }
14709
+ return { redTimeMs: redMs, yellowTimeMs: yellowMs };
14710
+ };
14324
14711
  return {
14325
14712
  store,
14326
14713
  setBaseState,
14327
14714
  clearState,
14328
14715
  applyWsEvent,
14329
- joinGame
14716
+ joinGame,
14717
+ getCountdownDigit,
14718
+ getChessClockTimes,
14719
+ getChessCapturedPieces,
14720
+ getTicTacToeClockTimes,
14721
+ getConnect4ClockTimes
14330
14722
  };
14331
14723
  }
14332
14724
  function isRpsState(state) {
@@ -14668,7 +15060,38 @@ function createNotificationsStore() {
14668
15060
  appNotifications: []
14669
15061
  }));
14670
15062
  };
14671
- return { store, applyWsEvent, setListFromApi, clear };
15063
+ const markAsRead = (id) => {
15064
+ store.updateState((state) => ({
15065
+ ...state,
15066
+ appNotifications: state.appNotifications.map(
15067
+ (n) => n.id === id ? { ...n, read: true } : n
15068
+ )
15069
+ }));
15070
+ };
15071
+ const markAllAsRead = () => {
15072
+ store.updateState((state) => ({
15073
+ ...state,
15074
+ appNotifications: state.appNotifications.map((n) => ({
15075
+ ...n,
15076
+ read: true
15077
+ }))
15078
+ }));
15079
+ };
15080
+ const dismiss = (id) => {
15081
+ store.updateState((state) => ({
15082
+ ...state,
15083
+ appNotifications: state.appNotifications.filter((n) => n.id !== id)
15084
+ }));
15085
+ };
15086
+ return {
15087
+ store,
15088
+ applyWsEvent,
15089
+ setListFromApi,
15090
+ clear,
15091
+ markAsRead,
15092
+ markAllAsRead,
15093
+ dismiss
15094
+ };
14672
15095
  }
14673
15096
  function createFriendsStore() {
14674
15097
  const store = createSdkStore({
@@ -14953,12 +15376,14 @@ var WsRouter = class {
14953
15376
  }
14954
15377
  const decoded = decodeWsEvent(eventName, payload);
14955
15378
  if (!decoded) return;
14956
- this.deps.lobbyStore.applyWsEvent(decoded);
14957
- this.deps.gameStore.applyWsEvent(decoded);
14958
- this.deps.gameActionsStore.applyWsEvent(decoded);
14959
- this.deps.chatStore.applyWsEvent(decoded);
14960
- this.deps.dmThreadsStore.applyWsEvent(decoded);
14961
- this.deps.notificationsStore.applyWsEvent(decoded);
15379
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
15380
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
15381
+ this.deps.lobbyStore.applyWsEvent(event);
15382
+ this.deps.gameStore.applyWsEvent(event);
15383
+ this.deps.gameActionsStore.applyWsEvent(event);
15384
+ this.deps.chatStore.applyWsEvent(event);
15385
+ this.deps.dmThreadsStore.applyWsEvent(event);
15386
+ this.deps.notificationsStore.applyWsEvent(event);
14962
15387
  });
14963
15388
  }
14964
15389
  stop() {
@@ -14970,12 +15395,14 @@ var WsRouter = class {
14970
15395
  this.transport.subscribeEvent(eventName, (payload) => {
14971
15396
  const decoded = decodeWsEvent(eventName, payload);
14972
15397
  if (!decoded) return;
14973
- this.deps.lobbyStore.applyWsEvent(decoded);
14974
- this.deps.gameStore.applyWsEvent(decoded);
14975
- this.deps.gameActionsStore.applyWsEvent(decoded);
14976
- this.deps.chatStore.applyWsEvent(decoded);
14977
- this.deps.dmThreadsStore.applyWsEvent(decoded);
14978
- this.deps.notificationsStore.applyWsEvent(decoded);
15398
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
15399
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
15400
+ this.deps.lobbyStore.applyWsEvent(event);
15401
+ this.deps.gameStore.applyWsEvent(event);
15402
+ this.deps.gameActionsStore.applyWsEvent(event);
15403
+ this.deps.chatStore.applyWsEvent(event);
15404
+ this.deps.dmThreadsStore.applyWsEvent(event);
15405
+ this.deps.notificationsStore.applyWsEvent(event);
14979
15406
  });
14980
15407
  }
14981
15408
  }
@@ -15015,6 +15442,7 @@ var SDK = class {
15015
15442
  constructor(config) {
15016
15443
  const baseUrl = config.baseUrl || "http://localhost:3000";
15017
15444
  const logger2 = config.logger || logger;
15445
+ this.analytics = config.analytics ?? new NoopAnalyticsClient();
15018
15446
  this.http = config.httpClient || new HttpClient(
15019
15447
  baseUrl,
15020
15448
  config.storage,
@@ -15047,14 +15475,18 @@ var SDK = class {
15047
15475
  this.referrals = new Referrals(this.http, logger2);
15048
15476
  this.reports = new Reports(this.http, logger2);
15049
15477
  this.support = new Support(this.http, logger2);
15050
- this.markets = new Markets(this.http, logger2);
15478
+ this.markets = new Markets(this.http, logger2, this.wallet);
15051
15479
  this.wsTransport = config.wsTransport || new StandaloneWsTransport(baseUrl);
15052
15480
  this.wsTransport.setAppId(config.appId);
15053
15481
  this.lobbyStore = createLobbyStore(this.wsTransport);
15482
+ this.lobbies.setLobbyStore(this.lobbyStore);
15054
15483
  this.gameStore = createGameStore(this.wsTransport);
15484
+ this.games.setGameStore(this.gameStore);
15055
15485
  this.gameActionsStore = createGameActionsStore(this.wsTransport);
15486
+ this.games.setGameActionsStore(this.gameActionsStore);
15056
15487
  this.chatStore = createChatStore(this.wsTransport);
15057
15488
  this.dmThreadsStore = createDmThreadsStore();
15489
+ this.chat.setDmThreadsStore(this.dmThreadsStore);
15058
15490
  this.notificationsStore = createNotificationsStore();
15059
15491
  this.friendsStore = createFriendsStore();
15060
15492
  this.notifications = new Notifications(
@@ -15084,6 +15516,29 @@ var SDK = class {
15084
15516
  this.wsTransport.connect();
15085
15517
  await this.wsTransport.waitUntilConnected(timeoutMs);
15086
15518
  }
15519
+ /**
15520
+ * Handle the full deposit-to-queue lifecycle for a lobby.
15521
+ *
15522
+ * After the deposit is confirmed, the backend may not have processed the
15523
+ * `lobby.deposit.allConfirmed` event yet, so the lobby can still be in
15524
+ * 'preparing' status. This method re-fetches the lobby and explicitly
15525
+ * joins the queue if needed, then pushes the latest state to the store
15526
+ * so the UI transitions immediately.
15527
+ */
15528
+ async depositAndJoinQueue(lobbyId) {
15529
+ const result = await this.escrow.depositForLobby(lobbyId);
15530
+ if (!result.canProceedToQueue) {
15531
+ const lobby2 = await this.lobbies.getLobby(lobbyId);
15532
+ this.lobbyStore.setBaseState([lobby2]);
15533
+ return { ...result, lobby: lobby2 };
15534
+ }
15535
+ let lobby = await this.lobbies.getLobby(lobbyId);
15536
+ if (lobby.status === "preparing") {
15537
+ lobby = await this.lobbies.joinQueue(lobbyId);
15538
+ }
15539
+ this.lobbyStore.setBaseState([lobby]);
15540
+ return { ...result, lobby };
15541
+ }
15087
15542
  };
15088
15543
  var import_utils = __toESM2(require_dist(), 1);
15089
15544
  var export_MICRO_UNITS = import_utils.MICRO_UNITS;
@@ -15094,10 +15549,34 @@ var export_toMajor = import_utils.toMajor;
15094
15549
  var import_tweetnacl = __toESM(require_nacl_fast(), 1);
15095
15550
  import { Keypair } from "@solana/web3.js";
15096
15551
  import bs58 from "bs58";
15552
+ function decodeJwtSub(token) {
15553
+ try {
15554
+ const parts2 = token.split(".");
15555
+ if (parts2.length < 2) return null;
15556
+ const base64 = parts2[1].replace(/-/g, "+").replace(/_/g, "/");
15557
+ const json = Buffer.from(base64, "base64").toString("utf-8");
15558
+ const parsed = JSON.parse(json);
15559
+ return typeof parsed.sub === "string" ? parsed.sub : null;
15560
+ } catch {
15561
+ return null;
15562
+ }
15563
+ }
15564
+ function signatureToBytes(sig) {
15565
+ if (sig.includes("-") || sig.includes("_")) {
15566
+ const padded = sig.replace(/-/g, "+").replace(/_/g, "/") + "==".slice(sig.length % 4 === 0 ? 4 : sig.length % 4);
15567
+ return new Uint8Array(Buffer.from(padded, "base64"));
15568
+ }
15569
+ if (/[+/=]/.test(sig)) {
15570
+ return new Uint8Array(Buffer.from(sig, "base64"));
15571
+ }
15572
+ return bs58.decode(sig);
15573
+ }
15097
15574
  var DimAgentClient = class {
15098
15575
  sdk;
15099
15576
  agentConfig;
15577
+ externalSignerMode;
15100
15578
  keypair;
15579
+ _externalAddress = null;
15101
15580
  config;
15102
15581
  authenticated = false;
15103
15582
  userId = null;
@@ -15111,21 +15590,35 @@ var DimAgentClient = class {
15111
15590
  constructor(config) {
15112
15591
  this.config = config;
15113
15592
  this.agentConfig = config.agentConfig;
15114
- const secretKeyBytes = bs58.decode(config.walletPrivateKey);
15115
- this.keypair = Keypair.fromSecretKey(secretKeyBytes);
15593
+ this.externalSignerMode = !config.walletPrivateKey;
15594
+ if (config.walletPrivateKey) {
15595
+ const secretKeyBytes = bs58.decode(config.walletPrivateKey);
15596
+ this.keypair = Keypair.fromSecretKey(secretKeyBytes);
15597
+ } else {
15598
+ this.keypair = null;
15599
+ }
15600
+ const storage = new NodeStorage();
15601
+ if (config.accessToken) {
15602
+ storage.set(TOKEN_KEY, config.accessToken);
15603
+ const sub = decodeJwtSub(config.accessToken);
15604
+ if (sub) {
15605
+ this.authenticated = true;
15606
+ this.userId = sub;
15607
+ }
15608
+ }
15116
15609
  this.sdk = new SDK({
15117
15610
  appId: "dim-agents",
15118
15611
  baseUrl: config.apiUrl || "https://api.dim.cool",
15119
- storage: new NodeStorage(),
15612
+ storage,
15120
15613
  autoPay: {
15121
- enabled: true,
15614
+ enabled: !this.externalSignerMode,
15122
15615
  maxAmountMinor: 25e3,
15123
15616
  maxRetries: 1
15124
15617
  }
15125
15618
  });
15126
15619
  }
15127
15620
  get walletAddress() {
15128
- return this.keypair.publicKey.toBase58();
15621
+ return this.keypair?.publicKey.toBase58() ?? this._externalAddress ?? "";
15129
15622
  }
15130
15623
  get isAuthenticated() {
15131
15624
  return this.authenticated;
@@ -15134,17 +15627,23 @@ var DimAgentClient = class {
15134
15627
  return this.userId;
15135
15628
  }
15136
15629
  async authenticate() {
15630
+ if (!this.keypair) {
15631
+ throw new Error(
15632
+ "No keypair configured. Use dim_request_auth_message + dim_complete_login for external signer mode."
15633
+ );
15634
+ }
15635
+ const keypair = this.keypair;
15137
15636
  this.sdk.wallet.setSigner({
15138
15637
  address: this.walletAddress,
15139
15638
  signMessage: async (message) => {
15140
15639
  const signature = import_tweetnacl.default.sign.detached(
15141
15640
  new TextEncoder().encode(message),
15142
- this.keypair.secretKey
15641
+ keypair.secretKey
15143
15642
  );
15144
15643
  return Buffer.from(signature).toString("base64");
15145
15644
  },
15146
15645
  signTransaction: async (transaction) => {
15147
- transaction.partialSign(this.keypair);
15646
+ transaction.partialSign(keypair);
15148
15647
  return transaction;
15149
15648
  }
15150
15649
  });
@@ -15198,26 +15697,61 @@ var DimAgentClient = class {
15198
15697
  get pendingEventCount() {
15199
15698
  return this.eventQueue.length;
15200
15699
  }
15201
- /** Get the Keypair for transaction signing. */
15700
+ /** Get the Keypair for transaction signing (keypair mode only). */
15202
15701
  getKeypair() {
15702
+ if (!this.keypair) {
15703
+ throw new Error(
15704
+ "No keypair configured \u2014 running in external signer mode."
15705
+ );
15706
+ }
15203
15707
  return this.keypair;
15204
15708
  }
15205
- // ── Game chat cursors ────────────────────────────────────────────────
15206
- /** Get the last-seen chat timestamp for a game. */
15207
- getGameChatCursor(gameId) {
15208
- return this.gameChatCursors.get(gameId);
15209
- }
15210
- /** Update the last-seen chat timestamp for a game. */
15211
- setGameChatCursor(gameId, timestamp) {
15212
- this.gameChatCursors.set(gameId, timestamp);
15213
- }
15214
- // ── Spend tracking ──────────────────────────────────────────────────
15215
- resetDailySpendIfNeeded() {
15216
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
15217
- if (this.spendResetDate !== today) {
15218
- this.dailySpendMinor = 0;
15219
- this.spendResetDate = today;
15220
- }
15709
+ /** Request the auth handshake message for a given address (external signer mode). */
15710
+ async requestAuthMessage(address) {
15711
+ return this.sdk.auth.generateHandshake(address);
15712
+ }
15713
+ /**
15714
+ * Complete authentication using an externally provided signature (external signer mode).
15715
+ * @param address - Solana wallet address (base58)
15716
+ * @param signature - Detached ed25519 signature in base58, base64, or base64url (from sign_solana_message)
15717
+ */
15718
+ async completeAuth(address, signature) {
15719
+ const signatureBytes = signatureToBytes(signature);
15720
+ const signedMessage = Buffer.from(signatureBytes).toString("base64");
15721
+ const response = await this.sdk.auth.loginWithExternalSignature(
15722
+ address,
15723
+ signedMessage,
15724
+ {
15725
+ referralCode: this.config.referralCode,
15726
+ walletMeta: { type: "phantom-embedded", provider: "injected" }
15727
+ }
15728
+ );
15729
+ this.sdk.wsTransport.setAccessToken(response.access_token);
15730
+ this._externalAddress = address;
15731
+ this.authenticated = true;
15732
+ this.userId = response.user.id;
15733
+ return {
15734
+ userId: response.user.id,
15735
+ username: response.user.username,
15736
+ accessToken: response.access_token
15737
+ };
15738
+ }
15739
+ // ── Game chat cursors ────────────────────────────────────────────────
15740
+ /** Get the last-seen chat timestamp for a game. */
15741
+ getGameChatCursor(gameId) {
15742
+ return this.gameChatCursors.get(gameId);
15743
+ }
15744
+ /** Update the last-seen chat timestamp for a game. */
15745
+ setGameChatCursor(gameId, timestamp) {
15746
+ this.gameChatCursors.set(gameId, timestamp);
15747
+ }
15748
+ // ── Spend tracking ──────────────────────────────────────────────────
15749
+ resetDailySpendIfNeeded() {
15750
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
15751
+ if (this.spendResetDate !== today) {
15752
+ this.dailySpendMinor = 0;
15753
+ this.spendResetDate = today;
15754
+ }
15221
15755
  }
15222
15756
  recordSpend(amountMinor) {
15223
15757
  this.resetDailySpendIfNeeded();
@@ -15329,6 +15863,77 @@ async function login(client) {
15329
15863
  };
15330
15864
  }
15331
15865
  }
15866
+ async function requestAuthMessage(client, args) {
15867
+ try {
15868
+ const { message } = await client.requestAuthMessage(args.address);
15869
+ return {
15870
+ data: {
15871
+ message,
15872
+ address: args.address,
15873
+ 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).'
15874
+ }
15875
+ };
15876
+ } catch (error) {
15877
+ return {
15878
+ error: `Failed to get auth message: ${error instanceof Error ? error.message : String(error)}`,
15879
+ isError: true
15880
+ };
15881
+ }
15882
+ }
15883
+ async function completeLogin(client, args) {
15884
+ try {
15885
+ const result = await client.completeAuth(args.address, args.signature);
15886
+ client.startEventListeners();
15887
+ const nextSteps = [];
15888
+ if (result.username == null || result.username === "") {
15889
+ nextSteps.push("No username set \u2014 call dim_set_username to claim one");
15890
+ }
15891
+ nextSteps.push("Check your balance with dim_get_balance");
15892
+ nextSteps.push("Explore available games with dim_list_games");
15893
+ try {
15894
+ const summary = await client.sdk.referrals.getSummary();
15895
+ if (!summary.hasReferrer) {
15896
+ nextSteps.push(
15897
+ "No referrer yet \u2014 call dim_apply_referral_code for 10% fee discount"
15898
+ );
15899
+ }
15900
+ if (result.username) {
15901
+ nextSteps.push(
15902
+ `Share your referral code "${result.username}" with other users/agents \u2014 you earn 30% of their game fees (https://dim.cool/?ref=${result.username})`
15903
+ );
15904
+ }
15905
+ } catch {
15906
+ }
15907
+ const response = {
15908
+ success: true,
15909
+ userId: result.userId,
15910
+ username: result.username ?? null,
15911
+ walletAddress: client.walletAddress,
15912
+ walletNote: "DIM is noncustodial. Signing is handled by your external wallet for this session.",
15913
+ safetyRules: SAFETY_RULES,
15914
+ nextSteps
15915
+ };
15916
+ const ac = client.agentConfig;
15917
+ const dailyLimit = ac?.dailySpendLimit ?? 20;
15918
+ response.agentConfig = {
15919
+ autoAcceptFriendRequests: ac?.autoAcceptFriendRequests ?? false,
15920
+ autoReplyDms: ac?.autoReplyDms ?? false,
15921
+ autoPlayGames: ac?.autoPlayGames ?? false,
15922
+ maxBetPerGame: ac?.maxBetPerGame ?? 1,
15923
+ dailySpendLimit: dailyLimit,
15924
+ autoJoinGlobalChat: ac?.autoJoinGlobalChat ?? false,
15925
+ autoPromoteReferrals: ac?.autoPromoteReferrals ?? false,
15926
+ dailySpentSoFar: client.dailySpentDollars,
15927
+ dailyRemaining: dailyLimit - client.dailySpentDollars
15928
+ };
15929
+ return { data: response };
15930
+ } catch (error) {
15931
+ return {
15932
+ error: `Login failed: ${error instanceof Error ? error.message : String(error)}`,
15933
+ isError: true
15934
+ };
15935
+ }
15936
+ }
15332
15937
  async function getProfile(client) {
15333
15938
  try {
15334
15939
  if (!client.currentUserId) {
@@ -15457,10 +16062,19 @@ async function listFriends(client, args) {
15457
16062
  };
15458
16063
  }
15459
16064
  }
15460
- async function getIncomingFriendRequests(client) {
16065
+ async function getIncomingFriendRequests(client, args) {
15461
16066
  try {
15462
- const result = await client.sdk.users.getIncomingFriendRequests();
15463
- return { data: result };
16067
+ const result = await client.sdk.users.getIncomingFriendRequests({
16068
+ limit: args?.limit,
16069
+ cursor: args?.cursor
16070
+ });
16071
+ return {
16072
+ data: {
16073
+ items: result.items,
16074
+ nextCursor: result.nextCursor,
16075
+ hasMore: !!result.nextCursor
16076
+ }
16077
+ };
15464
16078
  } catch (error) {
15465
16079
  return {
15466
16080
  error: `Failed to get requests: ${error instanceof Error ? error.message : String(error)}`,
@@ -15581,6 +16195,42 @@ async function sendUsdc(client, args) {
15581
16195
  const spendErr = client.checkSpendLimit(args.amount);
15582
16196
  if (spendErr) return { error: spendErr, isError: true };
15583
16197
  const amountMinor = Math.round(args.amount * 1e6);
16198
+ if (client.externalSignerMode) {
16199
+ const senderAddress = client.walletAddress;
16200
+ if (!senderAddress) {
16201
+ return {
16202
+ error: "Wallet address not set. Call dim_complete_login first.",
16203
+ isError: true
16204
+ };
16205
+ }
16206
+ const prepared = await client.sdk.wallet.prepareTransfer(
16207
+ senderAddress,
16208
+ args.recipient,
16209
+ amountMinor
16210
+ );
16211
+ return {
16212
+ data: {
16213
+ needsSigning: true,
16214
+ unsignedTx: prepared.transaction,
16215
+ fee: prepared.fee,
16216
+ totalAmount: prepared.totalAmount,
16217
+ recipientAddress: prepared.recipientAddress,
16218
+ ataCreated: prepared.ataCreated ?? false,
16219
+ confirmWith: {
16220
+ tool: "dim_confirm_send_usdc",
16221
+ params: {
16222
+ recipientAddress: prepared.recipientAddress,
16223
+ amount: amountMinor,
16224
+ fee: prepared.fee,
16225
+ token: "USDC",
16226
+ ataCreated: prepared.ataCreated ?? false,
16227
+ recipientInput: args.recipient
16228
+ }
16229
+ },
16230
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_send_usdc with the signature and the confirmWith.params."
16231
+ }
16232
+ };
16233
+ }
15584
16234
  const result = await client.sdk.wallet.send(args.recipient, amountMinor);
15585
16235
  client.recordSpend(amountMinor);
15586
16236
  return {
@@ -15601,11 +16251,74 @@ async function sendUsdc(client, args) {
15601
16251
  };
15602
16252
  }
15603
16253
  }
16254
+ async function confirmSendUsdc(client, args) {
16255
+ try {
16256
+ const senderAddress = client.walletAddress;
16257
+ const result = await client.sdk.wallet.confirmTransferSignature(
16258
+ args.signature,
16259
+ senderAddress,
16260
+ args.recipientAddress,
16261
+ args.amount,
16262
+ args.token || "USDC",
16263
+ args.ataCreated,
16264
+ args.recipientInput
16265
+ );
16266
+ client.recordSpend(args.amount);
16267
+ return {
16268
+ data: {
16269
+ success: true,
16270
+ signature: result.signature,
16271
+ status: result.status,
16272
+ recipientAddress: args.recipientAddress
16273
+ }
16274
+ };
16275
+ } catch (error) {
16276
+ return {
16277
+ error: `Failed to confirm USDC transfer: ${error instanceof Error ? error.message : String(error)}`,
16278
+ isError: true
16279
+ };
16280
+ }
16281
+ }
15604
16282
  async function tipUser(client, args) {
15605
16283
  try {
15606
16284
  const spendErr = client.checkSpendLimit(args.amount);
15607
16285
  if (spendErr) return { error: spendErr, isError: true };
15608
16286
  const amountMinor = Math.round(args.amount * 1e6);
16287
+ if (client.externalSignerMode) {
16288
+ const senderAddress = client.walletAddress;
16289
+ if (!senderAddress) {
16290
+ return {
16291
+ error: "Wallet address not set. Call dim_complete_login first.",
16292
+ isError: true
16293
+ };
16294
+ }
16295
+ const prepared = await client.sdk.tips.prepare({
16296
+ recipientUsername: args.recipientUsername,
16297
+ amount: amountMinor,
16298
+ supportsPresign: false
16299
+ });
16300
+ return {
16301
+ data: {
16302
+ needsSigning: true,
16303
+ unsignedTx: prepared.transaction,
16304
+ fee: prepared.fee,
16305
+ totalAmount: prepared.totalAmount,
16306
+ recipientAddress: prepared.recipientAddress,
16307
+ confirmWith: {
16308
+ tool: "dim_confirm_tip_user",
16309
+ params: {
16310
+ recipientAddress: prepared.recipientAddress,
16311
+ recipientUserId: prepared.recipientUserId,
16312
+ recipientUsername: prepared.recipientUsername,
16313
+ amount: prepared.amount,
16314
+ fee: prepared.fee,
16315
+ ataCreated: false
16316
+ }
16317
+ },
16318
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_tip_user with the signature and the confirmWith.params."
16319
+ }
16320
+ };
16321
+ }
15609
16322
  const result = await client.sdk.tips.send(
15610
16323
  args.recipientUsername,
15611
16324
  amountMinor
@@ -15628,6 +16341,38 @@ async function tipUser(client, args) {
15628
16341
  };
15629
16342
  }
15630
16343
  }
16344
+ async function confirmTipUser(client, args) {
16345
+ try {
16346
+ const senderAddress = client.walletAddress;
16347
+ await client.sdk.wallet.confirmTransferSignature(
16348
+ args.signature,
16349
+ senderAddress,
16350
+ args.recipientAddress,
16351
+ args.amount,
16352
+ "USDC",
16353
+ args.ataCreated ?? false,
16354
+ args.recipientUsername
16355
+ );
16356
+ await client.sdk.tips.broadcast({
16357
+ recipientUserId: args.recipientUserId,
16358
+ amount: args.amount
16359
+ });
16360
+ client.recordSpend(args.amount);
16361
+ return {
16362
+ data: {
16363
+ success: true,
16364
+ signature: args.signature,
16365
+ recipient: args.recipientUsername,
16366
+ broadcastedToGlobalChat: true
16367
+ }
16368
+ };
16369
+ } catch (error) {
16370
+ return {
16371
+ error: `Failed to confirm tip: ${error instanceof Error ? error.message : String(error)}`,
16372
+ isError: true
16373
+ };
16374
+ }
16375
+ }
15631
16376
  async function getWalletActivity(client, args) {
15632
16377
  try {
15633
16378
  const activity = await client.sdk.wallet.getActivity({
@@ -15734,6 +16479,7 @@ function buildRpsContext(state, agentUserId) {
15734
16479
  roundHistory: state.roundHistory,
15735
16480
  phase: roundState?.phase,
15736
16481
  timeRemaining: roundState?.timeRemaining,
16482
+ ...state.bufferEndsAt != null && { bufferEndsAt: state.bufferEndsAt },
15737
16483
  moveFormat: 'dim_submit_action: gameType="rock-paper-scissors", action="play", payload={ action: "rock"|"paper"|"scissors" }'
15738
16484
  };
15739
16485
  }
@@ -15746,6 +16492,7 @@ function buildChessContext(state, agentUserId) {
15746
16492
  yourColor,
15747
16493
  whiteTimeMs: state.whiteTimeMs,
15748
16494
  blackTimeMs: state.blackTimeMs,
16495
+ ...state.bufferEndsAt != null && { bufferEndsAt: state.bufferEndsAt },
15749
16496
  moveFormat: 'dim_submit_action: gameType="chess", action="move", payload={ from: "e2", to: "e4" }'
15750
16497
  };
15751
16498
  }
@@ -15955,6 +16702,23 @@ async function createLobby(client, args) {
15955
16702
  }
15956
16703
  async function depositForLobby(client, args) {
15957
16704
  try {
16705
+ if (client.externalSignerMode) {
16706
+ const { transaction } = await client.sdk.escrow.prepareAndStartDeposit(
16707
+ args.lobbyId,
16708
+ false
16709
+ );
16710
+ return {
16711
+ data: {
16712
+ needsSigning: true,
16713
+ unsignedTx: transaction,
16714
+ confirmWith: {
16715
+ tool: "dim_confirm_lobby_deposit",
16716
+ params: { lobbyId: args.lobbyId }
16717
+ },
16718
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_lobby_deposit with the lobbyId and signature."
16719
+ }
16720
+ };
16721
+ }
15958
16722
  const result = await client.sdk.escrow.depositForLobbySync(args.lobbyId);
15959
16723
  return {
15960
16724
  data: {
@@ -15971,6 +16735,44 @@ async function depositForLobby(client, args) {
15971
16735
  };
15972
16736
  }
15973
16737
  }
16738
+ async function confirmLobbyDeposit(client, args) {
16739
+ try {
16740
+ await client.sdk.escrow.confirmDepositSignature(
16741
+ args.lobbyId,
16742
+ args.signature
16743
+ );
16744
+ const MAX_WAIT_MS = 6e4;
16745
+ const POLL_INTERVAL_MS = 1e3;
16746
+ const startTime = Date.now();
16747
+ while (Date.now() - startTime < MAX_WAIT_MS) {
16748
+ const status = await client.sdk.escrow.getDepositStatus(args.lobbyId);
16749
+ if (status.canProceedToQueue) {
16750
+ return {
16751
+ data: {
16752
+ signature: args.signature,
16753
+ status: "confirmed",
16754
+ canProceedToQueue: true,
16755
+ hint: "Deposit confirmed. Call dim_join_queue to enter matchmaking."
16756
+ }
16757
+ };
16758
+ }
16759
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
16760
+ }
16761
+ return {
16762
+ data: {
16763
+ signature: args.signature,
16764
+ status: "pending",
16765
+ canProceedToQueue: false,
16766
+ hint: "Deposit submitted but not yet confirmed on-chain. Try dim_confirm_lobby_deposit again in a few seconds."
16767
+ }
16768
+ };
16769
+ } catch (error) {
16770
+ return {
16771
+ error: `Failed to confirm deposit: ${error instanceof Error ? error.message : String(error)}`,
16772
+ isError: true
16773
+ };
16774
+ }
16775
+ }
15974
16776
  async function leaveLobby(client, args) {
15975
16777
  try {
15976
16778
  const result = await client.sdk.lobbies.leaveLobby(args.lobbyId);
@@ -16040,7 +16842,7 @@ async function joinQueue(client, args) {
16040
16842
  gameId,
16041
16843
  matched: !!matched,
16042
16844
  ...spectateUrl && { spectateUrl },
16043
- 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."
16845
+ 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."
16044
16846
  }
16045
16847
  };
16046
16848
  } catch (error) {
@@ -16115,6 +16917,23 @@ async function donateToPot(client, args) {
16115
16917
  const spendErr = client.checkSpendLimit(args.amount);
16116
16918
  if (spendErr) return { error: spendErr, isError: true };
16117
16919
  const amountMinor = Math.round(args.amount * 1e6);
16920
+ if (client.externalSignerMode) {
16921
+ const prepared = await client.sdk.games.prepareGameDonation(
16922
+ args.gameId,
16923
+ amountMinor
16924
+ );
16925
+ return {
16926
+ data: {
16927
+ needsSigning: true,
16928
+ unsignedTx: prepared.transaction,
16929
+ confirmWith: {
16930
+ tool: "dim_confirm_donate_to_pot",
16931
+ params: { gameId: args.gameId, amount: amountMinor }
16932
+ },
16933
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_donate_to_pot with the signature."
16934
+ }
16935
+ };
16936
+ }
16118
16937
  const result = await client.sdk.games.sendDonation(
16119
16938
  args.gameId,
16120
16939
  amountMinor
@@ -16138,6 +16957,29 @@ async function donateToPot(client, args) {
16138
16957
  };
16139
16958
  }
16140
16959
  }
16960
+ async function confirmDonateToPot(client, args) {
16961
+ try {
16962
+ const result = await client.sdk.games.confirmGameDonationSignature(
16963
+ args.gameId,
16964
+ args.amount,
16965
+ args.signature
16966
+ );
16967
+ client.recordSpend(args.amount);
16968
+ return {
16969
+ data: {
16970
+ success: true,
16971
+ gameId: args.gameId,
16972
+ signature: result.signature,
16973
+ status: result.status
16974
+ }
16975
+ };
16976
+ } catch (error) {
16977
+ return {
16978
+ error: `Failed to confirm donation: ${error instanceof Error ? error.message : String(error)}`,
16979
+ isError: true
16980
+ };
16981
+ }
16982
+ }
16141
16983
  async function getGame(client, args) {
16142
16984
  try {
16143
16985
  const game = await client.sdk.games.getGame(args.gameId);
@@ -16149,6 +16991,12 @@ async function getGame(client, args) {
16149
16991
  };
16150
16992
  }
16151
16993
  }
16994
+ function isInStartingPhase(state) {
16995
+ const roundState = state.roundState;
16996
+ if (roundState?.phase === "starting") return true;
16997
+ if (state.status === "active" && state.currentPlayerId == null) return true;
16998
+ return false;
16999
+ }
16152
17000
  async function gameLoop(client, args) {
16153
17001
  try {
16154
17002
  const { gameId } = args;
@@ -16166,7 +17014,7 @@ async function gameLoop(client, args) {
16166
17014
  if (state?.status === "completed") {
16167
17015
  return buildGameLoopReturn(client, gameId, state, "completed");
16168
17016
  }
16169
- if (state?.currentPlayerId === client.currentUserId) {
17017
+ if (state?.currentPlayerId === client.currentUserId && !isInStartingPhase(state)) {
16170
17018
  return buildGameLoopReturn(client, gameId, state, "your-turn");
16171
17019
  }
16172
17020
  await raceTimeout(
@@ -16792,10 +17640,10 @@ async function checkNotifications(client) {
16792
17640
  isError: true
16793
17641
  };
16794
17642
  }
16795
- const [notifications, dmThreads, friendRequests] = await Promise.all([
17643
+ const [notifications, dmThreads, friendRequestsPage] = await Promise.all([
16796
17644
  client.sdk.notifications.list({ page: 1, limit: 20 }),
16797
17645
  client.sdk.chat.listDmThreads(),
16798
- client.sdk.users.getIncomingFriendRequests()
17646
+ client.sdk.users.getIncomingFriendRequests({ limit: 20 })
16799
17647
  ]);
16800
17648
  const unreadDms = dmThreads.filter(
16801
17649
  (t) => (t.unreadCount ?? 0) > 0
@@ -16807,9 +17655,10 @@ async function checkNotifications(client) {
16807
17655
  unreadNotificationCount: notifications.unreadCount,
16808
17656
  notifications: notifications.notifications.filter((n) => !n.read),
16809
17657
  unreadDmThreads: unreadDms,
16810
- incomingFriendRequests: friendRequests,
17658
+ incomingFriendRequests: friendRequestsPage.items,
17659
+ hasMoreIncomingFriendRequests: !!friendRequestsPage.nextCursor,
16811
17660
  pendingWsEvents: client.pendingEventCount,
16812
- hint: "Use dim_get_pending_events to drain buffered real-time events."
17661
+ 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."
16813
17662
  }
16814
17663
  };
16815
17664
  } catch (error) {
@@ -17195,6 +18044,22 @@ async function getGameHistory(client, args) {
17195
18044
  };
17196
18045
  }
17197
18046
  }
18047
+ async function reportUser(client, args) {
18048
+ try {
18049
+ await client.sdk.reports.create(args.userId, args.reason);
18050
+ return {
18051
+ data: {
18052
+ success: true,
18053
+ hint: "Report submitted. The DIM moderation team will review it."
18054
+ }
18055
+ };
18056
+ } catch (error) {
18057
+ return {
18058
+ error: `Failed to report user: ${error instanceof Error ? error.message : String(error)}`,
18059
+ isError: true
18060
+ };
18061
+ }
18062
+ }
17198
18063
  async function getMyStats(client) {
17199
18064
  try {
17200
18065
  if (!client.currentUserId) {
@@ -17225,6 +18090,36 @@ async function getMyStats(client) {
17225
18090
 
17226
18091
  // ../dim-agent-core/src/tools/index.ts
17227
18092
  var TOOL_DEFINITIONS = [
18093
+ // ── External signer mode (auth) ──────────────────────────────────────
18094
+ {
18095
+ name: "dim_request_auth_message",
18096
+ 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.",
18097
+ params: {
18098
+ address: {
18099
+ type: "string",
18100
+ description: "Your Solana wallet address (base58)",
18101
+ required: true
18102
+ }
18103
+ },
18104
+ execute: (c, a) => requestAuthMessage(c, a)
18105
+ },
18106
+ {
18107
+ name: "dim_complete_login",
18108
+ description: "External wallet login step 2: provide the wallet address and signature from sign_solana_message to complete authentication with DIM.",
18109
+ params: {
18110
+ address: {
18111
+ type: "string",
18112
+ description: "Your Solana wallet address (base58)",
18113
+ required: true
18114
+ },
18115
+ signature: {
18116
+ type: "string",
18117
+ description: "Signature from sign_solana_message \u2014 base58, base64, or base64url accepted",
18118
+ required: true
18119
+ }
18120
+ },
18121
+ execute: (c, a) => completeLogin(c, a)
18122
+ },
17228
18123
  // ── Auth ─────────────────────────────────────────────────────────────
17229
18124
  {
17230
18125
  name: "dim_login",
@@ -17378,9 +18273,23 @@ var TOOL_DEFINITIONS = [
17378
18273
  },
17379
18274
  {
17380
18275
  name: "dim_get_incoming_friend_requests",
17381
- description: "List pending incoming friend requests.",
17382
- params: {},
17383
- execute: (c) => getIncomingFriendRequests(c)
18276
+ description: "List pending incoming friend requests. Returns { items, nextCursor, hasMore }. Pass cursor from a previous response to page through results.",
18277
+ params: {
18278
+ limit: {
18279
+ type: "number",
18280
+ description: "Maximum number of requests to return (1\u2013100, default 50).",
18281
+ required: false
18282
+ },
18283
+ cursor: {
18284
+ type: "string",
18285
+ description: "Pagination cursor from a previous response nextCursor field. Omit for the first page.",
18286
+ required: false
18287
+ }
18288
+ },
18289
+ execute: (c, a) => getIncomingFriendRequests(
18290
+ c,
18291
+ a
18292
+ )
17384
18293
  },
17385
18294
  // ── Chat ─────────────────────────────────────────────────────────────
17386
18295
  {
@@ -17506,6 +18415,90 @@ var TOOL_DEFINITIONS = [
17506
18415
  },
17507
18416
  execute: (c, a) => tipUser(c, a)
17508
18417
  },
18418
+ {
18419
+ name: "dim_confirm_send_usdc",
18420
+ 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.",
18421
+ params: {
18422
+ signature: {
18423
+ type: "string",
18424
+ description: "On-chain transaction signature returned by send_solana_transaction",
18425
+ required: true
18426
+ },
18427
+ recipientAddress: {
18428
+ type: "string",
18429
+ description: "Recipient Solana address (from confirmWith.params)",
18430
+ required: true
18431
+ },
18432
+ amount: {
18433
+ type: "number",
18434
+ description: "Amount in USDC minor units (from confirmWith.params)",
18435
+ required: true
18436
+ },
18437
+ fee: {
18438
+ type: "number",
18439
+ description: "Fee in minor units (from confirmWith.params)"
18440
+ },
18441
+ token: {
18442
+ type: "string",
18443
+ description: "Token type: USDC or SOL (default: USDC)"
18444
+ },
18445
+ ataCreated: {
18446
+ type: "string",
18447
+ description: "Whether a new ATA was created (from confirmWith.params)"
18448
+ },
18449
+ recipientInput: {
18450
+ type: "string",
18451
+ description: "Original recipient input (from confirmWith.params)"
18452
+ }
18453
+ },
18454
+ execute: (c, a) => confirmSendUsdc(
18455
+ c,
18456
+ a
18457
+ )
18458
+ },
18459
+ {
18460
+ name: "dim_confirm_tip_user",
18461
+ 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.",
18462
+ params: {
18463
+ signature: {
18464
+ type: "string",
18465
+ description: "On-chain transaction signature returned by send_solana_transaction",
18466
+ required: true
18467
+ },
18468
+ recipientAddress: {
18469
+ type: "string",
18470
+ description: "Recipient Solana address (from confirmWith.params)",
18471
+ required: true
18472
+ },
18473
+ recipientUserId: {
18474
+ type: "string",
18475
+ description: "Recipient user ID (from confirmWith.params)",
18476
+ required: true
18477
+ },
18478
+ recipientUsername: {
18479
+ type: "string",
18480
+ description: "Recipient username (from confirmWith.params)",
18481
+ required: true
18482
+ },
18483
+ amount: {
18484
+ type: "number",
18485
+ description: "Amount in USDC minor units (from confirmWith.params)",
18486
+ required: true
18487
+ },
18488
+ fee: {
18489
+ type: "number",
18490
+ description: "Fee in minor units (from confirmWith.params)"
18491
+ },
18492
+ ataCreated: {
18493
+ type: "string",
18494
+ description: "Whether a new ATA was created (from confirmWith.params)"
18495
+ }
18496
+ },
18497
+ execute: (c, a) => confirmTipUser(
18498
+ c,
18499
+ a
18500
+ )
18501
+ },
17509
18502
  {
17510
18503
  name: "dim_get_wallet_activity",
17511
18504
  description: "Get recent wallet transaction activity (deposits, payouts, transfers, refunds) and your DIM wallet address. Highlights any claimable items.",
@@ -17565,6 +18558,23 @@ var TOOL_DEFINITIONS = [
17565
18558
  params: {},
17566
18559
  execute: (c) => getMyStats(c)
17567
18560
  },
18561
+ {
18562
+ name: "dim_report_user",
18563
+ description: "Report a user for cheating, harassment, or other violations. Rate limited to 5 reports per hour.",
18564
+ params: {
18565
+ userId: {
18566
+ type: "string",
18567
+ description: "The ID of the user to report",
18568
+ required: true
18569
+ },
18570
+ reason: {
18571
+ type: "string",
18572
+ description: "Reason for the report (max 300 characters)",
18573
+ required: true
18574
+ }
18575
+ },
18576
+ execute: (c, a) => reportUser(c, a)
18577
+ },
17568
18578
  {
17569
18579
  name: "dim_create_lobby",
17570
18580
  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.",
@@ -17602,6 +18612,23 @@ var TOOL_DEFINITIONS = [
17602
18612
  },
17603
18613
  execute: (c, a) => depositForLobby(c, a)
17604
18614
  },
18615
+ {
18616
+ name: "dim_confirm_lobby_deposit",
18617
+ description: "External wallet: confirm a lobby deposit after signing and broadcasting the transaction. Polls until the deposit is confirmed on-chain, then returns canProceedToQueue.",
18618
+ params: {
18619
+ lobbyId: {
18620
+ type: "string",
18621
+ description: "The lobby ID (from confirmWith.params)",
18622
+ required: true
18623
+ },
18624
+ signature: {
18625
+ type: "string",
18626
+ description: "On-chain transaction signature returned by send_solana_transaction",
18627
+ required: true
18628
+ }
18629
+ },
18630
+ execute: (c, a) => confirmLobbyDeposit(c, a)
18631
+ },
17605
18632
  {
17606
18633
  name: "dim_leave_lobby",
17607
18634
  description: "Leave a lobby. Use this to exit a lobby you created or joined. Returns when you have left.",
@@ -17694,6 +18721,31 @@ var TOOL_DEFINITIONS = [
17694
18721
  },
17695
18722
  execute: (c, a) => gameLoop(c, a)
17696
18723
  },
18724
+ {
18725
+ name: "dim_confirm_donate_to_pot",
18726
+ description: "External wallet: confirm a game pot donation after signing and broadcasting the transaction.",
18727
+ params: {
18728
+ signature: {
18729
+ type: "string",
18730
+ description: "On-chain transaction signature returned by send_solana_transaction",
18731
+ required: true
18732
+ },
18733
+ gameId: {
18734
+ type: "string",
18735
+ description: "The game ID (from confirmWith.params)",
18736
+ required: true
18737
+ },
18738
+ amount: {
18739
+ type: "number",
18740
+ description: "Amount in USDC minor units (from confirmWith.params)",
18741
+ required: true
18742
+ }
18743
+ },
18744
+ execute: (c, a) => confirmDonateToPot(
18745
+ c,
18746
+ a
18747
+ )
18748
+ },
17697
18749
  {
17698
18750
  name: "dim_request_rematch",
17699
18751
  description: "Request a rematch after a completed game. If both players request, a lobby is created automatically server-side.",
@@ -18087,11 +19139,18 @@ async function executeWithAuthRetry(client, tool, params) {
18087
19139
  if (tool.name === "dim_login") {
18088
19140
  return tool.execute(client, params);
18089
19141
  }
19142
+ if (client.externalSignerMode) {
19143
+ return tool.execute(client, params);
19144
+ }
18090
19145
  try {
18091
19146
  const result = await tool.execute(client, params);
18092
19147
  if (!result.error && !result.isError) return result;
18093
19148
  if (!isUnauthorizedResult(result)) return result;
18094
- await client.authenticate();
19149
+ try {
19150
+ await client.authenticate();
19151
+ } catch {
19152
+ return { error: UNAUTHORIZED_MESSAGE, isError: true };
19153
+ }
18095
19154
  const retryResult = await tool.execute(client, params);
18096
19155
  if (retryResult.isError || retryResult.error) {
18097
19156
  if (isUnauthorizedResult(retryResult)) {
@@ -18294,6 +19353,10 @@ function registerResources(server2, client) {
18294
19353
  }
18295
19354
 
18296
19355
  // src/server.ts
19356
+ var HOSTED_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
19357
+ "dim_game_loop",
19358
+ "dim_get_pending_events"
19359
+ ]);
18297
19360
  function paramToZod(p) {
18298
19361
  let schema;
18299
19362
  if (p.enum) {
@@ -18314,14 +19377,14 @@ function paramToZod(p) {
18314
19377
  if (!p.required) schema = schema.optional();
18315
19378
  return schema.describe(p.description);
18316
19379
  }
18317
- function createDimMcpServer(config) {
19380
+ function buildMcpServer(client, mode) {
18318
19381
  const server2 = new McpServer({
18319
19382
  name: "dim",
18320
19383
  version: "1.0.0",
18321
19384
  description: "DIM Gaming Platform \u2014 Play games, chat, send USDC, challenge users, and earn referral income. DIM is noncustodial; your Solana address is returned by dim_get_profile and dim_get_balance. Start by calling dim_login. After login: set a username with dim_set_username, check balance with dim_get_balance, list games with dim_list_games, or apply a referral code with dim_apply_referral_code for a 10% fee discount."
18322
19385
  });
18323
- const client = new DimAgentClient(config);
18324
19386
  for (const tool of TOOL_DEFINITIONS) {
19387
+ if (mode === "hosted" && HOSTED_EXCLUDED_TOOLS.has(tool.name)) continue;
18325
19388
  const zodParams = {};
18326
19389
  for (const [key, param] of Object.entries(tool.params)) {
18327
19390
  zodParams[key] = paramToZod(param);
@@ -18351,6 +19414,7 @@ function createDimMcpServer(config) {
18351
19414
  }
18352
19415
  );
18353
19416
  }
19417
+ const gameHint = mode === "hosted" ? "Game flow: dim_create_lobby \u2192 dim_deposit_for_lobby (returns unsignedTx \u2014 sign with Phantom MCP) \u2192 dim_join_queue \u2192 poll dim_get_lobby until matched \u2192 dim_get_game_state \u2192 dim_submit_action \u2192 repeat. For real-time game_loop, use the DIM CLI (npx @dimcool/cli)." : "Game flow: For paid lobbies, dim_create_lobby \u2192 dim_deposit_for_lobby \u2192 dim_join_queue. For free lobbies, dim_create_lobby \u2192 dim_join_queue. Then dim_game_loop (blocks until your turn) \u2192 dim_submit_action \u2192 dim_game_loop \u2192 repeat. Use dim_leave_lobby to exit a lobby.";
18354
19418
  server2.tool(
18355
19419
  "dim_list_instructions",
18356
19420
  "List all available DIM tools with short descriptions.",
@@ -18364,7 +19428,7 @@ function createDimMcpServer(config) {
18364
19428
  wallet_linkage: WALLET_LINKAGE_NOTE,
18365
19429
  wallet_address: "Your DIM Solana address is returned by dim_get_profile, dim_get_balance, dim_get_wallet_activity, and dim_get_wallet_address. Use it to confirm which address is tied to DIM.",
18366
19430
  wallet_activity: "For deposits, transfers, payouts, and claimable items use dim_get_wallet_activity.",
18367
- hint: "Game flow: For paid lobbies, dim_create_lobby \u2192 dim_deposit_for_lobby \u2192 dim_join_queue. For free lobbies, dim_create_lobby \u2192 dim_join_queue. Then dim_game_loop (blocks until your turn) \u2192 dim_submit_action \u2192 dim_game_loop \u2192 repeat. Use dim_leave_lobby to exit a lobby.",
19431
+ hint: gameHint,
18368
19432
  spectate_on_game_start: "When a game starts (dim_join_queue or dim_accept_challenge returns matched/gameId), tell the user they can spectate at the spectateUrl in the tool response, or at https://dim.cool/spectate/[your DIM username].",
18369
19433
  daily_summary: "At least once per day, report to the operator your DIM progress: call dim_get_game_history (and optionally dim_get_my_stats and dim_get_referral_summary), then summarize games played and referral earnings.",
18370
19434
  move_formats: {
@@ -18385,6 +19449,15 @@ function createDimMcpServer(config) {
18385
19449
  })
18386
19450
  );
18387
19451
  registerResources(server2, client);
19452
+ return server2;
19453
+ }
19454
+ function createDimMcpServer(clientOrConfig, options) {
19455
+ const mode = options?.mode ?? "local";
19456
+ if (clientOrConfig instanceof DimAgentClient) {
19457
+ return buildMcpServer(clientOrConfig, mode);
19458
+ }
19459
+ const client = new DimAgentClient(clientOrConfig);
19460
+ const server2 = buildMcpServer(client, mode);
18388
19461
  return { server: server2, client };
18389
19462
  }
18390
19463
 
@@ -18517,12 +19590,11 @@ if (cliArgs[0] === "init-wallet") {
18517
19590
  var walletPrivateKey = await resolveWalletPrivateKey(cliArgs);
18518
19591
  if (!walletPrivateKey) {
18519
19592
  console.error(
18520
- "Error: DIM wallet key is not configured.\nProvide DIM_WALLET_PRIVATE_KEY, or initialize a local wallet store.\n\nExamples:\n DIM_WALLET_PRIVATE_KEY=your-base58-key npx @dimcool/mcp\n npx @dimcool/mcp init-wallet\n DIM_WALLET_AUTO_CREATE=true npx @dimcool/mcp"
19593
+ "No DIM wallet key configured \u2014 starting in external signer mode.\nUse dim_request_auth_message \u2192 sign_solana_message \u2192 dim_complete_login to authenticate.\n\nTo use a dedicated wallet instead:\n DIM_WALLET_PRIVATE_KEY=your-base58-key npx @dimcool/mcp\n npx @dimcool/mcp init-wallet\n DIM_WALLET_AUTO_CREATE=true npx @dimcool/mcp"
18521
19594
  );
18522
- process.exit(1);
18523
19595
  }
18524
19596
  var { server } = createDimMcpServer({
18525
- walletPrivateKey,
19597
+ walletPrivateKey: walletPrivateKey ?? void 0,
18526
19598
  apiUrl: process.env.DIM_API_URL || "https://api.dim.cool",
18527
19599
  referralCode: process.env.DIM_REFERRAL_CODE
18528
19600
  });