@dimcool/dimclaw 0.1.19 → 0.1.20

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 (4) hide show
  1. package/dist/index.js +2364 -2279
  2. package/index.ts +151 -2281
  3. package/package.json +4 -6
  4. package/dim-client.ts +0 -214
package/index.ts CHANGED
@@ -1,20 +1,27 @@
1
1
  /**
2
2
  * @dimcool/dimclaw — OpenClaw plugin for DIM.
3
- * Uses the SDK directly (no MCP subprocess). Register all DIM tools for OpenClaw agents.
3
+ * Thin adapter: imports tool definitions from dim-agent-core and registers them with OpenClaw.
4
4
  */
5
5
 
6
6
  import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
7
7
  import path from 'node:path';
8
- import { Keypair, Transaction } from '@solana/web3.js';
8
+ import { Keypair } from '@solana/web3.js';
9
9
  import bs58 from 'bs58';
10
- import type { ValidAction } from '@dimcool/sdk';
11
- import { DimClient } from './dim-client';
10
+ import {
11
+ DimAgentClient,
12
+ TOOL_DEFINITIONS,
13
+ DIM_INSTRUCTIONS,
14
+ } from '@dimcool/dim-agent-core';
15
+ import type { ToolParam, BufferedEvent } from '@dimcool/dim-agent-core';
16
+
17
+ // ── Plugin config ────────────────────────────────────────────────────
12
18
 
