@dimcool/dimclaw 0.1.29 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +940 -119
  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();
@@ -33075,11 +33089,25 @@ var Users = class {
33075
33089
  async removeFriend(userId) {
33076
33090
  return this.http.delete(`/friends/${userId}`);
33077
33091
  }
33078
- async getIncomingFriendRequests() {
33079
- return this.http.get("/friends/requests/incoming");
33092
+ async getIncomingFriendRequests(opts) {
33093
+ const params = new URLSearchParams();
33094
+ if (opts?.limit !== void 0)
33095
+ params.append("limit", opts.limit.toString());
33096
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
33097
+ const qs = params.toString();
33098
+ return this.http.get(
33099
+ qs ? `/friends/requests/incoming?${qs}` : "/friends/requests/incoming"
33100
+ );
33080
33101
  }
33081
- async getOutgoingFriendRequests() {
33082
- return this.http.get("/friends/requests/outgoing");
33102
+ async getOutgoingFriendRequests(opts) {
33103
+ const params = new URLSearchParams();
33104
+ if (opts?.limit !== void 0)
33105
+ params.append("limit", opts.limit.toString());
33106
+ if (opts?.cursor !== void 0) params.append("cursor", opts.cursor);
33107
+ const qs = params.toString();
33108
+ return this.http.get(
33109
+ qs ? `/friends/requests/outgoing?${qs}` : "/friends/requests/outgoing"
33110
+ );
33083
33111
  }
33084
33112
  async acceptFriendRequest(userId) {
33085
33113
  return this.http.post(
@@ -33166,6 +33194,9 @@ var FeatureFlags = class {
33166
33194
  this.loaded = true;
33167
33195
  return this.flags;
33168
33196
  }
33197
+ async getAdminFeatureFlags() {
33198
+ return this.http.get("/feature-flags/admin");
33199
+ }
33169
33200
  isEnabledFlag(name) {
33170
33201
  const flag = this.flags.find((f) => f.name === name);
33171
33202
  return flag?.enabled ?? false;
@@ -33185,10 +33216,11 @@ var FeatureFlags = class {
33185
33216
  }
33186
33217
  return flag;
33187
33218
  }
33188
- async createFeatureFlag(name, enabled) {
33219
+ async createFeatureFlag(name, enabled, description) {
33189
33220
  const flag = await this.http.post("/feature-flags", {
33190
33221
  name,
33191
- enabled
33222
+ enabled,
33223
+ ...description !== void 0 && { description }
33192
33224
  });
33193
33225
  this.flags.push(flag);
33194
33226
  return flag;
@@ -33199,11 +33231,17 @@ var Lobbies = class {
33199
33231
  this.http = http2;
33200
33232
  this.logger = logger2;
33201
33233
  }
33234
+ /** Called by SDK after the store is created so mutations can update state directly. */
33235
+ setLobbyStore(store) {
33236
+ this.lobbyStore = store;
33237
+ }
33202
33238
  async createLobby(gameType, betAmount) {
33203
33239
  return this.http.post("/lobbies", { gameType, betAmount });
33204
33240
  }
33205
33241
  async getLobby(lobbyId) {
33206
- return this.http.get(`/lobbies/${lobbyId}`);
33242
+ const lobby = await this.http.get(`/lobbies/${lobbyId}`);
33243
+ this.lobbyStore?.setBaseState([lobby]);
33244
+ return lobby;
33207
33245
  }
33208
33246
  async inviteFriend(lobbyId, friendId) {
33209
33247
  return this.http.post(`/lobbies/${lobbyId}/invite`, {
@@ -33214,7 +33252,9 @@ var Lobbies = class {
33214
33252
  return this.http.post(`/lobbies/${lobbyId}/accept-invite`, {});
33215
33253
  }
33216
33254
  async joinLobby(lobbyId) {
33217
- return this.http.post(`/lobbies/${lobbyId}/join`, {});
33255
+ const lobby = await this.http.post(`/lobbies/${lobbyId}/join`, {});
33256
+ this.lobbyStore?.setBaseState([lobby]);
33257
+ return lobby;
33218
33258
  }
33219
33259
  async removePlayer(lobbyId, userId) {
33220
33260
  return this.http.delete(
@@ -33231,12 +33271,17 @@ var Lobbies = class {
33231
33271
  return this.http.post(`/lobbies/${lobbyId}/join-queue`, {});
33232
33272
  }
33233
33273
  async cancelQueue(lobbyId) {
33234
- return this.http.delete(`/lobbies/${lobbyId}/queue`);
33274
+ const lobby = await this.http.delete(`/lobbies/${lobbyId}/queue`);
33275
+ this.lobbyStore?.setBaseState([lobby]);
33276
+ return lobby;
33235
33277
  }
33236
33278
  async updateBetAmount(lobbyId, betAmount) {
33237
- return this.http.patch(`/lobbies/${lobbyId}/bet-amount`, {
33238
- betAmount
33239
- });
33279
+ const lobby = await this.http.patch(
33280
+ `/lobbies/${lobbyId}/bet-amount`,
33281
+ { betAmount }
33282
+ );
33283
+ this.lobbyStore?.setBaseState([lobby]);
33284
+ return lobby;
33240
33285
  }
33241
33286
  async playSound(lobbyId, sound) {
33242
33287
  return this.http.post(`/lobbies/${lobbyId}/sound`, {
@@ -33275,6 +33320,12 @@ var Games = class {
33275
33320
  this.wallet = wallet;
33276
33321
  this.logger = logger2;
33277
33322
  }
33323
+ setGameStore(store) {
33324
+ this.gameStore = store;
33325
+ }
33326
+ setGameActionsStore(store) {
33327
+ this.gameActionsStore = store;
33328
+ }
33278
33329
  async getAvailableGames() {
33279
33330
  return this.http.get("/games/available");
33280
33331
  }
@@ -33287,7 +33338,11 @@ var Games = class {
33287
33338
  return this.http.get("/games/metrics");
33288
33339
  }
33289
33340
  async getGame(gameId) {
33290
- return this.http.get(`/games/${gameId}`);
33341
+ const existing = this.gameStore?.store.getState().gamesById[gameId];
33342
+ if (existing?.status === "completed") return existing;
33343
+ const game = await this.http.get(`/games/${gameId}`);
33344
+ this.gameStore?.setBaseState([game]);
33345
+ return game;
33291
33346
  }
33292
33347
  /**
33293
33348
  * Get list of currently active (live) games. Public endpoint for spectating.
@@ -33314,7 +33369,13 @@ var Games = class {
33314
33369
  * Get current game state with timer information
33315
33370
  */
33316
33371
  async getGameState(gameId) {
33317
- return this.http.get(`/games/${gameId}/state`);
33372
+ const existing = this.gameActionsStore?.store.getState().statesByGameId[gameId];
33373
+ if (existing?.status === "completed") return existing;
33374
+ const state = await this.http.get(
33375
+ `/games/${gameId}/state`
33376
+ );
33377
+ this.gameActionsStore?.setBaseState(gameId, state);
33378
+ return state;
33318
33379
  }
33319
33380
  /**
33320
33381
  * Request a rematch for a completed game
@@ -33350,8 +33411,18 @@ var Games = class {
33350
33411
  { amountMinor, signedTransaction }
33351
33412
  );
33352
33413
  }
33414
+ /**
33415
+ * Confirm a donation that was already sent by the client (signAndSendTransaction path).
33416
+ */
33417
+ async confirmGameDonationSignature(gameId, amountMinor, signature) {
33418
+ return this.http.post(
33419
+ `/games/${gameId}/donate/confirm-signature`,
33420
+ { amountMinor, signature }
33421
+ );
33422
+ }
33353
33423
  /**
33354
33424
  * One-call donation flow: prepare -> sign -> submit.
33425
+ * Automatically uses the right signing path (embedded vs injected wallet).
33355
33426
  */
33356
33427
  async sendDonation(gameId, amountMinor) {
33357
33428
  if (!this.wallet.hasSigner()) {
@@ -33360,16 +33431,10 @@ var Games = class {
33360
33431
  );
33361
33432
  }
33362
33433
  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
- );
33434
+ const result = await this.wallet.signAndDispatch(prepared.transaction, {
33435
+ onSigned: (signedTxBase64) => this.donateToGame(gameId, amountMinor, signedTxBase64),
33436
+ onSignedAndSent: (sig) => this.confirmGameDonationSignature(gameId, amountMinor, sig)
33437
+ });
33373
33438
  return {
33374
33439
  ...result,
33375
33440
  escrowAddress: prepared.escrowAddress,
@@ -33462,6 +33527,9 @@ var Chat = class {
33462
33527
  this.logger = logger2;
33463
33528
  this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...retryOptions };
33464
33529
  }
33530
+ setDmThreadsStore(store) {
33531
+ this.dmThreadsStore = store;
33532
+ }
33465
33533
  encodeContextId(id) {
33466
33534
  return encodeURIComponent(id);
33467
33535
  }
@@ -33543,7 +33611,9 @@ var Chat = class {
33543
33611
  return response;
33544
33612
  }
33545
33613
  async listDmThreads() {
33546
- return this.http.get("/chat/dm/threads");
33614
+ const threads = await this.http.get("/chat/dm/threads");
33615
+ this.dmThreadsStore?.setBaseState(threads);
33616
+ return threads;
33547
33617
  }
33548
33618
  async getDmThread(dmKey) {
33549
33619
  return this.http.get(
@@ -33631,21 +33701,32 @@ var Tips = class {
33631
33701
  );
33632
33702
  }
33633
33703
  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
- );
33704
+ const supportsPresign = !this.wallet.isSignAndSendMode();
33705
+ const prepared = await this.prepare({
33706
+ recipientUsername,
33707
+ amount,
33708
+ supportsPresign
33709
+ });
33710
+ const transfer = await this.wallet.signAndDispatch(prepared.transaction, {
33711
+ onSigned: (signedTxBase64) => this.wallet.submitTransfer(
33712
+ signedTxBase64,
33713
+ senderAddress,
33714
+ prepared.recipientAddress,
33715
+ prepared.amount,
33716
+ "USDC",
33717
+ false,
33718
+ prepared.recipientUsername
33719
+ ),
33720
+ onSignedAndSent: (sig) => this.wallet.confirmTransferSignature(
33721
+ sig,
33722
+ senderAddress,
33723
+ prepared.recipientAddress,
33724
+ prepared.amount,
33725
+ "USDC",
33726
+ false,
33727
+ prepared.recipientUsername
33728
+ )
33729
+ });
33649
33730
  const message = await this.chat.broadcastGlobalTip(
33650
33731
  prepared.recipientUserId,
33651
33732
  prepared.amount
@@ -33963,13 +34044,15 @@ var Wallet = class {
33963
34044
  );
33964
34045
  }
33965
34046
  try {
34047
+ const supportsPresign = !this.isSignAndSendMode();
33966
34048
  const response = await this.http.post(
33967
34049
  "/wallets/transfer/prepare",
33968
34050
  {
33969
34051
  senderAddress,
33970
34052
  recipient,
33971
34053
  amount: amountMinor,
33972
- token
34054
+ token,
34055
+ supportsPresign
33973
34056
  }
33974
34057
  );
33975
34058
  this.logger.debug("Transfer prepared", {
@@ -34053,6 +34136,29 @@ var Wallet = class {
34053
34136
  throw error;
34054
34137
  }
34055
34138
  }
34139
+ /**
34140
+ * Sign a prepared transaction and invoke the appropriate backend callback
34141
+ * based on the configured signer — without exposing the mode decision to callers.
34142
+ * - Embedded wallet (signAndSend): signs+sends, calls onSignedAndSent(signature)
34143
+ * - Injected wallet (signTransaction): signs locally, calls onSigned(signedTxBase64)
34144
+ */
34145
+ async signAndDispatch(unsignedTxBase64, handlers) {
34146
+ if (!this.signer) {
34147
+ throw new Error(
34148
+ "No signer configured. Call setSigner() with a WalletSigner implementation first."
34149
+ );
34150
+ }
34151
+ const unsignedTx = Transaction.from(base64ToBytes(unsignedTxBase64));
34152
+ if (this.isSignAndSendMode()) {
34153
+ const sig = await this.signAndSendTransaction(unsignedTx);
34154
+ return handlers.onSignedAndSent(sig);
34155
+ }
34156
+ const signedTx = await this.signTransaction(unsignedTx);
34157
+ const signedBase64 = bytesToBase64(
34158
+ signedTx.serialize({ requireAllSignatures: false })
34159
+ );
34160
+ return handlers.onSigned(signedBase64);
34161
+ }
34056
34162
  /**
34057
34163
  * Full transfer flow in one call: prepare -> sign -> submit
34058
34164
  * Recipient can be username, .sol domain, or Solana address (resolved by backend).
@@ -34134,12 +34240,32 @@ var Escrow = class {
34134
34240
  * initializes depositStatus, and prepares the unsigned transaction in one call.
34135
34241
  * Eliminates one HTTP round-trip vs startDeposits() + prepareDepositTransaction().
34136
34242
  */
34137
- async prepareAndStartDeposit(lobbyId) {
34243
+ async prepareAndStartDeposit(lobbyId, supportsPresign) {
34244
+ const presign = supportsPresign ?? !this.wallet.isSignAndSendMode();
34138
34245
  return this.http.post(
34139
34246
  `/escrow/lobby/${lobbyId}/deposit/prepare-and-start`,
34140
- {}
34247
+ { supportsPresign: presign }
34141
34248
  );
34142
34249
  }
34250
+ /**
34251
+ * Sign and submit a prepared (unsigned) deposit transaction using the
34252
+ * configured signer. Automatically picks the right path:
34253
+ * - Embedded wallet: signAndSendTransaction → confirmDepositSignature
34254
+ * - Injected wallet: signTransaction → submitDeposit
34255
+ * Returns the on-chain signature.
34256
+ */
34257
+ async signAndSubmitPreparedDeposit(lobbyId, unsignedTxBase64) {
34258
+ return this.wallet.signAndDispatch(unsignedTxBase64, {
34259
+ onSigned: async (signedTxBase64) => {
34260
+ const result = await this.submitDeposit(lobbyId, signedTxBase64);
34261
+ return result.signature;
34262
+ },
34263
+ onSignedAndSent: async (signature) => {
34264
+ await this.confirmDepositSignature(lobbyId, signature);
34265
+ return signature;
34266
+ }
34267
+ });
34268
+ }
34143
34269
  /**
34144
34270
  * Submit a signed deposit transaction
34145
34271
  * The transaction will be submitted to the Solana network and confirmed
@@ -34182,7 +34308,11 @@ var Escrow = class {
34182
34308
  "No signer configured. Use sdk.wallet.setSigner(...) first."
34183
34309
  );
34184
34310
  }
34185
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
34311
+ const supportsPresign = !this.wallet.isSignAndSendMode();
34312
+ const { transaction } = await this.prepareAndStartDeposit(
34313
+ lobbyId,
34314
+ supportsPresign
34315
+ );
34186
34316
  const unsignedTx = Transaction.from(base64ToBytes(transaction));
34187
34317
  let signature;
34188
34318
  if (this.wallet.isSignAndSendMode()) {
@@ -34217,8 +34347,13 @@ var Escrow = class {
34217
34347
  };
34218
34348
  }
34219
34349
  /**
34220
- * Zero-polling deposit variant using server-side awaitConfirmation.
34221
- * 2 HTTP calls total, 0 client-side polling. Ideal for agents.
34350
+ * Deposit for a lobby and wait until the calling user's own deposit is confirmed.
34351
+ * Ideal for agents: returns as soon as your deposit is on-chain without waiting
34352
+ * for the other player. The server auto-joins the queue when all players deposit.
34353
+ *
34354
+ * Confirmation is handled asynchronously by the transaction queue processor.
34355
+ * This method polls the deposit status endpoint until the signature appears as
34356
+ * confirmed (up to 60 seconds).
34222
34357
  *
34223
34358
  * Automatically uses signAndSendTransaction or signTransaction based on signer capability.
34224
34359
  */
@@ -34228,26 +34363,45 @@ var Escrow = class {
34228
34363
  "No signer configured. Use sdk.wallet.setSigner(...) first."
34229
34364
  );
34230
34365
  }
34231
- const { transaction } = await this.prepareAndStartDeposit(lobbyId);
34366
+ const supportsPresign2 = !this.wallet.isSignAndSendMode();
34367
+ const { transaction } = await this.prepareAndStartDeposit(
34368
+ lobbyId,
34369
+ supportsPresign2
34370
+ );
34232
34371
  const unsignedTx = Transaction.from(base64ToBytes(transaction));
34233
34372
  let signature;
34234
34373
  if (this.wallet.isSignAndSendMode()) {
34235
34374
  signature = await this.wallet.signAndSendTransaction(unsignedTx);
34236
- await this.http.post(
34237
- `/escrow/lobby/${lobbyId}/deposit/confirm-signature?awaitConfirmation=true`,
34238
- { signature }
34239
- );
34375
+ await this.confirmDepositSignature(lobbyId, signature);
34240
34376
  } else {
34241
34377
  const signedTx = await this.wallet.signTransaction(unsignedTx);
34242
34378
  const signedBase64 = bytesToBase64(
34243
34379
  signedTx.serialize({ requireAllSignatures: false })
34244
34380
  );
34245
- const result = await this.http.post(`/escrow/lobby/${lobbyId}/deposit/submit?awaitConfirmation=true`, {
34246
- signedTransaction: signedBase64
34247
- });
34248
- signature = result.signature;
34381
+ const submitResult = await this.submitDeposit(lobbyId, signedBase64);
34382
+ signature = submitResult.signature;
34383
+ }
34384
+ const MAX_WAIT_MS = 6e4;
34385
+ const POLL_INTERVAL_MS = 1e3;
34386
+ const startTime = Date.now();
34387
+ while (Date.now() - startTime < MAX_WAIT_MS) {
34388
+ const status = await this.getDepositStatus(lobbyId);
34389
+ const myDeposit = status.deposits.find(
34390
+ (d) => d.transactionHash === signature
34391
+ );
34392
+ if (myDeposit?.status === "confirmed") {
34393
+ return {
34394
+ signature,
34395
+ status: "confirmed",
34396
+ canProceedToQueue: status.canProceedToQueue
34397
+ };
34398
+ }
34399
+ if (status.allConfirmed && status.canProceedToQueue) {
34400
+ return { signature, status: "confirmed", canProceedToQueue: true };
34401
+ }
34402
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
34249
34403
  }
34250
- return { signature, status: "confirmed", canProceedToQueue: true };
34404
+ return { signature, status: "pending", canProceedToQueue: false };
34251
34405
  }
34252
34406
  async claimLobbyDepositRefund(lobbyId, depositSignature) {
34253
34407
  return this.http.post(
@@ -34589,9 +34743,10 @@ var Support = class {
34589
34743
  }
34590
34744
  };
34591
34745
  var Markets = class {
34592
- constructor(http2, logger2) {
34746
+ constructor(http2, logger2, wallet) {
34593
34747
  this.http = http2;
34594
34748
  this.logger = logger2;
34749
+ this.wallet = wallet;
34595
34750
  }
34596
34751
  /**
34597
34752
  * Get the prediction market state for a game.
@@ -34633,6 +34788,35 @@ var Markets = class {
34633
34788
  { signedTransaction, outcomeId, amountMinor }
34634
34789
  );
34635
34790
  }
34791
+ /**
34792
+ * Confirm a buy order that was already broadcast by the client (signAndSendTransaction path).
34793
+ */
34794
+ async confirmBuyOrderSignature(gameId, depositSignature, outcomeId, amountMinor) {
34795
+ return this.http.post(
34796
+ `/games/${gameId}/market/orders/buy/confirm-signature`,
34797
+ { depositSignature, outcomeId, amountMinor }
34798
+ );
34799
+ }
34800
+ /**
34801
+ * One-call buy order: prepare → sign → submit, automatically choosing
34802
+ * the right signing path (embedded vs injected wallet).
34803
+ */
34804
+ async buy(gameId, outcomeId, amountMinor) {
34805
+ if (!this.wallet?.hasSigner()) {
34806
+ throw new Error(
34807
+ "No signer configured. Use sdk.wallet.setSigner(...) first."
34808
+ );
34809
+ }
34810
+ const { transaction } = await this.prepareBuyOrder(
34811
+ gameId,
34812
+ outcomeId,
34813
+ amountMinor
34814
+ );
34815
+ return this.wallet.signAndDispatch(transaction, {
34816
+ onSigned: (signedTxBase64) => this.submitBuyOrder(gameId, signedTxBase64, outcomeId, amountMinor),
34817
+ onSignedAndSent: (sig) => this.confirmBuyOrderSignature(gameId, sig, outcomeId, amountMinor)
34818
+ });
34819
+ }
34636
34820
  /**
34637
34821
  * Sell shares back to the AMM pool.
34638
34822
  * @param gameId - The game ID
@@ -34681,6 +34865,20 @@ var Markets = class {
34681
34865
  return this.http.get(`/games/admin/markets${qs ? `?${qs}` : ""}`);
34682
34866
  }
34683
34867
  };
34868
+ var NoopAnalyticsClient = class {
34869
+ userLoggedIn(_user, _meta) {
34870
+ }
34871
+ userLoggedOut() {
34872
+ }
34873
+ sessionRestored(_user) {
34874
+ }
34875
+ track(_event, _properties) {
34876
+ }
34877
+ setUserProperties(_properties) {
34878
+ }
34879
+ group(_groupType, _groupKey, _properties) {
34880
+ }
34881
+ };
34684
34882
  var BaseWsTransport = class {
34685
34883
  constructor() {
34686
34884
  this.roomRefs = /* @__PURE__ */ new Map();
@@ -34768,21 +34966,35 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
34768
34966
  this.accessToken = null;
34769
34967
  this.reconnectAttempts = 0;
34770
34968
  this.reconnectTimer = null;
34771
- this.periodicReconnectInterval = null;
34772
- this.maxReconnectAttempts = 5;
34773
- this.reconnectDelay = 1e3;
34774
- this.reconnectDelayMax = 3e4;
34969
+ this.needsReconnect = false;
34970
+ this.visibilityHandler = null;
34775
34971
  this.wildcardHandlers = /* @__PURE__ */ new Set();
34776
34972
  this.eventHandlers = /* @__PURE__ */ new Map();
34777
34973
  this.registeredEvents = /* @__PURE__ */ new Set();
34778
34974
  this.wildcardRegistered = false;
34779
34975
  this.url = url3;
34976
+ if (typeof document !== "undefined") {
34977
+ this.visibilityHandler = () => {
34978
+ if (document.visibilityState === "visible" && !this.connectionState.connected) {
34979
+ this.reconnectImmediately();
34980
+ }
34981
+ };
34982
+ document.addEventListener("visibilitychange", this.visibilityHandler);
34983
+ }
34780
34984
  }
34781
34985
  connect() {
34986
+ if (this.reconnectTimer !== null) {
34987
+ clearTimeout(this.reconnectTimer);
34988
+ this.reconnectTimer = null;
34989
+ }
34990
+ this.reconnectAttempts = 0;
34782
34991
  this.ensureSocket();
34783
34992
  }
34784
34993
  disconnect() {
34785
- this.clearPeriodicReconnect();
34994
+ if (this.visibilityHandler && typeof document !== "undefined") {
34995
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
34996
+ this.visibilityHandler = null;
34997
+ }
34786
34998
  if (!this.socket) return;
34787
34999
  this.socket.disconnect();
34788
35000
  this.socket = null;
@@ -34881,13 +35093,13 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
34881
35093
  reconnection: false
34882
35094
  });
34883
35095
  socket.on("connect", () => {
34884
- this.clearPeriodicReconnect();
34885
35096
  this.updateConnectionState({
34886
35097
  connected: true,
34887
35098
  connecting: false,
34888
35099
  error: null
34889
35100
  });
34890
- const wasReconnect = this.reconnectAttempts > 0;
35101
+ const wasReconnect = this.needsReconnect;
35102
+ this.needsReconnect = false;
34891
35103
  this.reconnectAttempts = 0;
34892
35104
  this.onReconnect();
34893
35105
  if (wasReconnect) {
@@ -34898,6 +35110,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
34898
35110
  });
34899
35111
  socket.on("disconnect", (reason) => {
34900
35112
  this.updateConnectionState({ connected: false, connecting: false });
35113
+ this.needsReconnect = true;
34901
35114
  this.clearSocketForReconnect();
34902
35115
  if (reason === "io server disconnect") {
34903
35116
  this.handleAuthFailure("io server disconnect");
@@ -34907,6 +35120,7 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
34907
35120
  });
34908
35121
  socket.on("connect_error", (error) => {
34909
35122
  const message = error?.message || "connect_error";
35123
+ this.needsReconnect = true;
34910
35124
  this.clearSocketForReconnect();
34911
35125
  if (message.includes("unauthorized") || message.includes("401")) {
34912
35126
  this.handleAuthFailure(message);
@@ -34934,31 +35148,31 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
34934
35148
  this.socket.removeAllListeners();
34935
35149
  this.socket = null;
34936
35150
  }
34937
- clearPeriodicReconnect() {
34938
- if (this.periodicReconnectInterval !== null) {
34939
- clearInterval(this.periodicReconnectInterval);
34940
- this.periodicReconnectInterval = null;
35151
+ reconnectImmediately() {
35152
+ if (this.reconnectTimer !== null) {
35153
+ clearTimeout(this.reconnectTimer);
35154
+ this.reconnectTimer = null;
34941
35155
  }
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);
35156
+ this.reconnectAttempts = 0;
35157
+ this.ensureSocket();
34950
35158
  }
34951
35159
  scheduleReconnect() {
34952
35160
  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
- );
35161
+ const attempt = this.reconnectAttempts;
34961
35162
  this.reconnectAttempts += 1;
35163
+ let delay;
35164
+ if (attempt < _StandaloneWsTransport2.FAST_RETRY_LIMIT) {
35165
+ delay = _StandaloneWsTransport2.FAST_RETRY_DELAY_MS;
35166
+ } else {
35167
+ const backoffAttempt = attempt - _StandaloneWsTransport2.FAST_RETRY_LIMIT;
35168
+ delay = Math.min(
35169
+ _StandaloneWsTransport2.FAST_RETRY_DELAY_MS * Math.pow(2, backoffAttempt),
35170
+ _StandaloneWsTransport2.MAX_BACKOFF_MS
35171
+ );
35172
+ if (attempt === _StandaloneWsTransport2.FAST_RETRY_LIMIT) {
35173
+ this.dispatchEvent("connection:slow-retry", { timestamp: Date.now() });
35174
+ }
35175
+ }
34962
35176
  this.reconnectTimer = setTimeout(() => {
34963
35177
  this.reconnectTimer = null;
34964
35178
  this.ensureSocket();
@@ -35019,7 +35233,9 @@ var _StandaloneWsTransport = class _StandaloneWsTransport2 extends BaseWsTranspo
35019
35233
  this.wildcardRegistered = true;
35020
35234
  }
35021
35235
  };
35022
- _StandaloneWsTransport.PERIODIC_RECONNECT_MS = 5 * 60 * 1e3;
35236
+ _StandaloneWsTransport.FAST_RETRY_LIMIT = 60;
35237
+ _StandaloneWsTransport.FAST_RETRY_DELAY_MS = 1e3;
35238
+ _StandaloneWsTransport.MAX_BACKOFF_MS = 3e4;
35023
35239
  var StandaloneWsTransport = _StandaloneWsTransport;
35024
35240
  function createSdkStore(initial) {
35025
35241
  const store = createStore()(subscribeWithSelector(() => initial));
@@ -35040,14 +35256,16 @@ function createLobbyStore(transport) {
35040
35256
  depositStatusByLobbyId: {}
35041
35257
  });
35042
35258
  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
- }));
35259
+ store.updateState((state) => {
35260
+ const lobbiesById = { ...state.lobbiesById };
35261
+ for (const lobby of lobbies) {
35262
+ const existing = lobbiesById[lobby.id];
35263
+ if (!existing || lobby.updatedAt >= existing.updatedAt) {
35264
+ lobbiesById[lobby.id] = lobby;
35265
+ }
35266
+ }
35267
+ return { ...state, lobbiesById };
35268
+ });
35051
35269
  };
35052
35270
  const applyWsEvent = (event) => {
35053
35271
  switch (event.event) {
@@ -35177,12 +35395,23 @@ function createGameStore(transport) {
35177
35395
  gamesById: {},
35178
35396
  spectatorCounts: {}
35179
35397
  });
35398
+ const STATUS_RANK = {
35399
+ active: 0,
35400
+ completed: 1,
35401
+ abandoned: 1
35402
+ };
35180
35403
  const setBaseState = (games) => {
35181
- const gamesById = {};
35182
- for (const game of games) {
35183
- gamesById[game.gameId] = game;
35184
- }
35185
- store.updateState((state) => ({ ...state, gamesById }));
35404
+ store.updateState((state) => {
35405
+ const gamesById = { ...state.gamesById };
35406
+ for (const game of games) {
35407
+ const existing = gamesById[game.gameId];
35408
+ if (existing && STATUS_RANK[existing.status] > STATUS_RANK[game.status]) {
35409
+ continue;
35410
+ }
35411
+ gamesById[game.gameId] = game;
35412
+ }
35413
+ return { ...state, gamesById };
35414
+ });
35186
35415
  };
35187
35416
  const applyWsEvent = (event) => {
35188
35417
  switch (event.event) {
@@ -35920,7 +36149,38 @@ function createNotificationsStore() {
35920
36149
  appNotifications: []
35921
36150
  }));
35922
36151
  };
35923
- return { store, applyWsEvent, setListFromApi, clear };
36152
+ const markAsRead = (id) => {
36153
+ store.updateState((state) => ({
36154
+ ...state,
36155
+ appNotifications: state.appNotifications.map(
36156
+ (n) => n.id === id ? { ...n, read: true } : n
36157
+ )
36158
+ }));
36159
+ };
36160
+ const markAllAsRead = () => {
36161
+ store.updateState((state) => ({
36162
+ ...state,
36163
+ appNotifications: state.appNotifications.map((n) => ({
36164
+ ...n,
36165
+ read: true
36166
+ }))
36167
+ }));
36168
+ };
36169
+ const dismiss = (id) => {
36170
+ store.updateState((state) => ({
36171
+ ...state,
36172
+ appNotifications: state.appNotifications.filter((n) => n.id !== id)
36173
+ }));
36174
+ };
36175
+ return {
36176
+ store,
36177
+ applyWsEvent,
36178
+ setListFromApi,
36179
+ clear,
36180
+ markAsRead,
36181
+ markAllAsRead,
36182
+ dismiss
36183
+ };
35924
36184
  }
35925
36185
  function createFriendsStore() {
35926
36186
  const store = createSdkStore({
@@ -36267,6 +36527,7 @@ var SDK = class {
36267
36527
  constructor(config) {
36268
36528
  const baseUrl = config.baseUrl || "http://localhost:3000";
36269
36529
  const logger2 = config.logger || logger;
36530
+ this.analytics = config.analytics ?? new NoopAnalyticsClient();
36270
36531
  this.http = config.httpClient || new HttpClient(
36271
36532
  baseUrl,
36272
36533
  config.storage,
@@ -36299,14 +36560,18 @@ var SDK = class {
36299
36560
  this.referrals = new Referrals(this.http, logger2);
36300
36561
  this.reports = new Reports(this.http, logger2);
36301
36562
  this.support = new Support(this.http, logger2);
36302
- this.markets = new Markets(this.http, logger2);
36563
+ this.markets = new Markets(this.http, logger2, this.wallet);
36303
36564
  this.wsTransport = config.wsTransport || new StandaloneWsTransport(baseUrl);
36304
36565
  this.wsTransport.setAppId(config.appId);
36305
36566
  this.lobbyStore = createLobbyStore(this.wsTransport);
36567
+ this.lobbies.setLobbyStore(this.lobbyStore);
36306
36568
  this.gameStore = createGameStore(this.wsTransport);
36569
+ this.games.setGameStore(this.gameStore);
36307
36570
  this.gameActionsStore = createGameActionsStore(this.wsTransport);
36571
+ this.games.setGameActionsStore(this.gameActionsStore);
36308
36572
  this.chatStore = createChatStore(this.wsTransport);
36309
36573
  this.dmThreadsStore = createDmThreadsStore();
36574
+ this.chat.setDmThreadsStore(this.dmThreadsStore);
36310
36575
  this.notificationsStore = createNotificationsStore();
36311
36576
  this.friendsStore = createFriendsStore();
36312
36577
  this.notifications = new Notifications(
@@ -36336,6 +36601,29 @@ var SDK = class {
36336
36601
  this.wsTransport.connect();
36337
36602
  await this.wsTransport.waitUntilConnected(timeoutMs);
36338
36603
  }
36604
+ /**
36605
+ * Handle the full deposit-to-queue lifecycle for a lobby.
36606
+ *
36607
+ * After the deposit is confirmed, the backend may not have processed the
36608
+ * `lobby.deposit.allConfirmed` event yet, so the lobby can still be in
36609
+ * 'preparing' status. This method re-fetches the lobby and explicitly
36610
+ * joins the queue if needed, then pushes the latest state to the store
36611
+ * so the UI transitions immediately.
36612
+ */
36613
+ async depositAndJoinQueue(lobbyId) {
36614
+ const result = await this.escrow.depositForLobby(lobbyId);
36615
+ if (!result.canProceedToQueue) {
36616
+ const lobby2 = await this.lobbies.getLobby(lobbyId);
36617
+ this.lobbyStore.setBaseState([lobby2]);
36618
+ return { ...result, lobby: lobby2 };
36619
+ }
36620
+ let lobby = await this.lobbies.getLobby(lobbyId);
36621
+ if (lobby.status === "preparing") {
36622
+ lobby = await this.lobbies.joinQueue(lobbyId);
36623
+ }
36624
+ this.lobbyStore.setBaseState([lobby]);
36625
+ return { ...result, lobby };
36626
+ }
36339
36627
  };
36340
36628
  var import_utils14 = __toESM2(require_dist(), 1);
36341
36629
  var export_MICRO_UNITS = import_utils14.MICRO_UNITS;
@@ -36482,7 +36770,9 @@ var import_tweetnacl = __toESM(require_nacl_fast(), 1);
36482
36770
  var DimAgentClient = class {
36483
36771
  sdk;
36484
36772
  agentConfig;
36773
+ externalSignerMode;
36485
36774
  keypair;
36775
+ _externalAddress = null;
36486
36776
  config;
36487
36777
  authenticated = false;
36488
36778
  userId = null;
@@ -36496,21 +36786,26 @@ var DimAgentClient = class {
36496
36786
  constructor(config) {
36497
36787
  this.config = config;
36498
36788
  this.agentConfig = config.agentConfig;
36499
- const secretKeyBytes = esm_default4.decode(config.walletPrivateKey);
36500
- this.keypair = Keypair.fromSecretKey(secretKeyBytes);
36789
+ this.externalSignerMode = !config.walletPrivateKey;
36790
+ if (config.walletPrivateKey) {
36791
+ const secretKeyBytes = esm_default4.decode(config.walletPrivateKey);
36792
+ this.keypair = Keypair.fromSecretKey(secretKeyBytes);
36793
+ } else {
36794
+ this.keypair = null;
36795
+ }
36501
36796
  this.sdk = new SDK({
36502
36797
  appId: "dim-agents",
36503
36798
  baseUrl: config.apiUrl || "https://api.dim.cool",
36504
36799
  storage: new NodeStorage(),
36505
36800
  autoPay: {
36506
- enabled: true,
36801
+ enabled: !this.externalSignerMode,
36507
36802
  maxAmountMinor: 25e3,
36508
36803
  maxRetries: 1
36509
36804
  }
36510
36805
  });
36511
36806
  }
36512
36807
  get walletAddress() {
36513
- return this.keypair.publicKey.toBase58();
36808
+ return this.keypair?.publicKey.toBase58() ?? this._externalAddress ?? "";
36514
36809
  }
36515
36810
  get isAuthenticated() {
36516
36811
  return this.authenticated;
@@ -36519,17 +36814,23 @@ var DimAgentClient = class {
36519
36814
  return this.userId;
36520
36815
  }
36521
36816
  async authenticate() {
36817
+ if (!this.keypair) {
36818
+ throw new Error(
36819
+ "No keypair configured. Use dim_request_auth_message + dim_complete_login for external signer mode."
36820
+ );
36821
+ }
36822
+ const keypair = this.keypair;
36522
36823
  this.sdk.wallet.setSigner({
36523
36824
  address: this.walletAddress,
36524
36825
  signMessage: async (message) => {
36525
36826
  const signature = import_tweetnacl.default.sign.detached(
36526
36827
  new TextEncoder().encode(message),
36527
- this.keypair.secretKey
36828
+ keypair.secretKey
36528
36829
  );
36529
36830
  return Buffer.from(signature).toString("base64");
36530
36831
  },
36531
36832
  signTransaction: async (transaction) => {
36532
- transaction.partialSign(this.keypair);
36833
+ transaction.partialSign(keypair);
36533
36834
  return transaction;
36534
36835
  }
36535
36836
  });
@@ -36583,10 +36884,45 @@ var DimAgentClient = class {
36583
36884
  get pendingEventCount() {
36584
36885
  return this.eventQueue.length;
36585
36886
  }
36586
- /** Get the Keypair for transaction signing. */
36887
+ /** Get the Keypair for transaction signing (keypair mode only). */
36587
36888
  getKeypair() {
36889
+ if (!this.keypair) {
36890
+ throw new Error(
36891
+ "No keypair configured \u2014 running in external signer mode."
36892
+ );
36893
+ }
36588
36894
  return this.keypair;
36589
36895
  }
36896
+ /** Request the auth handshake message for a given address (external signer mode). */
36897
+ async requestAuthMessage(address) {
36898
+ return this.sdk.auth.generateHandshake(address);
36899
+ }
36900
+ /**
36901
+ * Complete authentication using an externally provided signature (external signer mode).
36902
+ * @param address - Solana wallet address (base58)
36903
+ * @param signatureBase58 - Detached ed25519 signature in base58 (from sign_solana_message)
36904
+ */
36905
+ async completeAuth(address, signatureBase58) {
36906
+ const signatureBytes = esm_default4.decode(signatureBase58);
36907
+ const signedMessage = Buffer.from(signatureBytes).toString("base64");
36908
+ const response = await this.sdk.auth.loginWithExternalSignature(
36909
+ address,
36910
+ signedMessage,
36911
+ {
36912
+ referralCode: this.config.referralCode,
36913
+ walletMeta: { type: "phantom-embedded", provider: "injected" }
36914
+ }
36915
+ );
36916
+ this.sdk.wsTransport.setAccessToken(response.access_token);
36917
+ this._externalAddress = address;
36918
+ this.authenticated = true;
36919
+ this.userId = response.user.id;
36920
+ return {
36921
+ userId: response.user.id,
36922
+ username: response.user.username,
36923
+ accessToken: response.access_token
36924
+ };
36925
+ }
36590
36926
  // ── Game chat cursors ────────────────────────────────────────────────
36591
36927
  /** Get the last-seen chat timestamp for a game. */
36592
36928
  getGameChatCursor(gameId) {
@@ -36714,6 +37050,77 @@ async function login(client) {
36714
37050
  };
36715
37051
  }
36716
37052
  }
37053
+ async function requestAuthMessage(client, args) {
37054
+ try {
37055
+ const { message } = await client.requestAuthMessage(args.address);
37056
+ return {
37057
+ data: {
37058
+ message,
37059
+ address: args.address,
37060
+ nextStep: 'Sign this message with sign_solana_message (networkId: "solana:mainnet"), then call dim_complete_login with the address and the base58 signature.'
37061
+ }
37062
+ };
37063
+ } catch (error) {
37064
+ return {
37065
+ error: `Failed to get auth message: ${error instanceof Error ? error.message : String(error)}`,
37066
+ isError: true
37067
+ };
37068
+ }
37069
+ }
37070
+ async function completeLogin(client, args) {
37071
+ try {
37072
+ const result = await client.completeAuth(args.address, args.signature);
37073
+ client.startEventListeners();
37074
+ const nextSteps = [];
37075
+ if (result.username == null || result.username === "") {
37076
+ nextSteps.push("No username set \u2014 call dim_set_username to claim one");
37077
+ }
37078
+ nextSteps.push("Check your balance with dim_get_balance");
37079
+ nextSteps.push("Explore available games with dim_list_games");
37080
+ try {
37081
+ const summary = await client.sdk.referrals.getSummary();
37082
+ if (!summary.hasReferrer) {
37083
+ nextSteps.push(
37084
+ "No referrer yet \u2014 call dim_apply_referral_code for 10% fee discount"
37085
+ );
37086
+ }
37087
+ if (result.username) {
37088
+ nextSteps.push(
37089
+ `Share your referral code "${result.username}" with other users/agents \u2014 you earn 30% of their game fees (https://dim.cool/?ref=${result.username})`
37090
+ );
37091
+ }
37092
+ } catch {
37093
+ }
37094
+ const response = {
37095
+ success: true,
37096
+ userId: result.userId,
37097
+ username: result.username ?? null,
37098
+ walletAddress: client.walletAddress,
37099
+ walletNote: "DIM is noncustodial. Signing is handled by your external wallet for this session.",
37100
+ safetyRules: SAFETY_RULES,
37101
+ nextSteps
37102
+ };
37103
+ const ac = client.agentConfig;
37104
+ const dailyLimit = ac?.dailySpendLimit ?? 20;
37105
+ response.agentConfig = {
37106
+ autoAcceptFriendRequests: ac?.autoAcceptFriendRequests ?? false,
37107
+ autoReplyDms: ac?.autoReplyDms ?? false,
37108
+ autoPlayGames: ac?.autoPlayGames ?? false,
37109
+ maxBetPerGame: ac?.maxBetPerGame ?? 1,
37110
+ dailySpendLimit: dailyLimit,
37111
+ autoJoinGlobalChat: ac?.autoJoinGlobalChat ?? false,
37112
+ autoPromoteReferrals: ac?.autoPromoteReferrals ?? false,
37113
+ dailySpentSoFar: client.dailySpentDollars,
37114
+ dailyRemaining: dailyLimit - client.dailySpentDollars
37115
+ };
37116
+ return { data: response };
37117
+ } catch (error) {
37118
+ return {
37119
+ error: `Login failed: ${error instanceof Error ? error.message : String(error)}`,
37120
+ isError: true
37121
+ };
37122
+ }
37123
+ }
36717
37124
  async function getProfile(client) {
36718
37125
  try {
36719
37126
  if (!client.currentUserId) {
@@ -36842,10 +37249,19 @@ async function listFriends(client, args) {
36842
37249
  };
36843
37250
  }
36844
37251
  }
36845
- async function getIncomingFriendRequests(client) {
37252
+ async function getIncomingFriendRequests(client, args) {
36846
37253
  try {
36847
- const result = await client.sdk.users.getIncomingFriendRequests();
36848
- return { data: result };
37254
+ const result = await client.sdk.users.getIncomingFriendRequests({
37255
+ limit: args?.limit,
37256
+ cursor: args?.cursor
37257
+ });
37258
+ return {
37259
+ data: {
37260
+ items: result.items,
37261
+ nextCursor: result.nextCursor,
37262
+ hasMore: !!result.nextCursor
37263
+ }
37264
+ };
36849
37265
  } catch (error) {
36850
37266
  return {
36851
37267
  error: `Failed to get requests: ${error instanceof Error ? error.message : String(error)}`,
@@ -36966,6 +37382,42 @@ async function sendUsdc(client, args) {
36966
37382
  const spendErr = client.checkSpendLimit(args.amount);
36967
37383
  if (spendErr) return { error: spendErr, isError: true };
36968
37384
  const amountMinor = Math.round(args.amount * 1e6);
37385
+ if (client.externalSignerMode) {
37386
+ const senderAddress = client.walletAddress;
37387
+ if (!senderAddress) {
37388
+ return {
37389
+ error: "Wallet address not set. Call dim_complete_login first.",
37390
+ isError: true
37391
+ };
37392
+ }
37393
+ const prepared = await client.sdk.wallet.prepareTransfer(
37394
+ senderAddress,
37395
+ args.recipient,
37396
+ amountMinor
37397
+ );
37398
+ return {
37399
+ data: {
37400
+ needsSigning: true,
37401
+ unsignedTx: prepared.transaction,
37402
+ fee: prepared.fee,
37403
+ totalAmount: prepared.totalAmount,
37404
+ recipientAddress: prepared.recipientAddress,
37405
+ ataCreated: prepared.ataCreated ?? false,
37406
+ confirmWith: {
37407
+ tool: "dim_confirm_send_usdc",
37408
+ params: {
37409
+ recipientAddress: prepared.recipientAddress,
37410
+ amount: amountMinor,
37411
+ fee: prepared.fee,
37412
+ token: "USDC",
37413
+ ataCreated: prepared.ataCreated ?? false,
37414
+ recipientInput: args.recipient
37415
+ }
37416
+ },
37417
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_send_usdc with the signature and the confirmWith.params."
37418
+ }
37419
+ };
37420
+ }
36969
37421
  const result = await client.sdk.wallet.send(args.recipient, amountMinor);
36970
37422
  client.recordSpend(amountMinor);
36971
37423
  return {
@@ -36986,11 +37438,74 @@ async function sendUsdc(client, args) {
36986
37438
  };
36987
37439
  }
36988
37440
  }
37441
+ async function confirmSendUsdc(client, args) {
37442
+ try {
37443
+ const senderAddress = client.walletAddress;
37444
+ const result = await client.sdk.wallet.confirmTransferSignature(
37445
+ args.signature,
37446
+ senderAddress,
37447
+ args.recipientAddress,
37448
+ args.amount,
37449
+ args.token || "USDC",
37450
+ args.ataCreated,
37451
+ args.recipientInput
37452
+ );
37453
+ client.recordSpend(args.amount);
37454
+ return {
37455
+ data: {
37456
+ success: true,
37457
+ signature: result.signature,
37458
+ status: result.status,
37459
+ recipientAddress: args.recipientAddress
37460
+ }
37461
+ };
37462
+ } catch (error) {
37463
+ return {
37464
+ error: `Failed to confirm USDC transfer: ${error instanceof Error ? error.message : String(error)}`,
37465
+ isError: true
37466
+ };
37467
+ }
37468
+ }
36989
37469
  async function tipUser(client, args) {
36990
37470
  try {
36991
37471
  const spendErr = client.checkSpendLimit(args.amount);
36992
37472
  if (spendErr) return { error: spendErr, isError: true };
36993
37473
  const amountMinor = Math.round(args.amount * 1e6);
37474
+ if (client.externalSignerMode) {
37475
+ const senderAddress = client.walletAddress;
37476
+ if (!senderAddress) {
37477
+ return {
37478
+ error: "Wallet address not set. Call dim_complete_login first.",
37479
+ isError: true
37480
+ };
37481
+ }
37482
+ const prepared = await client.sdk.tips.prepare({
37483
+ recipientUsername: args.recipientUsername,
37484
+ amount: amountMinor,
37485
+ supportsPresign: false
37486
+ });
37487
+ return {
37488
+ data: {
37489
+ needsSigning: true,
37490
+ unsignedTx: prepared.transaction,
37491
+ fee: prepared.fee,
37492
+ totalAmount: prepared.totalAmount,
37493
+ recipientAddress: prepared.recipientAddress,
37494
+ confirmWith: {
37495
+ tool: "dim_confirm_tip_user",
37496
+ params: {
37497
+ recipientAddress: prepared.recipientAddress,
37498
+ recipientUserId: prepared.recipientUserId,
37499
+ recipientUsername: prepared.recipientUsername,
37500
+ amount: prepared.amount,
37501
+ fee: prepared.fee,
37502
+ ataCreated: false
37503
+ }
37504
+ },
37505
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_tip_user with the signature and the confirmWith.params."
37506
+ }
37507
+ };
37508
+ }
36994
37509
  const result = await client.sdk.tips.send(
36995
37510
  args.recipientUsername,
36996
37511
  amountMinor
@@ -37013,6 +37528,38 @@ async function tipUser(client, args) {
37013
37528
  };
37014
37529
  }
37015
37530
  }
37531
+ async function confirmTipUser(client, args) {
37532
+ try {
37533
+ const senderAddress = client.walletAddress;
37534
+ await client.sdk.wallet.confirmTransferSignature(
37535
+ args.signature,
37536
+ senderAddress,
37537
+ args.recipientAddress,
37538
+ args.amount,
37539
+ "USDC",
37540
+ args.ataCreated ?? false,
37541
+ args.recipientUsername
37542
+ );
37543
+ await client.sdk.tips.broadcast({
37544
+ recipientUserId: args.recipientUserId,
37545
+ amount: args.amount
37546
+ });
37547
+ client.recordSpend(args.amount);
37548
+ return {
37549
+ data: {
37550
+ success: true,
37551
+ signature: args.signature,
37552
+ recipient: args.recipientUsername,
37553
+ broadcastedToGlobalChat: true
37554
+ }
37555
+ };
37556
+ } catch (error) {
37557
+ return {
37558
+ error: `Failed to confirm tip: ${error instanceof Error ? error.message : String(error)}`,
37559
+ isError: true
37560
+ };
37561
+ }
37562
+ }
37016
37563
  async function getWalletActivity(client, args) {
37017
37564
  try {
37018
37565
  const activity = await client.sdk.wallet.getActivity({
@@ -37119,6 +37666,7 @@ function buildRpsContext(state, agentUserId) {
37119
37666
  roundHistory: state.roundHistory,
37120
37667
  phase: roundState?.phase,
37121
37668
  timeRemaining: roundState?.timeRemaining,
37669
+ ...state.bufferEndsAt != null && { bufferEndsAt: state.bufferEndsAt },
37122
37670
  moveFormat: 'dim_submit_action: gameType="rock-paper-scissors", action="play", payload={ action: "rock"|"paper"|"scissors" }'
37123
37671
  };
37124
37672
  }
@@ -37131,6 +37679,7 @@ function buildChessContext(state, agentUserId) {
37131
37679
  yourColor,
37132
37680
  whiteTimeMs: state.whiteTimeMs,
37133
37681
  blackTimeMs: state.blackTimeMs,
37682
+ ...state.bufferEndsAt != null && { bufferEndsAt: state.bufferEndsAt },
37134
37683
  moveFormat: 'dim_submit_action: gameType="chess", action="move", payload={ from: "e2", to: "e4" }'
37135
37684
  };
37136
37685
  }
@@ -37340,6 +37889,23 @@ async function createLobby(client, args) {
37340
37889
  }
37341
37890
  async function depositForLobby(client, args) {
37342
37891
  try {
37892
+ if (client.externalSignerMode) {
37893
+ const { transaction } = await client.sdk.escrow.prepareAndStartDeposit(
37894
+ args.lobbyId,
37895
+ false
37896
+ );
37897
+ return {
37898
+ data: {
37899
+ needsSigning: true,
37900
+ unsignedTx: transaction,
37901
+ confirmWith: {
37902
+ tool: "dim_confirm_lobby_deposit",
37903
+ params: { lobbyId: args.lobbyId }
37904
+ },
37905
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_lobby_deposit with the lobbyId and signature."
37906
+ }
37907
+ };
37908
+ }
37343
37909
  const result = await client.sdk.escrow.depositForLobbySync(args.lobbyId);
37344
37910
  return {
37345
37911
  data: {
@@ -37356,6 +37922,44 @@ async function depositForLobby(client, args) {
37356
37922
  };
37357
37923
  }
37358
37924
  }
37925
+ async function confirmLobbyDeposit(client, args) {
37926
+ try {
37927
+ await client.sdk.escrow.confirmDepositSignature(
37928
+ args.lobbyId,
37929
+ args.signature
37930
+ );
37931
+ const MAX_WAIT_MS = 6e4;
37932
+ const POLL_INTERVAL_MS = 1e3;
37933
+ const startTime = Date.now();
37934
+ while (Date.now() - startTime < MAX_WAIT_MS) {
37935
+ const status = await client.sdk.escrow.getDepositStatus(args.lobbyId);
37936
+ if (status.canProceedToQueue) {
37937
+ return {
37938
+ data: {
37939
+ signature: args.signature,
37940
+ status: "confirmed",
37941
+ canProceedToQueue: true,
37942
+ hint: "Deposit confirmed. Call dim_join_queue to enter matchmaking."
37943
+ }
37944
+ };
37945
+ }
37946
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
37947
+ }
37948
+ return {
37949
+ data: {
37950
+ signature: args.signature,
37951
+ status: "pending",
37952
+ canProceedToQueue: false,
37953
+ hint: "Deposit submitted but not yet confirmed on-chain. Try dim_confirm_lobby_deposit again in a few seconds."
37954
+ }
37955
+ };
37956
+ } catch (error) {
37957
+ return {
37958
+ error: `Failed to confirm deposit: ${error instanceof Error ? error.message : String(error)}`,
37959
+ isError: true
37960
+ };
37961
+ }
37962
+ }
37359
37963
  async function leaveLobby(client, args) {
37360
37964
  try {
37361
37965
  const result = await client.sdk.lobbies.leaveLobby(args.lobbyId);
@@ -37425,7 +38029,7 @@ async function joinQueue(client, args) {
37425
38029
  gameId,
37426
38030
  matched: !!matched,
37427
38031
  ...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."
38032
+ 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
38033
  }
37430
38034
  };
37431
38035
  } catch (error) {
@@ -37500,6 +38104,23 @@ async function donateToPot(client, args) {
37500
38104
  const spendErr = client.checkSpendLimit(args.amount);
37501
38105
  if (spendErr) return { error: spendErr, isError: true };
37502
38106
  const amountMinor = Math.round(args.amount * 1e6);
38107
+ if (client.externalSignerMode) {
38108
+ const prepared = await client.sdk.games.prepareGameDonation(
38109
+ args.gameId,
38110
+ amountMinor
38111
+ );
38112
+ return {
38113
+ data: {
38114
+ needsSigning: true,
38115
+ unsignedTx: prepared.transaction,
38116
+ confirmWith: {
38117
+ tool: "dim_confirm_donate_to_pot",
38118
+ params: { gameId: args.gameId, amount: amountMinor }
38119
+ },
38120
+ hint: "Sign and broadcast unsignedTx with send_solana_transaction, then call dim_confirm_donate_to_pot with the signature."
38121
+ }
38122
+ };
38123
+ }
37503
38124
  const result = await client.sdk.games.sendDonation(
37504
38125
  args.gameId,
37505
38126
  amountMinor
@@ -37523,6 +38144,29 @@ async function donateToPot(client, args) {
37523
38144
  };
37524
38145
  }
37525
38146
  }
38147
+ async function confirmDonateToPot(client, args) {
38148
+ try {
38149
+ const result = await client.sdk.games.confirmGameDonationSignature(
38150
+ args.gameId,
38151
+ args.amount,
38152
+ args.signature
38153
+ );
38154
+ client.recordSpend(args.amount);
38155
+ return {
38156
+ data: {
38157
+ success: true,
38158
+ gameId: args.gameId,
38159
+ signature: result.signature,
38160
+ status: result.status
38161
+ }
38162
+ };
38163
+ } catch (error) {
38164
+ return {
38165
+ error: `Failed to confirm donation: ${error instanceof Error ? error.message : String(error)}`,
38166
+ isError: true
38167
+ };
38168
+ }
38169
+ }
37526
38170
  async function getGame(client, args) {
37527
38171
  try {
37528
38172
  const game = await client.sdk.games.getGame(args.gameId);
@@ -37534,6 +38178,12 @@ async function getGame(client, args) {
37534
38178
  };
37535
38179
  }
37536
38180
  }
38181
+ function isInStartingPhase(state) {
38182
+ const roundState = state.roundState;
38183
+ if (roundState?.phase === "starting") return true;
38184
+ if (state.status === "active" && state.currentPlayerId == null) return true;
38185
+ return false;
38186
+ }
37537
38187
  async function gameLoop(client, args) {
37538
38188
  try {
37539
38189
  const { gameId } = args;
@@ -37551,7 +38201,7 @@ async function gameLoop(client, args) {
37551
38201
  if (state?.status === "completed") {
37552
38202
  return buildGameLoopReturn(client, gameId, state, "completed");
37553
38203
  }
37554
- if (state?.currentPlayerId === client.currentUserId) {
38204
+ if (state?.currentPlayerId === client.currentUserId && !isInStartingPhase(state)) {
37555
38205
  return buildGameLoopReturn(client, gameId, state, "your-turn");
37556
38206
  }
37557
38207
  await raceTimeout(
@@ -38177,10 +38827,10 @@ async function checkNotifications(client) {
38177
38827
  isError: true
38178
38828
  };
38179
38829
  }
38180
- const [notifications, dmThreads, friendRequests] = await Promise.all([
38830
+ const [notifications, dmThreads, friendRequestsPage] = await Promise.all([
38181
38831
  client.sdk.notifications.list({ page: 1, limit: 20 }),
38182
38832
  client.sdk.chat.listDmThreads(),
38183
- client.sdk.users.getIncomingFriendRequests()
38833
+ client.sdk.users.getIncomingFriendRequests({ limit: 20 })
38184
38834
  ]);
38185
38835
  const unreadDms = dmThreads.filter(
38186
38836
  (t) => (t.unreadCount ?? 0) > 0
@@ -38192,9 +38842,10 @@ async function checkNotifications(client) {
38192
38842
  unreadNotificationCount: notifications.unreadCount,
38193
38843
  notifications: notifications.notifications.filter((n) => !n.read),
38194
38844
  unreadDmThreads: unreadDms,
38195
- incomingFriendRequests: friendRequests,
38845
+ incomingFriendRequests: friendRequestsPage.items,
38846
+ hasMoreIncomingFriendRequests: !!friendRequestsPage.nextCursor,
38196
38847
  pendingWsEvents: client.pendingEventCount,
38197
- hint: "Use dim_get_pending_events to drain buffered real-time events."
38848
+ 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
38849
  }
38199
38850
  };
38200
38851
  } catch (error) {
@@ -38610,6 +39261,36 @@ async function getMyStats(client) {
38610
39261
 
38611
39262
  // ../dim-agent-core/src/tools/index.ts
38612
39263
  var TOOL_DEFINITIONS = [
39264
+ // ── External signer mode (auth) ──────────────────────────────────────
39265
+ {
39266
+ name: "dim_request_auth_message",
39267
+ 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.",
39268
+ params: {
39269
+ address: {
39270
+ type: "string",
39271
+ description: "Your Solana wallet address (base58)",
39272
+ required: true
39273
+ }
39274
+ },
39275
+ execute: (c, a) => requestAuthMessage(c, a)
39276
+ },
39277
+ {
39278
+ name: "dim_complete_login",
39279
+ description: "External wallet login step 2: provide the wallet address and base58 signature from sign_solana_message to complete authentication with DIM.",
39280
+ params: {
39281
+ address: {
39282
+ type: "string",
39283
+ description: "Your Solana wallet address (base58)",
39284
+ required: true
39285
+ },
39286
+ signature: {
39287
+ type: "string",
39288
+ description: "Base58-encoded signature returned by sign_solana_message",
39289
+ required: true
39290
+ }
39291
+ },
39292
+ execute: (c, a) => completeLogin(c, a)
39293
+ },
38613
39294
  // ── Auth ─────────────────────────────────────────────────────────────
38614
39295
  {
38615
39296
  name: "dim_login",
@@ -38763,9 +39444,23 @@ var TOOL_DEFINITIONS = [
38763
39444
  },
38764
39445
  {
38765
39446
  name: "dim_get_incoming_friend_requests",
38766
- description: "List pending incoming friend requests.",
38767
- params: {},
38768
- execute: (c) => getIncomingFriendRequests(c)
39447
+ description: "List pending incoming friend requests. Returns { items, nextCursor, hasMore }. Pass cursor from a previous response to page through results.",
39448
+ params: {
39449
+ limit: {
39450
+ type: "number",
39451
+ description: "Maximum number of requests to return (1\u2013100, default 50).",
39452
+ required: false
39453
+ },
39454
+ cursor: {
39455
+ type: "string",
39456
+ description: "Pagination cursor from a previous response nextCursor field. Omit for the first page.",
39457
+ required: false
39458
+ }
39459
+ },
39460
+ execute: (c, a) => getIncomingFriendRequests(
39461
+ c,
39462
+ a
39463
+ )
38769
39464
  },
38770
39465
  // ── Chat ─────────────────────────────────────────────────────────────
38771
39466
  {
@@ -38891,6 +39586,90 @@ var TOOL_DEFINITIONS = [
38891
39586
  },
38892
39587
  execute: (c, a) => tipUser(c, a)
38893
39588
  },
39589
+ {
39590
+ name: "dim_confirm_send_usdc",
39591
+ 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.",
39592
+ params: {
39593
+ signature: {
39594
+ type: "string",
39595
+ description: "On-chain transaction signature returned by send_solana_transaction",
39596
+ required: true
39597
+ },
39598
+ recipientAddress: {
39599
+ type: "string",
39600
+ description: "Recipient Solana address (from confirmWith.params)",
39601
+ required: true
39602
+ },
39603
+ amount: {
39604
+ type: "number",
39605
+ description: "Amount in USDC minor units (from confirmWith.params)",
39606
+ required: true
39607
+ },
39608
+ fee: {
39609
+ type: "number",
39610
+ description: "Fee in minor units (from confirmWith.params)"
39611
+ },
39612
+ token: {
39613
+ type: "string",
39614
+ description: "Token type: USDC or SOL (default: USDC)"
39615
+ },
39616
+ ataCreated: {
39617
+ type: "string",
39618
+ description: "Whether a new ATA was created (from confirmWith.params)"
39619
+ },
39620
+ recipientInput: {
39621
+ type: "string",
39622
+ description: "Original recipient input (from confirmWith.params)"
39623
+ }
39624
+ },
39625
+ execute: (c, a) => confirmSendUsdc(
39626
+ c,
39627
+ a
39628
+ )
39629
+ },
39630
+ {
39631
+ name: "dim_confirm_tip_user",
39632
+ 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.",
39633
+ params: {
39634
+ signature: {
39635
+ type: "string",
39636
+ description: "On-chain transaction signature returned by send_solana_transaction",
39637
+ required: true
39638
+ },
39639
+ recipientAddress: {
39640
+ type: "string",
39641
+ description: "Recipient Solana address (from confirmWith.params)",
39642
+ required: true
39643
+ },
39644
+ recipientUserId: {
39645
+ type: "string",
39646
+ description: "Recipient user ID (from confirmWith.params)",
39647
+ required: true
39648
+ },
39649
+ recipientUsername: {
39650
+ type: "string",
39651
+ description: "Recipient username (from confirmWith.params)",
39652
+ required: true
39653
+ },
39654
+ amount: {
39655
+ type: "number",
39656
+ description: "Amount in USDC minor units (from confirmWith.params)",
39657
+ required: true
39658
+ },
39659
+ fee: {
39660
+ type: "number",
39661
+ description: "Fee in minor units (from confirmWith.params)"
39662
+ },
39663
+ ataCreated: {
39664
+ type: "string",
39665
+ description: "Whether a new ATA was created (from confirmWith.params)"
39666
+ }
39667
+ },
39668
+ execute: (c, a) => confirmTipUser(
39669
+ c,
39670
+ a
39671
+ )
39672
+ },
38894
39673
  {
38895
39674
  name: "dim_get_wallet_activity",
38896
39675
  description: "Get recent wallet transaction activity (deposits, payouts, transfers, refunds) and your DIM wallet address. Highlights any claimable items.",
@@ -38987,6 +39766,23 @@ var TOOL_DEFINITIONS = [
38987
39766
  },
38988
39767
  execute: (c, a) => depositForLobby(c, a)
38989
39768
  },
39769
+ {
39770
+ name: "dim_confirm_lobby_deposit",
39771
+ description: "External wallet: confirm a lobby deposit after signing and broadcasting the transaction. Polls until the deposit is confirmed on-chain, then returns canProceedToQueue.",
39772
+ params: {
39773
+ lobbyId: {
39774
+ type: "string",
39775
+ description: "The lobby ID (from confirmWith.params)",
39776
+ required: true
39777
+ },
39778
+ signature: {
39779
+ type: "string",
39780
+ description: "On-chain transaction signature returned by send_solana_transaction",
39781
+ required: true
39782
+ }
39783
+ },
39784
+ execute: (c, a) => confirmLobbyDeposit(c, a)
39785
+ },
38990
39786
  {
38991
39787
  name: "dim_leave_lobby",
38992
39788
  description: "Leave a lobby. Use this to exit a lobby you created or joined. Returns when you have left.",
@@ -39079,6 +39875,31 @@ var TOOL_DEFINITIONS = [
39079
39875
  },
39080
39876
  execute: (c, a) => gameLoop(c, a)
39081
39877
  },
39878
+ {
39879
+ name: "dim_confirm_donate_to_pot",
39880
+ description: "External wallet: confirm a game pot donation after signing and broadcasting the transaction.",
39881
+ params: {
39882
+ signature: {
39883
+ type: "string",
39884
+ description: "On-chain transaction signature returned by send_solana_transaction",
39885
+ required: true
39886
+ },
39887
+ gameId: {
39888
+ type: "string",
39889
+ description: "The game ID (from confirmWith.params)",
39890
+ required: true
39891
+ },
39892
+ amount: {
39893
+ type: "number",
39894
+ description: "Amount in USDC minor units (from confirmWith.params)",
39895
+ required: true
39896
+ }
39897
+ },
39898
+ execute: (c, a) => confirmDonateToPot(
39899
+ c,
39900
+ a
39901
+ )
39902
+ },
39082
39903
  {
39083
39904
  name: "dim_request_rematch",
39084
39905
  description: "Request a rematch after a completed game. If both players request, a lobby is created automatically server-side.",