13
19
  type PluginConfig = {
14
20
  walletPrivateKey?: string;
15
21
  walletStorePath?: string;
16
22
  apiUrl?: string;
17
23
  heartbeatPath?: string;
24
+ referralCode?: string;
18
25
  // Autonomy scopes
19
26
  autoAcceptFriendRequests?: boolean;
20
27
  autoReplyDms?: boolean;
@@ -34,13 +41,7 @@ function getPluginConfig(api: { config?: unknown }): PluginConfig | null {
34
41
  return dimclawEntry?.config ?? null;
35
42
  }
36
43
 
37
- function resolveStorePath(storePath: string): string {
38
- const expanded =
39
- storePath.startsWith('~/') && process.env.HOME
40
- ? path.join(process.env.HOME, storePath.slice(2))
41
- : storePath;
42
- return path.resolve(expanded);
43
- }
44
+ // ── Wallet resolution ────────────────────────────────────────────────
44
45
 
45
46
  interface WalletRecord {
46
47
  version: 1;
@@ -49,6 +50,14 @@ interface WalletRecord {
49
50
  createdAt: string;
50
51
  }
51
52
 
53
+ function resolveStorePath(storePath: string): string {
54
+ const expanded =
55
+ storePath.startsWith('~/') && process.env.HOME
56
+ ? path.join(process.env.HOME, storePath.slice(2))
57
+ : storePath;
58
+ return path.resolve(expanded);
59
+ }
60
+
52
61
  async function readWalletFile(storePath: string): Promise<WalletRecord | null> {
53
62
  try {
54
63
  const raw = await readFile(storePath, 'utf8');
@@ -90,7 +99,6 @@ function createWalletRecord(): WalletRecord {
90
99
  };
91
100
  }
92
101
 
93
- /** Resolve wallet private key from config: direct key, or read/create at store path. */
94
102
  async function resolveWalletKey(config: PluginConfig): Promise<string | null> {
95
103
  if (config.walletPrivateKey?.trim()) return config.walletPrivateKey.trim();
96
104
  if (!config.walletStorePath?.trim()) return null;
@@ -102,200 +110,93 @@ async function resolveWalletKey(config: PluginConfig): Promise<string | null> {
102
110
  return record.walletPrivateKey;
103
111
  }
104
112
 
105
- function generateOnboardingScript(platform: string, referrer: string): string {
106
- const templates: Record<string, string> = {
107
- openclaw: `# Connect to DIM — OpenClaw Setup
108
-
109
- ## Step 1: Install the DIM plugin
110
- \`\`\`bash
111
- openclaw plugins install @dimcool/dimclaw
112
- \`\`\`
113
-
114
- ## Step 2: Configure
115
- Add to your OpenClaw config under \`plugins.entries.dimclaw.config\`:
116
- \`\`\`yaml
117
- plugins:
118
- entries:
119
- dimclaw:
120
- enabled: true
121
- config:
122
- walletStorePath: '~/.dim/openclaw-wallet.json'
123
- \`\`\`
124
- A wallet will be created automatically on first run.
125
-
126
- ## Step 3: Restart and verify
127
- \`\`\`bash
128
- openclaw gateway restart
129
- \`\`\`
130
-
131
- Then ask your agent to run:
132
- 1. \`dim_login\`
133
- 2. \`dim_apply_referral_code\` with referralCode: \`${referrer}\`
134
- 3. \`dim_get_balance\`
135
-
136
- ## Step 4: Fund your wallet
137
- Send USDC on Solana to the wallet address shown by \`dim_get_balance\`. Even $5 is enough to start playing.
113
+ // ── Heartbeat (OpenClaw-specific) ────────────────────────────────────
138
114
 
139
- You now have a 10% fee discount on all games thanks to the referral code!`,
140
-
141
- 'claude-desktop': `# Connect to DIM — Claude Desktop Setup
142
-
143
- ## Step 1: Initialize wallet
144
- \`\`\`bash
145
- npx @dimcool/mcp init-wallet
146
- \`\`\`
147
- Copy the wallet store path from the output.
148
-
149
- ## Step 2: Edit your Claude Desktop config
150
- Open \`~/Library/Application Support/Claude/claude_desktop_config.json\` (macOS) or \`%APPDATA%\\Claude\\claude_desktop_config.json\` (Windows).
151
-
152
- Add this to the \`mcpServers\` section:
153
- \`\`\`json
154
- {
155
- "mcpServers": {
156
- "dim": {
157
- "command": "npx",
158
- "args": ["@dimcool/mcp"],
159
- "env": {
160
- "DIM_WALLET_STORE_PATH": "/path/from/init-wallet/output",
161
- "DIM_API_URL": "https://api.dim.cool",
162
- "DIM_REFERRAL_CODE": "${referrer}"
163
- }
164
- }
115
+ function resolveHeartbeatPath(p: string): string {
116
+ if (p.startsWith('~/') && process.env.HOME) {
117
+ return path.join(process.env.HOME, p.slice(2));
165
118
  }
119
+ return path.resolve(p);
166
120
  }
167
- \`\`\`
168
- Replace \`/path/from/init-wallet/output\` with the actual path from step 1.
169
-
170
- ## Step 3: Restart Claude Desktop
171
- Quit and reopen Claude Desktop. The referral code \`${referrer}\` will be applied on first login automatically.
172
-
173
- ## Step 4: Verify
174
- Ask Claude: "Log in to DIM and check my balance"
175
- Claude will call \`dim_login\` then \`dim_get_balance\`.
176
121
 
177
- ## Step 5: Fund your wallet
178
- Send USDC on Solana to the wallet address shown. Even $5 is enough to start.
179
-
180
- You have a 10% fee discount on all games thanks to the referral code!`,
181
-
182
- cursor: `# Connect to DIM — Cursor Setup
183
-
184
- ## Step 1: Initialize wallet
185
- \`\`\`bash
186
- npx @dimcool/mcp init-wallet
187
- \`\`\`
188
- Copy the wallet store path from the output.
189
-
190
- ## Step 2: Edit your Cursor MCP config
191
- Create or edit \`.cursor/mcp.json\` in your project root:
192
- \`\`\`json
193
- {
194
- "mcpServers": {
195
- "dim": {
196
- "command": "npx",
197
- "args": ["@dimcool/mcp"],
198
- "env": {
199
- "DIM_WALLET_STORE_PATH": "/path/from/init-wallet/output",
200
- "DIM_API_URL": "https://api.dim.cool",
201
- "DIM_REFERRAL_CODE": "${referrer}"
202
- }
122
+ function createHeartbeatWriter(heartbeatPath: string | undefined) {
123
+ if (!heartbeatPath) return undefined;
124
+ const filePath = resolveHeartbeatPath(heartbeatPath);
125
+ const pending: BufferedEvent[] = [];
126
+
127
+ return async (event: BufferedEvent) => {
128
+ pending.push(event);
129
+ const lines = ['# DIM Heartbeat', ''];
130
+ const eventTypes = new Set(pending.map((e) => e.event));
131
+ if (eventTypes.has('chat:message')) {
132
+ lines.push('- You have new DMs — call dim_check_notifications');
203
133
  }
204
- }
134
+ if (eventTypes.has('notification')) {
135
+ lines.push(
136
+ '- New notifications (challenges, friend requests, game results) — call dim_check_notifications',
137
+ );
138
+ }
139
+ if (eventTypes.has('lobby:matched') || eventTypes.has('lobby:invitation')) {
140
+ lines.push('- A game match is ready — call dim_get_pending_events');
141
+ }
142
+ if (eventTypes.has('game:turn')) {
143
+ lines.push("- It's your turn in a game — call dim_get_pending_events");
144
+ }
145
+ if (eventTypes.has('game:completed')) {
146
+ lines.push('- A game has completed — call dim_get_pending_events');
147
+ }
148
+ lines.push('');
149
+ try {
150
+ await mkdir(path.dirname(filePath), { recursive: true });
151
+ const tmp = `${filePath}.tmp`;
152
+ await writeFile(tmp, lines.join('\n'), 'utf8');
153
+ await rename(tmp, filePath);
154
+ } catch {
155
+ /* best-effort */
156
+ }
157
+ };
205
158
  }
206
- \`\`\`
207
- Replace \`/path/from/init-wallet/output\` with the actual path from step 1.
208
-
209
- ## Step 3: Restart Cursor
210
- The referral code \`${referrer}\` will be applied on first login automatically.
211
-
212
- ## Step 4: Verify
213
- Ask: "Log in to DIM and check my balance"
214
-
215
- ## Step 5: Fund your wallet
216
- Send USDC on Solana to the wallet address shown. Even $5 is enough to start.`,
217
-
218
- hermes: `# Connect to DIM — Hermes Setup
219
-
220
- ## Step 1: Initialize wallet
221
- \`\`\`bash
222
- npx @dimcool/mcp init-wallet
223
- \`\`\`
224
- Copy the wallet store path.
225
-
226
- ## Step 2: Add DIM as a Hermes skill
227
- In your Hermes config, add:
228
- \`\`\`yaml
229
- skills:
230
- dim:
231
- command: npx
232
- args: ["@dimcool/mcp"]
233
- env:
234
- DIM_WALLET_STORE_PATH: "/path/from/init-wallet/output"
235
- DIM_API_URL: "https://api.dim.cool"
236
- DIM_REFERRAL_CODE: "${referrer}"
237
- \`\`\`
238
-
239
- ## Step 3: Restart Hermes
240
- The referral code \`${referrer}\` will be applied on first login.
241
-
242
- ## Step 4: Verify
243
- Call \`dim_login\` then \`dim_get_balance\`.
244
-
245
- ## Step 5: Fund your wallet
246
- Send USDC on Solana to the wallet address shown.`,
247
-
248
- 'node-sdk': `# Connect to DIM — Node.js SDK Setup
249
-
250
- ## Step 1: Install packages
251
- \`\`\`bash
252
- npm install @dimcool/sdk @dimcool/wallet
253
- \`\`\`
254
-
255
- ## Step 2: Initialize and connect
256
- \`\`\`typescript
257
- import { SDK, NodeStorage } from '@dimcool/sdk';
258
- import { Wallet } from '@dimcool/wallet';
259
-
260
- const wallet = new Wallet({
261
- enabledNetworks: ['solana'],
262
- fromPrivateKey: process.env.DIM_WALLET_PRIVATE_KEY!,
263
- });
264
-
265
- const sdk = new SDK({
266
- appId: 'dim-agents',
267
- baseUrl: 'https://api.dim.cool',
268
- storage: new NodeStorage(),
269
- autoPay: { enabled: true, maxAmountMinor: 20_000 },
270
- });
271
-
272
- sdk.wallet.setSigner(wallet.getSigner());
273
159
 
274
- const { access_token, user } = await sdk.auth.loginWithWallet({
275
- referralCode: '${referrer}',
276
- });
277
-
278
- sdk.wsTransport.setAccessToken(access_token);
279
- await sdk.ensureWebSocketConnected(10000);
280
-
281
- console.log('Connected as', user.username, '— wallet:', wallet.getAddresses().solana);
282
- \`\`\`
283
-
284
- ## Step 3: Fund your wallet
285
- Send USDC on Solana to the wallet address. Even $5 is enough to start.
286
-
287
- The referral code \`${referrer}\` gives you a 10% fee discount on all games.
288
-
289
- Full docs: https://docs.dim.cool`,
290
- };
160
+ // ── ToolParam JSON Schema ─────────────────────────────────────────
161
+
162
+ function paramToJsonSchema(p: ToolParam): Record<string, unknown> {
163
+ const schema: Record<string, unknown> = { description: p.description };
164
+ if (p.enum) {
165
+ schema.type = 'string';
166
+ schema.enum = p.enum;
167
+ } else if (p.type === 'number') {
168
+ schema.type = 'number';
169
+ if (p.min != null) schema.minimum = p.min;
170
+ if (p.max != null) schema.maximum = p.max;
171
+ } else if (p.type === 'object') {
172
+ schema.type = 'object';
173
+ } else {
174
+ schema.type = 'string';
175
+ if (p.min != null) schema.minLength = p.min;
176
+ if (p.max != null) schema.maxLength = p.max;
177
+ }
178
+ return schema;
179
+ }
291
180
 
292
- const script = templates[platform];
293
- if (!script) {
294
- return `Unknown platform "${platform}". Supported: openclaw, claude-desktop, cursor, hermes, node-sdk`;
181
+ function buildJsonSchema(
182
+ params: Record<string, ToolParam>,
183
+ ): Record<string, unknown> {
184
+ const properties: Record<string, unknown> = {};
185
+ const required: string[] = [];
186
+ for (const [key, param] of Object.entries(params)) {
187
+ properties[key] = paramToJsonSchema(param);
188
+ if (param.required) required.push(key);
295
189
  }
296
- return script;
190
+ return {
191
+ type: 'object',
192
+ properties,
193
+ ...(required.length > 0 ? { required } : {}),
194
+ additionalProperties: false,
195
+ };
297
196
  }
298
197
 
198
+ // ── OpenClaw response helper ─────────────────────────────────────────
199
+
299
200
  function textResult(
300
201
  text: string,
301
202
  isError = false,
@@ -306,6 +207,8 @@ function textResult(
306
207
  };
307
208
  }
308
209
 
210
+ // ── Plugin entry point ───────────────────────────────────────────────
211
+
309
212
  export default function register(api: {
310
213
  config?: unknown;
311
214
  registerTool: (tool: {
@@ -321,45 +224,36 @@ export default function register(api: {
321
224
  }>;
322
225
  }) => void;
323
226
  }) {
324
- let client: DimClient | null = null;
227
+ let client: DimAgentClient | null = null;
325
228
  let pluginConfig: PluginConfig | null = null;
326
229
 
327
- function checkSpendLimit(
328
- c: DimClient,
329
- amountDollars: number,
330
- isGameBet = false,
331
- ): string | null {
332
- if (!pluginConfig) return null;
333
- if (isGameBet) {
334
- const maxBet = pluginConfig.maxBetPerGame ?? 1.0;
335
- if (amountDollars > maxBet) {
336
- return `Bet $${amountDollars.toFixed(2)} exceeds maxBetPerGame limit of $${maxBet.toFixed(2)}. Ask your operator to increase maxBetPerGame in the plugin config if needed.`;
337
- }
338
- }
339
- const limit = pluginConfig.dailySpendLimit ?? 20.0;
340
- const projected = c.dailySpentDollars + amountDollars;
341
- if (projected > limit) {
342
- return `Daily spend limit reached ($${c.dailySpentDollars.toFixed(2)} spent of $${limit.toFixed(2)} limit). This action would cost $${amountDollars.toFixed(2)}. Ask your operator to increase dailySpendLimit in the plugin config if you need more.`;
343
- }
344
- return null;
345
- }
346
-
347
- async function getClient(): Promise<DimClient | null> {
230
+ async function getClient(): Promise<DimAgentClient | null> {
348
231
  if (client) return client;
349
232
  pluginConfig = getPluginConfig(api);
350
233
  if (!pluginConfig) return null;
351
234
  const walletPrivateKey = await resolveWalletKey(pluginConfig);
352
235
  if (!walletPrivateKey) return null;
353
- client = new DimClient({
236
+ const heartbeatWriter = createHeartbeatWriter(pluginConfig.heartbeatPath);
237
+ client = new DimAgentClient({
354
238
  walletPrivateKey,
355
239
  apiUrl: pluginConfig.apiUrl,
356
- heartbeatPath: pluginConfig.heartbeatPath,
240
+ referralCode: pluginConfig.referralCode,
241
+ agentConfig: {
242
+ autoAcceptFriendRequests: pluginConfig.autoAcceptFriendRequests,
243
+ autoReplyDms: pluginConfig.autoReplyDms,
244
+ autoPlayGames: pluginConfig.autoPlayGames,
245
+ maxBetPerGame: pluginConfig.maxBetPerGame,
246
+ dailySpendLimit: pluginConfig.dailySpendLimit,
247
+ autoJoinGlobalChat: pluginConfig.autoJoinGlobalChat,
248
+ autoPromoteReferrals: pluginConfig.autoPromoteReferrals,
249
+ },
250
+ onEvent: heartbeatWriter,
357
251
  });
358
252
  return client;
359
253
  }
360
254
 
361
255
  async function requireClient(): Promise<
362
- DimClient | { error: ReturnType<typeof textResult> }
256
+ DimAgentClient | { error: ReturnType<typeof textResult> }
363
257
  > {
364
258
  const c = await getClient();
365
259
  if (c) return c;
@@ -371,1962 +265,57 @@ export default function register(api: {
371
265
  };
372
266
  }
373
267
 
374
- // --- Auth ---
375
- api.registerTool({
376
- name: 'dim_login',
377
- description:
378
- 'Authenticate with DIM using the configured wallet. 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.',
379
- parameters: { type: 'object', properties: {}, additionalProperties: false },
380
- async execute() {
381
- const c = await requireClient();
382
- if ('error' in c) return c.error;
383
- try {
384
- const result = await c.authenticate();
385
- c.startEventListeners();
386
- const nextSteps: string[] = [];
387
- if (result.username == null || result.username === '') {
388
- nextSteps.push(
389
- 'No username set — call dim_set_username to claim one',
390
- );
391
- }
392
- nextSteps.push('Check your balance with dim_get_balance');
393
- nextSteps.push('Explore available games with dim_list_games');
268
+ // Register all tools from the core registry
269
+ for (const tool of TOOL_DEFINITIONS) {
270
+ api.registerTool({
271
+ name: tool.name,
272
+ description: tool.description,
273
+ parameters: buildJsonSchema(tool.params),
274
+ async execute(_id, params) {
275
+ const c = await requireClient();
276
+ if ('error' in c) return c.error;
394
277
  try {
395
- const summary = await c.sdk.referrals.getSummary();
396
- if (!summary.hasReferrer) {
397
- nextSteps.push(
398
- 'No referrer yet — call dim_apply_referral_code for 10% fee discount',
399
- );
400
- }
401
- if (result.username) {
402
- nextSteps.push(
403
- `Share your referral code "${result.username}" with other users/agents — you earn 30% of their game fees (https://dim.cool/?ref=${result.username})`,
404
- );
278
+ const result = await tool.execute(c, params);
279
+ if (result.isError || result.error) {
280
+ return textResult(result.error || 'Unknown error', true);
405
281
  }
406
- } catch {
407
- /* non-critical */
408
- }
409
-
410
- const booleanExists = (value: boolean | undefined, defaultValue: boolean) => typeof value === 'boolean' ? value : defaultValue;
411
- const response: Record<string, unknown> = {
412
- success: true,
413
- userId: result.userId,
414
- username: result.username ?? null,
415
- walletAddress: c.walletAddress,
416
- agentConfig: {
417
- autoAcceptFriendRequests: booleanExists(
418
- pluginConfig?.autoAcceptFriendRequests, true),
419
- autoReplyDms: booleanExists(pluginConfig?.autoReplyDms, true),
420
- autoPlayGames: booleanExists(pluginConfig?.autoPlayGames, true),
421
- maxBetPerGame: pluginConfig?.maxBetPerGame ?? 1.0,
422
- dailySpendLimit: pluginConfig?.dailySpendLimit ?? 20.0,
423
- autoJoinGlobalChat: booleanExists(pluginConfig?.autoJoinGlobalChat, true),
424
- autoPromoteReferrals: booleanExists(pluginConfig?.autoPromoteReferrals, true),
425
- },
426
- nextSteps,
427
- };
428
- return textResult(JSON.stringify(response, null, 2));
429
- } catch (err) {
430
- return textResult(
431
- `Authentication failed: ${err instanceof Error ? err.message : String(err)}`,
432
- true,
433
- );
434
- }
435
- },
436
- });
437
-
438
- api.registerTool({
439
- name: 'dim_get_profile',
440
- description:
441
- 'Get the current authenticated user profile including username, avatar, bio, and chess ELO rating.',
442
- parameters: { type: 'object', properties: {}, additionalProperties: false },
443
- async execute() {
444
- const c = await requireClient();
445
- if ('error' in c) return c.error;
446
- try {
447
- if (!c.currentUserId)
448
- return textResult('Not authenticated. Call dim_login first.', true);
449
- const user = await c.sdk.users.getUserById(c.currentUserId);
450
- return textResult(JSON.stringify(user, null, 2));
451
- } catch (err) {
452
- return textResult(
453
- `Failed to get profile: ${err instanceof Error ? err.message : String(err)}`,
454
- true,
455
- );
456
- }
457
- },
458
- });
459
-
460
- api.registerTool({
461
- name: 'dim_set_username',
462
- description:
463
- 'Set or update the username for the authenticated user. Username must be alphanumeric, 3-20 characters.',
464
- parameters: {
465
- type: 'object',
466
- properties: {
467
- username: { type: 'string', description: 'The desired username' },
468
- },
469
- required: ['username'],
470
- additionalProperties: false,
471
- },
472
- async execute(_, params) {
473
- const c = await requireClient();
474
- if ('error' in c) return c.error;
475
- const username = String(params.username ?? '');
476
- try {
477
- const { available, valid } =
478
- await c.sdk.users.isUsernameAvailable(username);
479
- if (!valid)
480
282
  return textResult(
481
- 'Invalid username. Must be alphanumeric, 3-20 characters.',
283
+ typeof result.data === 'string'
284
+ ? result.data
285
+ : JSON.stringify(result.data, null, 2),
286
+ );
287
+ } catch (err) {
288
+ return textResult(
289
+ `${tool.name} failed: ${err instanceof Error ? err.message : String(err)}`,
482
290
  true,
483
291
  );
484
- if (!available)
485
- return textResult(`Username "${username}" is already taken.`, true);
486
- const user = await c.sdk.users.updateUsername(username);
487
- return textResult(
488
- JSON.stringify({ success: true, username: user.username }, null, 2),
489
- );
490
- } catch (err) {
491
- return textResult(
492
- `Failed to set username: ${err instanceof Error ? err.message : String(err)}`,
493
- true,
494
- );
495
- }
496
- },
497
- });
498
-
499
- // --- Instructions (discovery) ---
500
- const DIM_INSTRUCTIONS = [
501
- {
502
- name: 'dim_login',
503
- description:
504
- 'Authenticate with DIM. Call first; then set username, check balance, list games, or apply referral code.',
505
- },
506
- {
507
- name: 'dim_get_profile',
508
- description:
509
- 'Get current user profile (username, avatar, bio, chess ELO).',
510
- },
511
- {
512
- name: 'dim_set_username',
513
- description: 'Set or update your username (alphanumeric, 3–20 chars).',
514
- },
515
- {
516
- name: 'dim_list_instructions',
517
- description:
518
- 'List all available DIM tools and short descriptions (this list).',
519
- },
520
- {
521
- name: 'dim_get_balance',
522
- description:
523
- 'Get SOL and USDC balance; usdcFormatted is always present (e.g. $0.00).',
524
- },
525
- {
526
- name: 'dim_send_usdc',
527
- description:
528
- 'Send USDC to a user by username or address. 1¢ fee; amount in dollars.',
529
- },
530
- {
531
- name: 'dim_tip_user',
532
- description: 'Tip a user with USDC; broadcast to global chat. 1¢ fee.',
533
- },
534
- {
535
- name: 'dim_get_wallet_activity',
536
- description: 'Get recent wallet activity (deposits, payouts, transfers).',
537
- },
538
- { name: 'dim_search_users', description: 'Search users by username.' },
539
- {
540
- name: 'dim_send_friend_request',
541
- description: 'Send friend request by user ID.',
542
- },
543
- {
544
- name: 'dim_accept_friend_request',
545
- description: 'Accept an incoming friend request.',
546
- },
547
- {
548
- name: 'dim_list_friends',
549
- description: 'List your friends with pagination.',
550
- },
551
- {
552
- name: 'dim_get_incoming_friend_requests',
553
- description: 'List pending incoming friend requests.',
554
- },
555
- {
556
- name: 'dim_send_message',
557
- description: 'Send chat in a context (lobby, game, dm, global).',
558
- },
559
- {
560
- name: 'dim_get_chat_history',
561
- description: 'Get chat history for a context.',
562
- },
563
- {
564
- name: 'dim_send_dm',
565
- description: 'Send a direct message to a user by ID.',
566
- },
567
- {
568
- name: 'dim_list_dm_threads',
569
- description: 'List DM threads with last message and unread counts.',
570
- },
571
- {
572
- name: 'dim_list_games',
573
- description: 'List available game types (RPS, Chess, Tic-Tac-Toe, etc.).',
574
- },
575
- {
576
- name: 'dim_get_game_metrics',
577
- description:
578
- 'Get real-time metrics: active players, live games, money in play.',
579
- },
580
- {
581
- name: 'dim_create_lobby',
582
- description: 'Create a game lobby; bet in USDC dollars.',
583
- },
584
- {
585
- name: 'dim_join_queue',
586
- description:
587
- 'Join matchmaking queue. If not matched immediately, poll dim_get_lobby until gameId appears.',
588
- },
589
- { name: 'dim_get_lobby', description: 'Get lobby details by ID.' },
590
- {
591
- name: 'dim_get_game_state',
592
- description: 'Get current state of a game (for submitting moves).',
593
- },
594
- {
595
- name: 'dim_submit_action',
596
- description: 'Submit a move/action in a game.',
597
- },
598
- { name: 'dim_donate_to_pot', description: 'Add to the pot in a lobby.' },
599
- { name: 'dim_get_game', description: 'Get game summary by ID.' },
600
- {
601
- name: 'dim_challenge_user',
602
- description: 'Challenge another user to a game.',
603
- },
604
- { name: 'dim_accept_challenge', description: 'Accept a game challenge.' },
605
- {
606
- name: 'dim_get_referral_summary',
607
- description: 'Get your referral stats and rewards.',
608
- },
609
- { name: 'dim_get_referral_tree', description: 'Get your referral tree.' },
610
- {
611
- name: 'dim_get_referral_rewards',
612
- description: 'Get claimable referral rewards.',
613
- },
614
- {
615
- name: 'dim_claim_referral_rewards',
616
- description: 'Claim referral rewards to your wallet.',
617
- },
618
- {
619
- name: 'dim_apply_referral_code',
620
- description: 'Apply a referral code for 10% fee discount.',
621
- },
622
- {
623
- name: 'dim_create_support_ticket',
624
- description: 'Create a support ticket.',
625
- },
626
- { name: 'dim_get_my_tickets', description: 'List your support tickets.' },
627
- { name: 'dim_get_ticket', description: 'Get a ticket by ID.' },
628
- {
629
- name: 'dim_add_ticket_message',
630
- description: 'Add a message to a ticket.',
631
- },
632
- { name: 'dim_close_ticket', description: 'Close a support ticket.' },
633
- {
634
- name: 'dim_get_market',
635
- description: 'Get prediction market for a game.',
636
- },
637
- { name: 'dim_buy_shares', description: 'Buy prediction market shares.' },
638
- { name: 'dim_sell_shares', description: 'Sell prediction market shares.' },
639
- { name: 'dim_get_positions', description: 'Get your market positions.' },
640
- {
641
- name: 'dim_redeem_shares',
642
- description: 'Redeem shares after market resolution.',
643
- },
644
- { name: 'dim_get_market_analytics', description: 'Get market analytics.' },
645
- {
646
- name: 'dim_get_pending_events',
647
- description:
648
- 'Drain buffered real-time events (DMs, challenges, game turns). Call regularly.',
649
- },
650
- {
651
- name: 'dim_check_notifications',
652
- description:
653
- 'Check unread notifications, DMs, and friend requests in one call.',
654
- },
655
- {
656
- name: 'dim_get_agent_config',
657
- description:
658
- 'Get autonomy scopes, spending limits, and current daily spend.',
659
- },
660
- {
661
- name: 'dim_get_referral_onboarding',
662
- description:
663
- 'Get platform-specific setup instructions to share with another agent, with your referral code embedded.',
664
- },
665
- ];
666
- api.registerTool({
667
- name: 'dim_list_instructions',
668
- description:
669
- 'List all available DIM tools (instructions) with short descriptions. Use this to discover what you can do after dim_login.',
670
- parameters: { type: 'object', properties: {}, additionalProperties: false },
671
- async execute() {
672
- return textResult(
673
- JSON.stringify(
674
- {
675
- instructions: DIM_INSTRUCTIONS,
676
- hint: 'Call dim_login first if you have not authenticated. Then use any tool above by name.',
677
- },
678
- null,
679
- 2,
680
- ),
681
- );
682
- },
683
- });
684
-
685
- // --- Wallet ---
686
- api.registerTool({
687
- name: 'dim_get_balance',
688
- description:
689
- 'Get the wallet balance for the authenticated user. Returns SOL and USDC (usdc in minor units; usdcFormatted is always present, e.g. $0.00 or $1.50, so you do not need to convert).',
690
- parameters: { type: 'object', properties: {}, additionalProperties: false },
691
- async execute() {
692
- const c = await requireClient();
693
- if ('error' in c) return c.error;
694
- try {
695
- const balance = await c.sdk.wallet.getBalances();
696
- const usdcDollars = balance.usdc / 1_000_000;
697
- const payload = {
698
- ...balance,
699
- usdcFormatted: `$${usdcDollars.toFixed(2)}`,
700
- };
701
- return textResult(JSON.stringify(payload, null, 2));
702
- } catch (err) {
703
- return textResult(
704
- `Failed to get balance: ${err instanceof Error ? err.message : String(err)}`,
705
- true,
706
- );
707
- }
708
- },
709
- });
710
-
711
- api.registerTool({
712
- name: 'dim_send_usdc',
713
- description:
714
- 'Send USDC to another user by username or Solana address. A 1 cent ($0.01) fee applies per transfer. Minimum transfer is 5 cents ($0.05). Amount is in USDC dollars (e.g., 1.50 for $1.50).',
715
- parameters: {
716
- type: 'object',
717
- properties: {
718
- recipient: {
719
- type: 'string',
720
- description: 'Recipient username or Solana wallet address',
721
- },
722
- amount: {
723
- type: 'number',
724
- description: 'Amount in USDC dollars (e.g., 1.50 for $1.50)',
725
- },
726
- },
727
- required: ['recipient', 'amount'],
728
- additionalProperties: false,
729
- },
730
- async execute(_, params) {
731
- const c = await requireClient();
732
- if ('error' in c) return c.error;
733
- const recipient = String(params.recipient ?? '');
734
- const amount = Number(params.amount ?? 0);
735
- const limitErr = checkSpendLimit(c, amount);
736
- if (limitErr) return textResult(limitErr, true);
737
- try {
738
- const amountMinor = Math.round(amount * 1_000_000);
739
- const result = await c.sdk.wallet.send(recipient, amountMinor);
740
- c.recordSpend(amountMinor);
741
- return textResult(
742
- JSON.stringify(
743
- {
744
- success: true,
745
- signature: result.signature,
746
- status: result.status,
747
- amountSent: `$${amount.toFixed(2)}`,
748
- fee: '$0.01',
749
- recipient,
750
- recipientAddress: result.recipientAddress,
751
- },
752
- null,
753
- 2,
754
- ),
755
- );
756
- } catch (err) {
757
- return textResult(
758
- `Failed to send USDC: ${err instanceof Error ? err.message : String(err)}`,
759
- true,
760
- );
761
- }
762
- },
763
- });
764
-
765
- api.registerTool({
766
- name: 'dim_tip_user',
767
- description:
768
- 'Tip a user with USDC and broadcast the tip to global chat. A 1 cent fee applies. Amount is in USDC dollars.',
769
- parameters: {
770
- type: 'object',
771
- properties: {
772
- recipientUsername: {
773
- type: 'string',
774
- description: 'Username of the user to tip',
775
- },
776
- amount: {
777
- type: 'number',
778
- description: 'Tip amount in USDC dollars (e.g., 1.00 for $1.00)',
779
- },
780
- },
781
- required: ['recipientUsername', 'amount'],
782
- additionalProperties: false,
783
- },
784
- async execute(_, params) {
785
- const c = await requireClient();
786
- if ('error' in c) return c.error;
787
- const recipientUsername = String(params.recipientUsername ?? '');
788
- const amount = Number(params.amount ?? 0);
789
- const limitErr = checkSpendLimit(c, amount);
790
- if (limitErr) return textResult(limitErr, true);
791
- try {
792
- const amountMinor = Math.round(amount * 1_000_000);
793
- const result = await c.sdk.tips.send(recipientUsername, amountMinor);
794
- c.recordSpend(amountMinor);
795
- return textResult(
796
- JSON.stringify(
797
- {
798
- success: true,
799
- signature: result.signature,
800
- tipAmount: `$${amount.toFixed(2)}`,
801
- fee: '$0.01',
802
- recipient: recipientUsername,
803
- broadcastedToGlobalChat: true,
804
- },
805
- null,
806
- 2,
807
- ),
808
- );
809
- } catch (err) {
810
- return textResult(
811
- `Failed to tip user: ${err instanceof Error ? err.message : String(err)}`,
812
- true,
813
- );
814
- }
815
- },
816
- });
817
-
818
- api.registerTool({
819
- name: 'dim_get_wallet_activity',
820
- description:
821
- 'Get recent wallet transaction activity (deposits, payouts, transfers, refunds).',
822
- parameters: {
823
- type: 'object',
824
- properties: {
825
- limit: {
826
- type: 'number',
827
- description: 'Max items to return (default: 20)',
828
- },
829
- },
830
- additionalProperties: false,
831
- },
832
- async execute(_, params) {
833
- const c = await requireClient();
834
- if ('error' in c) return c.error;
835
- const limit = typeof params.limit === 'number' ? params.limit : 20;
836
- try {
837
- const activity = await c.sdk.wallet.getActivity({ limit });
838
- return textResult(JSON.stringify(activity, null, 2));
839
- } catch (err) {
840
- return textResult(
841
- `Failed to get wallet activity: ${err instanceof Error ? err.message : String(err)}`,
842
- true,
843
- );
844
- }
845
- },
846
- });
847
-
848
- // --- Friends ---
849
- api.registerTool({
850
- name: 'dim_search_users',
851
- description:
852
- 'Search for DIM users by username. Returns matching users with their online status and friendship status.',
853
- parameters: {
854
- type: 'object',
855
- properties: {
856
- query: { type: 'string', description: 'Username to search for' },
857
- limit: {
858
- type: 'number',
859
- description: 'Max results to return (default: 10)',
860
- },
861
- },
862
- required: ['query'],
863
- additionalProperties: false,
864
- },
865
- async execute(_, params) {
866
- const c = await requireClient();
867
- if ('error' in c) return c.error;
868
- const query = String(params.query ?? '');
869
- const limit = typeof params.limit === 'number' ? params.limit : 10;
870
- try {
871
- const result = await c.sdk.users.searchUsers(query, 1, limit);
872
- return textResult(JSON.stringify(result, null, 2));
873
- } catch (err) {
874
- return textResult(
875
- `Search failed: ${err instanceof Error ? err.message : String(err)}`,
876
- true,
877
- );
878
- }
879
- },
880
- });
881
-
882
- api.registerTool({
883
- name: 'dim_send_friend_request',
884
- description:
885
- 'Send a friend request to a user by their user ID. If they already sent you a request, this auto-accepts.',
886
- parameters: {
887
- type: 'object',
888
- properties: {
889
- userId: {
890
- type: 'string',
891
- description: 'The user ID to send a friend request to',
892
- },
893
- },
894
- required: ['userId'],
895
- additionalProperties: false,
896
- },
897
- async execute(_, params) {
898
- const c = await requireClient();
899
- if ('error' in c) return c.error;
900
- try {
901
- const result = await c.sdk.users.addFriend(String(params.userId ?? ''));
902
- return textResult(JSON.stringify(result, null, 2));
903
- } catch (err) {
904
- return textResult(
905
- `Failed to send friend request: ${err instanceof Error ? err.message : String(err)}`,
906
- true,
907
- );
908
- }
909
- },
910
- });
911
-
912
- api.registerTool({
913
- name: 'dim_accept_friend_request',
914
- description: 'Accept an incoming friend request from a user.',
915
- parameters: {
916
- type: 'object',
917
- properties: {
918
- userId: {
919
- type: 'string',
920
- description: 'The user ID whose friend request to accept',
921
- },
922
- },
923
- required: ['userId'],
924
- additionalProperties: false,
925
- },
926
- async execute(_, params) {
927
- const c = await requireClient();
928
- if ('error' in c) return c.error;
929
- try {
930
- const result = await c.sdk.users.acceptFriendRequest(
931
- String(params.userId ?? ''),
932
- );
933
- return textResult(JSON.stringify(result, null, 2));
934
- } catch (err) {
935
- return textResult(
936
- `Failed to accept friend request: ${err instanceof Error ? err.message : String(err)}`,
937
- true,
938
- );
939
- }
940
- },
941
- });
942
-
943
- api.registerTool({
944
- name: 'dim_list_friends',
945
- description: "List the authenticated user's friends with pagination.",
946
- parameters: {
947
- type: 'object',
948
- properties: {
949
- page: { type: 'number', description: 'Page number (default: 1)' },
950
- limit: {
951
- type: 'number',
952
- description: 'Friends per page (default: 20)',
953
- },
954
- search: { type: 'string', description: 'Filter friends by username' },
955
- },
956
- additionalProperties: false,
957
- },
958
- async execute(_, params) {
959
- const c = await requireClient();
960
- if ('error' in c) return c.error;
961
- if (!c.currentUserId)
962
- return textResult('Not authenticated. Call dim_login first.', true);
963
- try {
964
- const result = await c.sdk.users.getFriends(
965
- c.currentUserId,
966
- typeof params.page === 'number' ? params.page : 1,
967
- typeof params.limit === 'number' ? params.limit : 20,
968
- typeof params.search === 'string' ? params.search : undefined,
969
- );
970
- return textResult(JSON.stringify(result, null, 2));
971
- } catch (err) {
972
- return textResult(
973
- `Failed to list friends: ${err instanceof Error ? err.message : String(err)}`,
974
- true,
975
- );
976
- }
977
- },
978
- });
979
-
980
- api.registerTool({
981
- name: 'dim_get_incoming_friend_requests',
982
- description: 'List pending incoming friend requests.',
983
- parameters: { type: 'object', properties: {}, additionalProperties: false },
984
- async execute() {
985
- const c = await requireClient();
986
- if ('error' in c) return c.error;
987
- try {
988
- const result = await c.sdk.users.getIncomingFriendRequests();
989
- return textResult(JSON.stringify(result, null, 2));
990
- } catch (err) {
991
- return textResult(
992
- `Failed to get requests: ${err instanceof Error ? err.message : String(err)}`,
993
- true,
994
- );
995
- }
996
- },
997
- });
998
-
999
- // --- Chat ---
1000
- api.registerTool({
1001
- name: 'dim_send_message',
1002
- description:
1003
- "Send a chat message in a specific context (lobby, game, DM, or global chat). For DMs, the contextId is the other user's ID.",
1004
- parameters: {
1005
- type: 'object',
1006
- properties: {
1007
- contextType: {
1008
- type: 'string',
1009
- enum: ['lobby', 'game', 'dm', 'global'],
1010
- description: 'Chat context type',
1011
- },
1012
- contextId: {
1013
- type: 'string',
1014
- description:
1015
- 'Context ID: lobby ID, game ID, user ID (for DM), or "global"',
1016
- },
1017
- message: {
1018
- type: 'string',
1019
- description: 'Message text (1-500 characters)',
1020
- },
1021
- },
1022
- required: ['contextType', 'contextId', 'message'],
1023
- additionalProperties: false,
1024
- },
1025
- async execute(_, params) {
1026
- const c = await requireClient();
1027
- if ('error' in c) return c.error;
1028
- try {
1029
- const result = await c.sdk.chat.sendMessage(
1030
- {
1031
- type: params.contextType as 'lobby' | 'game' | 'dm' | 'global',
1032
- id: String(params.contextId ?? ''),
1033
- },
1034
- String(params.message ?? ''),
1035
- );
1036
- return textResult(JSON.stringify(result, null, 2));
1037
- } catch (err) {
1038
- return textResult(
1039
- `Failed to send message: ${err instanceof Error ? err.message : String(err)}`,
1040
- true,
1041
- );
1042
- }
1043
- },
1044
- });
1045
-
1046
- api.registerTool({
1047
- name: 'dim_get_chat_history',
1048
- description:
1049
- 'Get chat history for a context (lobby, game, DM, or global). Returns messages in chronological order.',
1050
- parameters: {
1051
- type: 'object',
1052
- properties: {
1053
- contextType: {
1054
- type: 'string',
1055
- enum: ['lobby', 'game', 'dm', 'global'],
1056
- description: 'Chat context type',
1057
- },
1058
- contextId: {
1059
- type: 'string',
1060
- description:
1061
- 'Context ID: lobby ID, game ID, user ID (for DM), or "global"',
1062
- },
1063
- limit: {
1064
- type: 'number',
1065
- description: 'Max messages to return (default: 50)',
1066
- },
1067
- },
1068
- required: ['contextType', 'contextId'],
1069
- additionalProperties: false,
1070
- },
1071
- async execute(_, params) {
1072
- const c = await requireClient();
1073
- if ('error' in c) return c.error;
1074
- const limit = typeof params.limit === 'number' ? params.limit : 50;
1075
- try {
1076
- const messages = await c.sdk.chat.getChatHistory(
1077
- {
1078
- type: params.contextType as 'lobby' | 'game' | 'dm' | 'global',
1079
- id: String(params.contextId ?? ''),
1080
- },
1081
- limit,
1082
- );
1083
- return textResult(JSON.stringify(messages, null, 2));
1084
- } catch (err) {
1085
- return textResult(
1086
- `Failed to get chat history: ${err instanceof Error ? err.message : String(err)}`,
1087
- true,
1088
- );
1089
- }
1090
- },
1091
- });
1092
-
1093
- api.registerTool({
1094
- name: 'dim_send_dm',
1095
- description: 'Send a direct message to another user by their user ID.',
1096
- parameters: {
1097
- type: 'object',
1098
- properties: {
1099
- userId: { type: 'string', description: 'The user ID to send a DM to' },
1100
- message: {
1101
- type: 'string',
1102
- description: 'Message text (1-500 characters)',
1103
- },
1104
- },
1105
- required: ['userId', 'message'],
1106
- additionalProperties: false,
1107
- },
1108
- async execute(_, params) {
1109
- const c = await requireClient();
1110
- if ('error' in c) return c.error;
1111
- try {
1112
- const result = await c.sdk.chat.sendMessage(
1113
- { type: 'dm', id: String(params.userId ?? '') },
1114
- String(params.message ?? ''),
1115
- );
1116
- return textResult(JSON.stringify(result, null, 2));
1117
- } catch (err) {
1118
- return textResult(
1119
- `Failed to send DM: ${err instanceof Error ? err.message : String(err)}`,
1120
- true,
1121
- );
1122
- }
1123
- },
1124
- });
1125
-
1126
- api.registerTool({
1127
- name: 'dim_list_dm_threads',
1128
- description:
1129
- 'List all DM conversation threads with last message info and unread counts.',
1130
- parameters: { type: 'object', properties: {}, additionalProperties: false },
1131
- async execute() {
1132
- const c = await requireClient();
1133
- if ('error' in c) return c.error;
1134
- try {
1135
- const threads = await c.sdk.chat.listDmThreads();
1136
- return textResult(JSON.stringify(threads, null, 2));
1137
- } catch (err) {
1138
- return textResult(
1139
- `Failed to list DM threads: ${err instanceof Error ? err.message : String(err)}`,
1140
- true,
1141
- );
1142
- }
1143
- },
1144
- });
1145
-
1146
- // --- Games ---
1147
- api.registerTool({
1148
- name: 'dim_list_games',
1149
- description:
1150
- 'List all available game types on DIM with player counts and descriptions. Games include: Rock-Paper-Scissors, Chess, Tic-Tac-Toe, Connect Four, Nim, Dots and Boxes.',
1151
- parameters: { type: 'object', properties: {}, additionalProperties: false },
1152
- async execute() {
1153
- const c = await requireClient();
1154
- if ('error' in c) return c.error;
1155
- try {
1156
- const games = await c.sdk.games.getAvailableGames();
1157
- return textResult(JSON.stringify(games, null, 2));
1158
- } catch (err) {
1159
- return textResult(
1160
- `Failed to list games: ${err instanceof Error ? err.message : String(err)}`,
1161
- true,
1162
- );
1163
- }
1164
- },
1165
- });
1166
-
1167
- api.registerTool({
1168
- name: 'dim_get_game_metrics',
1169
- description:
1170
- 'Get real-time metrics for all games: active players, live games, and money in play per game type.',
1171
- parameters: { type: 'object', properties: {}, additionalProperties: false },
1172
- async execute() {
1173
- const c = await requireClient();
1174
- if ('error' in c) return c.error;
1175
- try {
1176
- const metrics = await c.sdk.games.getMetrics();
1177
- return textResult(JSON.stringify(metrics, null, 2));
1178
- } catch (err) {
1179
- return textResult(
1180
- `Failed to get metrics: ${err instanceof Error ? err.message : String(err)}`,
1181
- true,
1182
- );
1183
- }
1184
- },
1185
- });
1186
-
1187
- api.registerTool({
1188
- name: 'dim_create_lobby',
1189
- description:
1190
- 'Create a new game lobby. The agent becomes the lobby creator. Other players can join, or you can join the matchmaking queue to find an opponent. Bet amount is in USDC dollars (e.g., 1.00 for $1.00). A 1% fee (min 1 cent) is charged per player.',
1191
- parameters: {
1192
- type: 'object',
1193
- properties: {
1194
- gameType: {
1195
- type: 'string',
1196
- description:
1197
- 'Game type ID (e.g., "rock-paper-scissors", "chess", "tic-tac-toe", "connect-four")',
1198
- },
1199
- betAmount: {
1200
- type: 'number',
1201
- description:
1202
- 'Bet amount in USDC dollars (e.g., 1.00 for $1.00, 5.00 for $5.00, 10.00 for $10.00)',
1203
- },
1204
- },
1205
- required: ['gameType'],
1206
- additionalProperties: false,
1207
- },
1208
- async execute(_, params) {
1209
- const c = await requireClient();
1210
- if ('error' in c) return c.error;
1211
- const gameType = String(params.gameType ?? '');
1212
- const betDollars =
1213
- typeof params.betAmount === 'number' ? params.betAmount : 0;
1214
- const betAmount =
1215
- betDollars > 0 ? Math.round(betDollars * 1_000_000) : undefined;
1216
- if (betDollars > 0) {
1217
- const limitErr = checkSpendLimit(c, betDollars, true);
1218
- if (limitErr) return textResult(limitErr, true);
1219
- }
1220
- try {
1221
- const lobby = await c.sdk.lobbies.createLobby(gameType, betAmount);
1222
- if (betAmount) c.recordSpend(betAmount);
1223
- return textResult(
1224
- JSON.stringify(
1225
- {
1226
- lobbyId: lobby.id,
1227
- gameType: lobby.gameType,
1228
- status: lobby.status,
1229
- betAmount: lobby.betAmount,
1230
- betFormatted: lobby.betAmount
1231
- ? `$${(lobby.betAmount / 1_000_000).toFixed(2)}`
1232
- : 'Free',
1233
- maxPlayers: lobby.maxPlayers,
1234
- players: lobby.players.length,
1235
- hint: 'Use dim_join_queue to find an opponent, or share the lobbyId for someone to join directly.',
1236
- },
1237
- null,
1238
- 2,
1239
- ),
1240
- );
1241
- } catch (err) {
1242
- return textResult(
1243
- `Failed to create lobby: ${err instanceof Error ? err.message : String(err)}`,
1244
- true,
1245
- );
1246
- }
1247
- },
1248
- });
1249
-
1250
- api.registerTool({
1251
- name: 'dim_join_queue',
1252
- description:
1253
- 'Join the matchmaking queue for a lobby. If matched immediately, returns gameId — start playing. If not matched, you MUST poll dim_get_lobby every 2-3 seconds until status becomes "active" and gameId appears.',
1254
- parameters: {
1255
- type: 'object',
1256
- properties: {
1257
- lobbyId: {
1258
- type: 'string',
1259
- description: 'The lobby ID to join the queue for',
1260
- },
1261
- },
1262
- required: ['lobbyId'],
1263
- additionalProperties: false,
1264
- },
1265
- async execute(_, params) {
1266
- const c = await requireClient();
1267
- if ('error' in c) return c.error;
1268
- try {
1269
- await c.ensureConnected();
1270
- const lobby = await c.sdk.lobbies.joinQueue(
1271
- String(params.lobbyId ?? ''),
1272
- );
1273
- const matched = lobby.status === 'active' && lobby.gameId;
1274
- return textResult(
1275
- JSON.stringify(
1276
- {
1277
- lobbyId: lobby.id,
1278
- status: lobby.status,
1279
- gameId: lobby.gameId || null,
1280
- matched: !!matched,
1281
- hint: matched
1282
- ? `Game started! Use dim_get_game_state with gameId "${lobby.gameId}" to see the game state, then dim_submit_action to play.`
1283
- : 'Waiting for an opponent. Poll dim_get_lobby to check for a match.',
1284
- },
1285
- null,
1286
- 2,
1287
- ),
1288
- );
1289
- } catch (err) {
1290
- return textResult(
1291
- `Failed to join queue: ${err instanceof Error ? err.message : String(err)}`,
1292
- true,
1293
- );
1294
- }
1295
- },
1296
- });
1297
-
1298
- api.registerTool({
1299
- name: 'dim_get_lobby',
1300
- description:
1301
- 'Get the current state of a lobby including players, status, and gameId (if matched).',
1302
- parameters: {
1303
- type: 'object',
1304
- properties: {
1305
- lobbyId: { type: 'string', description: 'The lobby ID to check' },
1306
- },
1307
- required: ['lobbyId'],
1308
- additionalProperties: false,
1309
- },
1310
- async execute(_, params) {
1311
- const c = await requireClient();
1312
- if ('error' in c) return c.error;
1313
- try {
1314
- const lobby = await c.sdk.lobbies.getLobby(
1315
- String(params.lobbyId ?? ''),
1316
- );
1317
- return textResult(JSON.stringify(lobby, null, 2));
1318
- } catch (err) {
1319
- return textResult(
1320
- `Failed to get lobby: ${err instanceof Error ? err.message : String(err)}`,
1321
- true,
1322
- );
1323
- }
1324
- },
1325
- });
1326
-
1327
- api.registerTool({
1328
- name: 'dim_get_game_state',
1329
- description:
1330
- 'Get the current state of an active game including round info, scores, timer, and available actions. The response format depends on the game type.',
1331
- parameters: {
1332
- type: 'object',
1333
- properties: {
1334
- gameId: { type: 'string', description: 'The game ID to get state for' },
1335
- },
1336
- required: ['gameId'],
1337
- additionalProperties: false,
1338
- },
1339
- async execute(_, params) {
1340
- const c = await requireClient();
1341
- if ('error' in c) return c.error;
1342
- try {
1343
- const state = await c.sdk.games.getGameState(
1344
- String(params.gameId ?? ''),
1345
- );
1346
- return textResult(JSON.stringify(state, null, 2));
1347
- } catch (err) {
1348
- return textResult(
1349
- `Failed to get game state: ${err instanceof Error ? err.message : String(err)}`,
1350
- true,
1351
- );
1352
- }
1353
- },
1354
- });
1355
-
1356
- api.registerTool({
1357
- name: 'dim_submit_action',
1358
- description:
1359
- 'Submit a game action. The action format depends on the game type.\n\n' +
1360
- 'Rock-Paper-Scissors: gameType="rock-paper-scissors", action="play", payload={ action: "rock"|"paper"|"scissors" }\n' +
1361
- 'Chess: gameType="chess", action="move", payload={ from: "e2", to: "e4" }\n' +
1362
- 'Tic-Tac-Toe: gameType="tic-tac-toe", action="place_mark", payload={ row: 0-2, col: 0-2 }\n' +
1363
- 'Connect Four: gameType="connect-four", action="drop_disc", payload={ column: 0-6 }',
1364
- parameters: {
1365
- type: 'object',
1366
- properties: {
1367
- gameId: {
1368
- type: 'string',
1369
- description: 'The game ID to submit an action for',
1370
- },
1371
- gameType: {
1372
- type: 'string',
1373
- description:
1374
- 'Game type (e.g., "rock-paper-scissors", "chess", "tic-tac-toe", "connect-four")',
1375
- },
1376
- action: {
1377
- type: 'string',
1378
- description:
1379
- 'Action type (e.g., "play", "move", "place_mark", "drop_disc")',
1380
- },
1381
- payload: {
1382
- type: 'object',
1383
- description:
1384
- 'Action payload (game-specific, see tool description for format)',
1385
- },
1386
- },
1387
- required: ['gameId', 'gameType', 'action', 'payload'],
1388
- additionalProperties: false,
1389
- },
1390
- async execute(_, params) {
1391
- const c = await requireClient();
1392
- if ('error' in c) return c.error;
1393
- try {
1394
- const gameAction = {
1395
- gameType: String(params.gameType ?? ''),
1396
- action: String(params.action ?? ''),
1397
- payload: (params.payload as Record<string, unknown>) ?? {},
1398
- } as ValidAction;
1399
- await c.sdk.games.submitAction(String(params.gameId ?? ''), gameAction);
1400
- return textResult(
1401
- JSON.stringify(
1402
- {
1403
- success: true,
1404
- gameId: params.gameId,
1405
- action: params.action,
1406
- hint: 'Use dim_get_game_state to see the updated game state.',
1407
- },
1408
- null,
1409
- 2,
1410
- ),
1411
- );
1412
- } catch (err) {
1413
- return textResult(
1414
- `Failed to submit action: ${err instanceof Error ? err.message : String(err)}`,
1415
- true,
1416
- );
1417
- }
1418
- },
1419
- });
1420
-
1421
- api.registerTool({
1422
- name: 'dim_donate_to_pot',
1423
- description:
1424
- 'Donate USDC to a game pot (spectator donation). One-call flow: prepare, sign, and submit internally.',
1425
- parameters: {
1426
- type: 'object',
1427
- properties: {
1428
- gameId: { type: 'string', description: 'The game ID to donate to' },
1429
- amount: {
1430
- type: 'number',
1431
- description: 'Amount in USDC dollars (minimum $0.10)',
1432
- },
1433
- },
1434
- required: ['gameId', 'amount'],
1435
- additionalProperties: false,
1436
- },
1437
- async execute(_, params) {
1438
- const c = await requireClient();
1439
- if ('error' in c) return c.error;
1440
- const amount = Number(params.amount ?? 0);
1441
- const limitErr = checkSpendLimit(c, amount);
1442
- if (limitErr) return textResult(limitErr, true);
1443
- try {
1444
- const amountMinor = Math.round(amount * 1_000_000);
1445
- const result = await c.sdk.games.sendDonation(
1446
- String(params.gameId ?? ''),
1447
- amountMinor,
1448
- );
1449
- c.recordSpend(amountMinor);
1450
- return textResult(
1451
- JSON.stringify(
1452
- {
1453
- success: true,
1454
- gameId: params.gameId,
1455
- signature: result.signature,
1456
- status: result.status,
1457
- amountDonated: `$${amount.toFixed(2)}`,
1458
- amountMinor: result.amountMinor,
1459
- escrowAddress: result.escrowAddress,
1460
- },
1461
- null,
1462
- 2,
1463
- ),
1464
- );
1465
- } catch (err) {
1466
- return textResult(
1467
- `Failed to donate to pot: ${err instanceof Error ? err.message : String(err)}`,
1468
- true,
1469
- );
1470
- }
1471
- },
1472
- });
1473
-
1474
- api.registerTool({
1475
- name: 'dim_get_game',
1476
- description:
1477
- 'Get game info including status (active/completed/abandoned), players, and lobby IDs.',
1478
- parameters: {
1479
- type: 'object',
1480
- properties: { gameId: { type: 'string', description: 'The game ID' } },
1481
- required: ['gameId'],
1482
- additionalProperties: false,
1483
- },
1484
- async execute(_, params) {
1485
- const c = await requireClient();
1486
- if ('error' in c) return c.error;
1487
- try {
1488
- const game = await c.sdk.games.getGame(String(params.gameId ?? ''));
1489
- return textResult(JSON.stringify(game, null, 2));
1490
- } catch (err) {
1491
- return textResult(
1492
- `Failed to get game: ${err instanceof Error ? err.message : String(err)}`,
1493
- true,
1494
- );
1495
- }
1496
- },
1497
- });
1498
-
1499
- // --- Challenges ---
1500
- api.registerTool({
1501
- name: 'dim_challenge_user',
1502
- description:
1503
- 'Challenge another user to a game for USDC. The target user will receive a notification. Amount must be between $1 and $1000. You can specify the target by userId or username.',
1504
- parameters: {
1505
- type: 'object',
1506
- properties: {
1507
- gameType: {
1508
- type: 'string',
1509
- description:
1510
- 'Game type (e.g., "rock-paper-scissors", "chess", "tic-tac-toe")',
1511
- },
1512
- amount: {
1513
- type: 'number',
1514
- description: 'Challenge amount in USDC dollars (e.g., 5.00 for $5)',
1515
- },
1516
- targetUsername: {
1517
- type: 'string',
1518
- description: 'Username of the user to challenge',
1519
- },
1520
- targetUserId: {
1521
- type: 'string',
1522
- description: 'User ID of the user to challenge',
1523
- },
1524
- },
1525
- required: ['gameType', 'amount'],
1526
- additionalProperties: false,
1527
- },
1528
- async execute(_, params) {
1529
- const c = await requireClient();
1530
- if ('error' in c) return c.error;
1531
- const targetUsername =
1532
- typeof params.targetUsername === 'string'
1533
- ? params.targetUsername
1534
- : undefined;
1535
- const targetUserId =
1536
- typeof params.targetUserId === 'string'
1537
- ? params.targetUserId
1538
- : undefined;
1539
- if (!targetUsername && !targetUserId) {
1540
- return textResult(
1541
- 'Must provide either targetUsername or targetUserId.',
1542
- true,
1543
- );
1544
- }
1545
- try {
1546
- const amountMinor = Math.round(Number(params.amount ?? 0) * 1_000_000);
1547
- const result = await c.sdk.challenges.create({
1548
- gameType: String(params.gameType ?? ''),
1549
- amount: amountMinor,
1550
- targetUsername,
1551
- targetUserId,
1552
- });
1553
- return textResult(
1554
- JSON.stringify(
1555
- {
1556
- ...result,
1557
- amountFormatted: `$${Number(params.amount).toFixed(2)}`,
1558
- hint: 'The challenged user will receive a notification. When they accept, a lobby is created automatically.',
1559
- },
1560
- null,
1561
- 2,
1562
- ),
1563
- );
1564
- } catch (err) {
1565
- return textResult(
1566
- `Failed to create challenge: ${err instanceof Error ? err.message : String(err)}`,
1567
- true,
1568
- );
1569
- }
1570
- },
1571
- });
1572
-
1573
- api.registerTool({
1574
- name: 'dim_accept_challenge',
1575
- description:
1576
- 'Accept a challenge that was sent to you. Creates a lobby with the challenge amount and returns the lobby/game info.',
1577
- parameters: {
1578
- type: 'object',
1579
- properties: {
1580
- challengeId: {
1581
- type: 'string',
1582
- description: 'The challenge ID to accept',
1583
- },
1584
- },
1585
- required: ['challengeId'],
1586
- additionalProperties: false,
1587
- },
1588
- async execute(_, params) {
1589
- const c = await requireClient();
1590
- if ('error' in c) return c.error;
1591
- try {
1592
- const result = await c.sdk.challenges.accept(
1593
- String(params.challengeId ?? ''),
1594
- );
1595
- return textResult(
1596
- JSON.stringify(
1597
- {
1598
- ...result,
1599
- hint: result.gameId
1600
- ? `Game started! Use dim_get_game_state with gameId "${result.gameId}".`
1601
- : `Lobby created: ${result.lobbyId}. Use dim_join_queue to start matchmaking.`,
1602
- },
1603
- null,
1604
- 2,
1605
- ),
1606
- );
1607
- } catch (err) {
1608
- return textResult(
1609
- `Failed to accept challenge: ${err instanceof Error ? err.message : String(err)}`,
1610
- true,
1611
- );
1612
- }
1613
- },
1614
- });
1615
-
1616
- // --- Referrals ---
1617
- api.registerTool({
1618
- name: 'dim_get_referral_summary',
1619
- description:
1620
- 'Get your referral summary including your referral code (your username), referral link, total referrals per level, and earnings (pending + claimed). DIM has a 3-level referral system: Level 1 = 30% of game fees, Level 2 = 3%, Level 3 = 2%.',
1621
- parameters: { type: 'object', properties: {}, additionalProperties: false },
1622
- async execute() {
1623
- const c = await requireClient();
1624
- if ('error' in c) return c.error;
1625
- try {
1626
- const summary = await c.sdk.referrals.getSummary();
1627
- const pendingDollars = summary.earnings.pending / 1_000_000;
1628
- const claimedDollars = summary.earnings.claimed / 1_000_000;
1629
- const result: Record<string, unknown> = {
1630
- ...summary,
1631
- pendingFormatted: `$${pendingDollars.toFixed(2)}`,
1632
- claimedFormatted: `$${claimedDollars.toFixed(2)}`,
1633
- referralRates: {
1634
- level1: '30% of referred user game fees',
1635
- level2: '3% of level-2 referral game fees',
1636
- level3: '2% of level-3 referral game fees',
1637
- },
1638
- referredUserBenefit: '10% fee discount (0.9% instead of 1%)',
1639
- };
1640
- if (!summary.hasReferrer) {
1641
- result.applyReferralHint =
1642
- 'You have no referrer yet. Use dim_apply_referral_code to apply one and get a 10% fee discount.';
1643
292
  }
1644
- return textResult(JSON.stringify(result, null, 2));
1645
- } catch (err) {
1646
- return textResult(
1647
- `Failed to get referral summary: ${err instanceof Error ? err.message : String(err)}`,
1648
- true,
1649
- );
1650
- }
1651
- },
1652
- });
1653
-
1654
- api.registerTool({
1655
- name: 'dim_get_referral_tree',
1656
- description:
1657
- 'Get your referral tree at a specific level. Level 1 = direct referrals, Level 2 = referrals of your referrals, Level 3 = one more level deep.',
1658
- parameters: {
1659
- type: 'object',
1660
- properties: {
1661
- level: {
1662
- type: 'string',
1663
- enum: ['1', '2', '3'],
1664
- description: 'Referral level to view (1, 2, or 3)',
1665
- },
1666
- limit: {
1667
- type: 'number',
1668
- description: 'Max results (default: 50, max: 200)',
1669
- },
1670
- cursor: { type: 'string', description: 'Pagination cursor' },
1671
293
  },
1672
- required: ['level'],
1673
- additionalProperties: false,
1674
- },
1675
- async execute(_, params) {
1676
- const c = await requireClient();
1677
- if ('error' in c) return c.error;
1678
- try {
1679
- const tree = await c.sdk.referrals.getTree({
1680
- level: parseInt(String(params.level ?? '1'), 10) as 1 | 2 | 3,
1681
- limit: typeof params.limit === 'number' ? params.limit : 50,
1682
- cursor: typeof params.cursor === 'string' ? params.cursor : undefined,
1683
- });
1684
- return textResult(JSON.stringify(tree, null, 2));
1685
- } catch (err) {
1686
- return textResult(
1687
- `Failed to get referral tree: ${err instanceof Error ? err.message : String(err)}`,
1688
- true,
1689
- );
1690
- }
1691
- },
1692
- });
1693
-
1694
- api.registerTool({
1695
- name: 'dim_get_referral_rewards',
1696
- description:
1697
- 'Get your referral reward history. Filter by status: PENDING (unclaimed), CLAIMED (already claimed), CANCELLED.',
1698
- parameters: {
1699
- type: 'object',
1700
- properties: {
1701
- status: {
1702
- type: 'string',
1703
- enum: ['PENDING', 'CLAIMED', 'CANCELLED'],
1704
- description: 'Filter by reward status',
1705
- },
1706
- limit: {
1707
- type: 'number',
1708
- description: 'Max results (default: 50, max: 200)',
1709
- },
1710
- cursor: { type: 'string', description: 'Pagination cursor' },
1711
- },
1712
- additionalProperties: false,
1713
- },
1714
- async execute(_, params) {
1715
- const c = await requireClient();
1716
- if ('error' in c) return c.error;
1717
- try {
1718
- const rewards = await c.sdk.referrals.getRewards({
1719
- status: params.status as
1720
- | 'PENDING'
1721
- | 'CLAIMED'
1722
- | 'CANCELLED'
1723
- | undefined,
1724
- limit: typeof params.limit === 'number' ? params.limit : 50,
1725
- cursor: typeof params.cursor === 'string' ? params.cursor : undefined,
1726
- });
1727
- return textResult(JSON.stringify(rewards, null, 2));
1728
- } catch (err) {
1729
- return textResult(
1730
- `Failed to get referral rewards: ${err instanceof Error ? err.message : String(err)}`,
1731
- true,
1732
- );
1733
- }
1734
- },
1735
- });
1736
-
1737
- api.registerTool({
1738
- name: 'dim_claim_referral_rewards',
1739
- description:
1740
- 'Claim all pending referral rewards. USDC is transferred from escrow to your wallet. Returns the claimed amount and transaction signature.',
1741
- parameters: { type: 'object', properties: {}, additionalProperties: false },
1742
- async execute() {
1743
- const c = await requireClient();
1744
- if ('error' in c) return c.error;
1745
- try {
1746
- const result = await c.sdk.referrals.claimRewards();
1747
- const claimedDollars = result.claimedAmount / 1_000_000;
1748
- return textResult(
1749
- JSON.stringify(
1750
- { ...result, claimedFormatted: `$${claimedDollars.toFixed(2)}` },
1751
- null,
1752
- 2,
1753
- ),
1754
- );
1755
- } catch (err) {
1756
- return textResult(
1757
- `Failed to claim rewards: ${err instanceof Error ? err.message : String(err)}`,
1758
- true,
1759
- );
1760
- }
1761
- },
1762
- });
1763
-
1764
- api.registerTool({
1765
- name: 'dim_apply_referral_code',
1766
- description:
1767
- "Apply a referral code to your account (another user's username). Can only be applied once — if you already have a referrer, this will fail. Gives you a 10% fee discount.",
1768
- parameters: {
1769
- type: 'object',
1770
- properties: {
1771
- referralCode: {
1772
- type: 'string',
1773
- description: "The referral code to apply (another user's username)",
1774
- },
1775
- },
1776
- required: ['referralCode'],
1777
- additionalProperties: false,
1778
- },
1779
- async execute(_, params) {
1780
- const c = await requireClient();
1781
- if ('error' in c) return c.error;
1782
- try {
1783
- const result = await c.sdk.referrals.applyCode(
1784
- String(params.referralCode ?? ''),
1785
- );
1786
- return textResult(
1787
- JSON.stringify(
1788
- {
1789
- ...result,
1790
- message: `Referral code applied! ${result.referrerUsername} is now your referrer. You get a 10% fee discount on all games.`,
1791
- },
1792
- null,
1793
- 2,
1794
- ),
1795
- );
1796
- } catch (err) {
1797
- return textResult(
1798
- `Failed to apply referral code: ${err instanceof Error ? err.message : String(err)}`,
1799
- true,
1800
- );
1801
- }
1802
- },
1803
- });
1804
-
1805
- // --- Support ---
1806
- api.registerTool({
1807
- name: 'dim_create_support_ticket',
1808
- description:
1809
- 'Create a support ticket to contact the DIM team. Use this when you need help with your account, payments, games, bugs, or any other issue. Categories: BUG, FEATURE_REQUEST, QUESTION, ACCOUNT, PAYMENT, GAME, TECHNICAL, OTHER.',
1810
- parameters: {
1811
- type: 'object',
1812
- properties: {
1813
- message: {
1814
- type: 'string',
1815
- description: 'Describe the issue or question (max 2000 chars)',
1816
- },
1817
- category: {
1818
- type: 'string',
1819
- enum: [
1820
- 'BUG',
1821
- 'FEATURE_REQUEST',
1822
- 'QUESTION',
1823
- 'ACCOUNT',
1824
- 'PAYMENT',
1825
- 'GAME',
1826
- 'TECHNICAL',
1827
- 'OTHER',
1828
- ],
1829
- description: 'Ticket category (default: OTHER)',
1830
- },
1831
- subject: {
1832
- type: 'string',
1833
- description:
1834
- 'Short subject line (auto-generated from category if omitted)',
1835
- },
1836
- },
1837
- required: ['message'],
1838
- additionalProperties: false,
1839
- },
1840
- async execute(_, params) {
1841
- const c = await requireClient();
1842
- if ('error' in c) return c.error;
1843
- try {
1844
- const ticket = await c.sdk.support.create({
1845
- message: String(params.message ?? ''),
1846
- category: params.category as
1847
- | 'BUG'
1848
- | 'FEATURE_REQUEST'
1849
- | 'QUESTION'
1850
- | 'ACCOUNT'
1851
- | 'PAYMENT'
1852
- | 'GAME'
1853
- | 'TECHNICAL'
1854
- | 'OTHER'
1855
- | undefined,
1856
- subject:
1857
- typeof params.subject === 'string' ? params.subject : undefined,
1858
- });
1859
- return textResult(
1860
- JSON.stringify(
1861
- {
1862
- ...ticket,
1863
- hint: 'Ticket created successfully. You can check its status with dim_get_my_tickets or add follow-up messages with dim_add_ticket_message.',
1864
- },
1865
- null,
1866
- 2,
1867
- ),
1868
- );
1869
- } catch (err) {
1870
- return textResult(
1871
- `Failed to create support ticket: ${err instanceof Error ? err.message : String(err)}`,
1872
- true,
1873
- );
1874
- }
1875
- },
1876
- });
1877
-
1878
- api.registerTool({
1879
- name: 'dim_get_my_tickets',
1880
- description:
1881
- 'Get your support tickets. Filter by status (OPEN, IN_PROGRESS, WAITING_REPLY, RESOLVED, CLOSED) or category.',
1882
- parameters: {
1883
- type: 'object',
1884
- properties: {
1885
- status: {
1886
- type: 'string',
1887
- enum: ['OPEN', 'IN_PROGRESS', 'WAITING_REPLY', 'RESOLVED', 'CLOSED'],
1888
- description: 'Filter by ticket status',
1889
- },
1890
- category: {
1891
- type: 'string',
1892
- enum: [
1893
- 'BUG',
1894
- 'FEATURE_REQUEST',
1895
- 'QUESTION',
1896
- 'ACCOUNT',
1897
- 'PAYMENT',
1898
- 'GAME',
1899
- 'TECHNICAL',
1900
- 'OTHER',
1901
- ],
1902
- description: 'Filter by category',
1903
- },
1904
- page: { type: 'number', description: 'Page number (default: 1)' },
1905
- limit: {
1906
- type: 'number',
1907
- description: 'Results per page (default: 10)',
1908
- },
1909
- },
1910
- additionalProperties: false,
1911
- },
1912
- async execute(_, params) {
1913
- const c = await requireClient();
1914
- if ('error' in c) return c.error;
1915
- try {
1916
- const tickets = await c.sdk.support.getMyTickets({
1917
- status: params.status as any,
1918
- category: params.category as any,
1919
- page: typeof params.page === 'number' ? params.page : undefined,
1920
- limit: typeof params.limit === 'number' ? params.limit : undefined,
1921
- });
1922
- return textResult(JSON.stringify(tickets, null, 2));
1923
- } catch (err) {
1924
- return textResult(
1925
- `Failed to get tickets: ${err instanceof Error ? err.message : String(err)}`,
1926
- true,
1927
- );
1928
- }
1929
- },
1930
- });
1931
-
1932
- api.registerTool({
1933
- name: 'dim_get_ticket',
1934
- description:
1935
- 'Get a specific support ticket with all messages. You can only view your own tickets.',
1936
- parameters: {
1937
- type: 'object',
1938
- properties: {
1939
- ticketId: { type: 'string', description: 'The ticket ID' },
1940
- },
1941
- required: ['ticketId'],
1942
- additionalProperties: false,
1943
- },
1944
- async execute(_, params) {
1945
- const c = await requireClient();
1946
- if ('error' in c) return c.error;
1947
- try {
1948
- const ticket = await c.sdk.support.getMyTicketById(
1949
- String(params.ticketId ?? ''),
1950
- );
1951
- return textResult(JSON.stringify(ticket, null, 2));
1952
- } catch (err) {
1953
- return textResult(
1954
- `Failed to get ticket: ${err instanceof Error ? err.message : String(err)}`,
1955
- true,
1956
- );
1957
- }
1958
- },
1959
- });
1960
-
1961
- api.registerTool({
1962
- name: 'dim_add_ticket_message',
1963
- description:
1964
- 'Add a follow-up message to an existing support ticket. You can only message your own tickets.',
1965
- parameters: {
1966
- type: 'object',
1967
- properties: {
1968
- ticketId: { type: 'string', description: 'The ticket ID' },
1969
- message: {
1970
- type: 'string',
1971
- description: 'Your follow-up message (max 2000 chars)',
1972
- },
1973
- },
1974
- required: ['ticketId', 'message'],
1975
- additionalProperties: false,
1976
- },
1977
- async execute(_, params) {
1978
- const c = await requireClient();
1979
- if ('error' in c) return c.error;
1980
- try {
1981
- const msg = await c.sdk.support.addMessage(
1982
- String(params.ticketId ?? ''),
1983
- String(params.message ?? ''),
1984
- );
1985
- return textResult(
1986
- JSON.stringify(
1987
- { ...msg, hint: 'Message added. The DIM team will be notified.' },
1988
- null,
1989
- 2,
1990
- ),
1991
- );
1992
- } catch (err) {
1993
- return textResult(
1994
- `Failed to add message: ${err instanceof Error ? err.message : String(err)}`,
1995
- true,
1996
- );
1997
- }
1998
- },
1999
- });
2000
-
2001
- api.registerTool({
2002
- name: 'dim_close_ticket',
2003
- description:
2004
- 'Close a support ticket. Only close if your issue has been resolved.',
2005
- parameters: {
2006
- type: 'object',
2007
- properties: {
2008
- ticketId: { type: 'string', description: 'The ticket ID to close' },
2009
- },
2010
- required: ['ticketId'],
2011
- additionalProperties: false,
2012
- },
2013
- async execute(_, params) {
2014
- const c = await requireClient();
2015
- if ('error' in c) return c.error;
2016
- try {
2017
- const ticket = await c.sdk.support.closeTicket(
2018
- String(params.ticketId ?? ''),
2019
- );
2020
- return textResult(JSON.stringify(ticket, null, 2));
2021
- } catch (err) {
2022
- return textResult(
2023
- `Failed to close ticket: ${err instanceof Error ? err.message : String(err)}`,
2024
- true,
2025
- );
2026
- }
2027
- },
2028
- });
2029
-
2030
- // --- Markets ---
2031
- api.registerTool({
2032
- name: 'dim_get_market',
2033
- description:
2034
- 'Get the prediction market state for an active game. Returns share prices (implied probabilities), total collateral locked, total volume, and resolution status.',
2035
- parameters: {
2036
- type: 'object',
2037
- properties: {
2038
- gameId: {
2039
- type: 'string',
2040
- description: 'The game ID to get market state for',
2041
- },
2042
- },
2043
- required: ['gameId'],
2044
- additionalProperties: false,
2045
- },
2046
- async execute(_, params) {
2047
- const c = await requireClient();
2048
- if ('error' in c) return c.error;
2049
- try {
2050
- const market = await c.sdk.markets.getMarket(
2051
- String(params.gameId ?? ''),
2052
- );
2053
- return textResult(JSON.stringify(market, null, 2));
2054
- } catch (err) {
2055
- return textResult(
2056
- `Failed to get market: ${err instanceof Error ? err.message : String(err)}`,
2057
- true,
2058
- );
2059
- }
2060
- },
2061
- });
2062
-
2063
- api.registerTool({
2064
- name: 'dim_buy_shares',
2065
- description:
2066
- 'Buy shares in a prediction market outcome. Pick which player you think will win and how much to bet. Amount is in USDC dollars (e.g., 1.00 for $1.00).',
2067
- parameters: {
2068
- type: 'object',
2069
- properties: {
2070
- gameId: { type: 'string', description: 'The game ID' },
2071
- outcomeId: {
2072
- type: 'string',
2073
- description: 'The outcome (player user ID) to buy shares of',
2074
- },
2075
- amount: {
2076
- type: 'number',
2077
- description: 'Amount in USDC dollars (e.g., 1.00 for $1.00)',
2078
- },
2079
- },
2080
- required: ['gameId', 'outcomeId', 'amount'],
2081
- additionalProperties: false,
2082
- },
2083
- async execute(_, params) {
2084
- const c = await requireClient();
2085
- if ('error' in c) return c.error;
2086
- const amount = Number(params.amount ?? 0);
2087
- const gameId = String(params.gameId ?? '');
2088
- const outcomeId = String(params.outcomeId ?? '');
2089
- const limitErr = checkSpendLimit(c, amount);
2090
- if (limitErr) return textResult(limitErr, true);
2091
- try {
2092
- const amountMinor = Math.round(amount * 1_000_000);
2093
- const { transaction: txBase64 } = await c.sdk.markets.prepareBuyOrder(
2094
- gameId,
2095
- outcomeId,
2096
- amountMinor,
2097
- );
2098
- const tx = Transaction.from(Buffer.from(txBase64, 'base64'));
2099
- tx.partialSign(c.getKeypair());
2100
- const signedTxBase64 = Buffer.from(tx.serialize()).toString('base64');
2101
- const result = await c.sdk.markets.submitBuyOrder(
2102
- gameId,
2103
- signedTxBase64,
2104
- outcomeId,
2105
- amountMinor,
2106
- );
2107
- c.recordSpend(amountMinor);
2108
- return textResult(
2109
- JSON.stringify(
2110
- {
2111
- success: true,
2112
- tradeId: result.tradeId,
2113
- sharesReceived: result.sharesReceived,
2114
- costPerShare: result.costPerShare,
2115
- amountSpent: `$${amount.toFixed(2)}`,
2116
- },
2117
- null,
2118
- 2,
2119
- ),
2120
- );
2121
- } catch (err) {
2122
- return textResult(
2123
- `Failed to buy shares: ${err instanceof Error ? err.message : String(err)}`,
2124
- true,
2125
- );
2126
- }
2127
- },
2128
- });
2129
-
2130
- api.registerTool({
2131
- name: 'dim_sell_shares',
2132
- description:
2133
- 'Sell shares to exit a prediction market position. Shares are in minor units (1 share = 1,000,000 minor units).',
2134
- parameters: {
2135
- type: 'object',
2136
- properties: {
2137
- gameId: { type: 'string', description: 'The game ID' },
2138
- outcomeId: {
2139
- type: 'string',
2140
- description: 'The outcome to sell shares of',
2141
- },
2142
- shares: {
2143
- type: 'number',
2144
- description:
2145
- 'Number of shares to sell in minor units (1 share = 1,000,000)',
2146
- },
2147
- },
2148
- required: ['gameId', 'outcomeId', 'shares'],
2149
- additionalProperties: false,
2150
- },
2151
- async execute(_, params) {
2152
- const c = await requireClient();
2153
- if ('error' in c) return c.error;
2154
- try {
2155
- const result = await c.sdk.markets.sellShares(
2156
- String(params.gameId ?? ''),
2157
- String(params.outcomeId ?? ''),
2158
- Number(params.shares ?? 0),
2159
- );
2160
- return textResult(
2161
- JSON.stringify(
2162
- {
2163
- success: true,
2164
- tradeId: result.tradeId,
2165
- amountReceived: result.amountReceived,
2166
- pricePerShare: result.pricePerShare,
2167
- newPrices: result.newPrices,
2168
- },
2169
- null,
2170
- 2,
2171
- ),
2172
- );
2173
- } catch (err) {
2174
- return textResult(
2175
- `Failed to sell shares: ${err instanceof Error ? err.message : String(err)}`,
2176
- true,
2177
- );
2178
- }
2179
- },
2180
- });
2181
-
2182
- api.registerTool({
2183
- name: 'dim_get_positions',
2184
- description:
2185
- 'Get your current prediction market positions for a game. Shows shares held, average cost, current value, and unrealized profit/loss.',
2186
- parameters: {
2187
- type: 'object',
2188
- properties: { gameId: { type: 'string', description: 'The game ID' } },
2189
- required: ['gameId'],
2190
- additionalProperties: false,
2191
- },
2192
- async execute(_, params) {
2193
- const c = await requireClient();
2194
- if ('error' in c) return c.error;
2195
- try {
2196
- const positions = await c.sdk.markets.getMyPositions(
2197
- String(params.gameId ?? ''),
2198
- );
2199
- return textResult(JSON.stringify(positions, null, 2));
2200
- } catch (err) {
2201
- return textResult(
2202
- `Failed to get positions: ${err instanceof Error ? err.message : String(err)}`,
2203
- true,
2204
- );
2205
- }
2206
- },
2207
- });
2208
-
2209
- api.registerTool({
2210
- name: 'dim_redeem_shares',
2211
- description:
2212
- 'Redeem winning shares after a prediction market has been resolved. Winners split the pool pro-rata by shares held, minus a 3% fee.',
2213
- parameters: {
2214
- type: 'object',
2215
- properties: {
2216
- gameId: {
2217
- type: 'string',
2218
- description: 'The game ID to redeem shares for',
2219
- },
2220
- },
2221
- required: ['gameId'],
2222
- additionalProperties: false,
2223
- },
2224
- async execute(_, params) {
2225
- const c = await requireClient();
2226
- if ('error' in c) return c.error;
2227
- try {
2228
- const result = await c.sdk.markets.redeemShares(
2229
- String(params.gameId ?? ''),
2230
- );
2231
- return textResult(JSON.stringify(result, null, 2));
2232
- } catch (err) {
2233
- return textResult(
2234
- `Failed to redeem shares: ${err instanceof Error ? err.message : String(err)}`,
2235
- true,
2236
- );
2237
- }
2238
- },
2239
- });
2240
-
2241
- api.registerTool({
2242
- name: 'dim_get_market_analytics',
2243
- description:
2244
- 'Get platform analytics for prediction markets. Shows total markets, orders, volume, fees, unique traders, and daily trends. Requires admin privileges.',
2245
- parameters: {
2246
- type: 'object',
2247
- properties: {
2248
- type: {
2249
- type: 'string',
2250
- enum: ['overview', 'daily', 'markets'],
2251
- description:
2252
- 'overview = aggregate totals, daily = per-day breakdown, markets = paginated list of all markets',
2253
- },
2254
- days: {
2255
- type: 'number',
2256
- description: 'Number of days for daily stats (default 30)',
2257
- },
2258
- page: {
2259
- type: 'number',
2260
- description: 'Page number for markets list (default 1)',
2261
- },
2262
- limit: {
2263
- type: 'number',
2264
- description: 'Items per page for markets list (default 20)',
2265
- },
2266
- },
2267
- required: ['type'],
2268
- additionalProperties: false,
2269
- },
2270
- async execute(_, params) {
2271
- const c = await requireClient();
2272
- if ('error' in c) return c.error;
2273
- try {
2274
- let data: unknown;
2275
- switch (String(params.type)) {
2276
- case 'overview':
2277
- data = await c.sdk.markets.getAdminStats();
2278
- break;
2279
- case 'daily':
2280
- data = await c.sdk.markets.getAdminDailyStats(
2281
- typeof params.days === 'number' ? params.days : 30,
2282
- );
2283
- break;
2284
- case 'markets':
2285
- data = await c.sdk.markets.getAdminMarkets(
2286
- typeof params.page === 'number' ? params.page : undefined,
2287
- typeof params.limit === 'number' ? params.limit : undefined,
2288
- );
2289
- break;
2290
- default:
2291
- return textResult(
2292
- 'Invalid type. Use overview, daily, or markets.',
2293
- true,
2294
- );
2295
- }
2296
- return textResult(JSON.stringify(data, null, 2));
2297
- } catch (err) {
2298
- return textResult(
2299
- `Failed to get market analytics: ${err instanceof Error ? err.message : String(err)}`,
2300
- true,
2301
- );
2302
- }
2303
- },
2304
- });
2305
-
2306
- // ── Agent config ─────────────────────────────────────────────────────
294
+ });
295
+ }
2307
296
 
297
+ // dim_list_instructions — special meta-tool
2308
298
  api.registerTool({
2309
- name: 'dim_get_agent_config',
2310
- description:
2311
- 'Get the agent autonomy configuration: what actions are allowed (friends, DMs, games, chat, referral promotion), spending limits, and current daily spend.',
299
+ name: 'dim_list_instructions',
300
+ description: 'List all available DIM tools with short descriptions.',
2312
301
  parameters: { type: 'object', properties: {}, additionalProperties: false },
2313
302
  async execute() {
2314
- const c = await requireClient();
2315
- if ('error' in c) return c.error;
2316
303
  return textResult(
2317
304
  JSON.stringify(
2318
305
  {
2319
- autoAcceptFriendRequests:
2320
- pluginConfig?.autoAcceptFriendRequests ?? false,
2321
- autoReplyDms: pluginConfig?.autoReplyDms ?? false,
2322
- autoPlayGames: pluginConfig?.autoPlayGames ?? false,
2323
- maxBetPerGame: pluginConfig?.maxBetPerGame ?? 1.0,
2324
- dailySpendLimit: pluginConfig?.dailySpendLimit ?? 20.0,
2325
- dailySpentSoFar: `$${c.dailySpentDollars.toFixed(2)}`,
2326
- dailyRemaining: `$${Math.max(0, (pluginConfig?.dailySpendLimit ?? 20.0) - c.dailySpentDollars).toFixed(2)}`,
2327
- autoJoinGlobalChat: pluginConfig?.autoJoinGlobalChat ?? false,
2328
- autoPromoteReferrals: pluginConfig?.autoPromoteReferrals ?? false,
2329
- hint: 'These scopes are set by your operator in the plugin config. If you need higher limits, ask your operator to update dailySpendLimit or maxBetPerGame.',
306
+ hint: 'Game flow: dim_create_lobby → dim_join_queue → dim_game_loop (blocks until your turn) → dim_submit_action → dim_game_loop → repeat until completed',
307
+ move_formats: {
308
+ 'rock-paper-scissors':
309
+ 'action: "play", payload: { action: "rock"|"paper"|"scissors" }',
310
+ chess: 'action: "move", payload: { from: "e2", to: "e4" }',
311
+ 'tic-tac-toe':
312
+ 'action: "place_mark", payload: { row: 0-2, col: 0-2 }',
313
+ 'connect-four': 'action: "drop_disc", payload: { column: 0-6 }',
314
+ },
315
+ market_flow:
316
+ 'dim_get_market dim_buy_shares (dollars) dim_get_positions dim_sell_shares or dim_redeem_shares after resolution',
317
+ fees: '1% game fee (min 1¢), 1¢ transfer fee, 3% market payout fee',
318
+ instructions: DIM_INSTRUCTIONS,
2330
319
  },
2331
320
  null,
2332
321
  2,
@@ -2334,123 +323,4 @@ export default function register(api: {
2334
323
  );
2335
324
  },
2336
325
  });
2337
-
2338
- // ── Referral onboarding ──────────────────────────────────────────────
2339
-
2340
- api.registerTool({
2341
- name: 'dim_get_referral_onboarding',
2342
- description:
2343
- 'Get platform-specific setup instructions to share with another agent or user to onboard them to DIM with your referral code. The instructions include your referral code so the new user is attributed to you.',
2344
- parameters: {
2345
- type: 'object',
2346
- properties: {
2347
- platform: {
2348
- type: 'string',
2349
- enum: ['openclaw', 'claude-desktop', 'cursor', 'hermes', 'node-sdk'],
2350
- description:
2351
- 'Target platform: openclaw, claude-desktop, cursor, hermes, or node-sdk',
2352
- },
2353
- },
2354
- required: ['platform'],
2355
- additionalProperties: false,
2356
- },
2357
- async execute(_, params) {
2358
- const c = await requireClient();
2359
- if ('error' in c) return c.error;
2360
- try {
2361
- let username = 'YOUR_USERNAME';
2362
- try {
2363
- if (c.currentUserId) {
2364
- const profile = await c.sdk.users.getUserById(c.currentUserId);
2365
- if (profile.username) username = profile.username;
2366
- }
2367
- } catch {
2368
- /* use fallback */
2369
- }
2370
- const platform = String(params.platform ?? 'openclaw');
2371
- const script = generateOnboardingScript(platform, username);
2372
- return textResult(script);
2373
- } catch (err) {
2374
- return textResult(
2375
- `Failed to generate onboarding script: ${err instanceof Error ? err.message : String(err)}`,
2376
- true,
2377
- );
2378
- }
2379
- },
2380
- });
2381
-
2382
- // ── Real-time event awareness ─────────────────────────────────────────
2383
-
2384
- api.registerTool({
2385
- name: 'dim_get_pending_events',
2386
- description:
2387
- 'Drain buffered real-time events (DMs, challenges, game turns, match notifications). Call this regularly during game loops or idle time to stay aware of incoming activity.',
2388
- parameters: { type: 'object', properties: {}, additionalProperties: false },
2389
- async execute() {
2390
- const c = await requireClient();
2391
- if ('error' in c) return c.error;
2392
- try {
2393
- const events = c.drainEvents();
2394
- return textResult(
2395
- JSON.stringify(
2396
- {
2397
- count: events.length,
2398
- events,
2399
- hint:
2400
- events.length === 0
2401
- ? 'No new events since last check.'
2402
- : 'Process these events and take action as needed.',
2403
- },
2404
- null,
2405
- 2,
2406
- ),
2407
- );
2408
- } catch (err) {
2409
- return textResult(
2410
- `Failed to get pending events: ${err instanceof Error ? err.message : String(err)}`,
2411
- true,
2412
- );
2413
- }
2414
- },
2415
- });
2416
-
2417
- api.registerTool({
2418
- name: 'dim_check_notifications',
2419
- description:
2420
- 'Check all pending items in one call: unread notifications (challenges, game results), unread DM threads, and incoming friend requests. Use this to catch up after being idle.',
2421
- parameters: { type: 'object', properties: {}, additionalProperties: false },
2422
- async execute() {
2423
- const c = await requireClient();
2424
- if ('error' in c) return c.error;
2425
- try {
2426
- const [notifications, dmThreads, friendRequests] = await Promise.all([
2427
- c.sdk.notifications.list({ page: 1, limit: 20 }),
2428
- c.sdk.chat.listDmThreads(),
2429
- c.sdk.users.getIncomingFriendRequests(),
2430
- ]);
2431
- const unreadDms = (dmThreads as Array<{ unreadCount?: number }>).filter(
2432
- (t) => (t.unreadCount ?? 0) > 0,
2433
- );
2434
- return textResult(
2435
- JSON.stringify(
2436
- {
2437
- unreadNotificationCount: notifications.unreadCount,
2438
- notifications: notifications.notifications.filter((n) => !n.read),
2439
- unreadDmThreads: unreadDms,
2440
- incomingFriendRequests: friendRequests,
2441
- pendingWsEvents: c.pendingEventCount,
2442
- hint: 'Use dim_get_pending_events to drain buffered real-time events.',
2443
- },
2444
- null,
2445
- 2,
2446
- ),
2447
- );
2448
- } catch (err) {
2449
- return textResult(
2450
- `Failed to check notifications: ${err instanceof Error ? err.message : String(err)}`,
2451
- true,
2452
- );
2453
- }
2454
- },
2455
- });
2456
326
  }