@dimcool/dimclaw 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts ADDED
@@ -0,0 +1,1848 @@
1
+ /**
2
+ * @dimcool/dimclaw — OpenClaw plugin for DIM.
3
+ * Uses the SDK directly (no MCP subprocess). Register all DIM tools for OpenClaw agents.
4
+ */
5
+
6
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import { Keypair, Transaction } from '@solana/web3.js';
9
+ import bs58 from 'bs58';
10
+ import { DimClient } from './dim-client';
11
+
12
+ type PluginConfig = {
13
+ walletPrivateKey?: string;
14
+ walletStorePath?: string;
15
+ apiUrl?: string;
16
+ };
17
+
18
+ function getPluginConfig(api: { config?: unknown }): PluginConfig | null {
19
+ const config = api.config as Record<string, unknown> | undefined;
20
+ const plugins = config?.plugins as
21
+ | { entries?: Record<string, { config?: PluginConfig }> }
22
+ | undefined;
23
+ const dimclawEntry = plugins?.entries?.dimclaw;
24
+ return dimclawEntry?.config ?? null;
25
+ }
26
+
27
+ function resolveStorePath(storePath: string): string {
28
+ const expanded =
29
+ storePath.startsWith('~/') && process.env.HOME
30
+ ? path.join(process.env.HOME, storePath.slice(2))
31
+ : storePath;
32
+ return path.resolve(expanded);
33
+ }
34
+
35
+ interface WalletRecord {
36
+ version: 1;
37
+ walletAddress: string;
38
+ walletPrivateKey: string;
39
+ createdAt: string;
40
+ }
41
+
42
+ async function readWalletFile(storePath: string): Promise<WalletRecord | null> {
43
+ try {
44
+ const raw = await readFile(storePath, 'utf8');
45
+ const parsed = JSON.parse(raw) as Partial<WalletRecord>;
46
+ if (parsed.walletPrivateKey && parsed.walletAddress) {
47
+ return {
48
+ version: 1,
49
+ walletAddress: parsed.walletAddress,
50
+ walletPrivateKey: parsed.walletPrivateKey,
51
+ createdAt: parsed.createdAt ?? new Date().toISOString(),
52
+ };
53
+ }
54
+ return null;
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ async function writeWalletFile(
61
+ storePath: string,
62
+ record: WalletRecord,
63
+ ): Promise<void> {
64
+ await mkdir(path.dirname(storePath), { recursive: true });
65
+ const tmp = `${storePath}.tmp`;
66
+ await writeFile(tmp, `${JSON.stringify(record, null, 2)}\n`, {
67
+ encoding: 'utf8',
68
+ mode: 0o600,
69
+ });
70
+ await rename(tmp, storePath);
71
+ }
72
+
73
+ function createWalletRecord(): WalletRecord {
74
+ const keypair = Keypair.generate();
75
+ return {
76
+ version: 1,
77
+ walletAddress: keypair.publicKey.toBase58(),
78
+ walletPrivateKey: bs58.encode(keypair.secretKey),
79
+ createdAt: new Date().toISOString(),
80
+ };
81
+ }
82
+
83
+ /** Resolve wallet private key from config: direct key, or read/create at store path. */
84
+ async function resolveWalletKey(config: PluginConfig): Promise<string | null> {
85
+ if (config.walletPrivateKey?.trim()) return config.walletPrivateKey.trim();
86
+ if (!config.walletStorePath?.trim()) return null;
87
+ const storePath = resolveStorePath(config.walletStorePath.trim());
88
+ const existing = await readWalletFile(storePath);
89
+ if (existing) return existing.walletPrivateKey;
90
+ const record = createWalletRecord();
91
+ await writeWalletFile(storePath, record);
92
+ return record.walletPrivateKey;
93
+ }
94
+
95
+ function textResult(
96
+ text: string,
97
+ isError = false,
98
+ ): { content: Array<{ type: 'text'; text: string }>; isError?: boolean } {
99
+ return {
100
+ content: [{ type: 'text', text }],
101
+ ...(isError ? { isError: true } : {}),
102
+ };
103
+ }
104
+
105
+ export default function register(api: {
106
+ config?: unknown;
107
+ registerTool: (tool: {
108
+ name: string;
109
+ description: string;
110
+ parameters?: Record<string, unknown>;
111
+ execute: (
112
+ id: unknown,
113
+ params: Record<string, unknown>,
114
+ ) => Promise<{
115
+ content: Array<{ type: 'text'; text: string }>;
116
+ isError?: boolean;
117
+ }>;
118
+ }) => void;
119
+ }) {
120
+ let client: DimClient | null = null;
121
+
122
+ async function getClient(): Promise<DimClient | null> {
123
+ if (client) return client;
124
+ const config = getPluginConfig(api);
125
+ if (!config) return null;
126
+ const walletPrivateKey = await resolveWalletKey(config);
127
+ if (!walletPrivateKey) return null;
128
+ client = new DimClient({
129
+ walletPrivateKey,
130
+ apiUrl: config.apiUrl,
131
+ });
132
+ return client;
133
+ }
134
+
135
+ async function requireClient(): Promise<
136
+ DimClient | { error: ReturnType<typeof textResult> }
137
+ > {
138
+ const c = await getClient();
139
+ if (c) return c;
140
+ return {
141
+ error: textResult(
142
+ 'DIM plugin is not configured. Set walletPrivateKey, or walletStorePath (we will create a wallet there if missing), under plugins.entries.dimclaw.config, then restart the gateway.',
143
+ true,
144
+ ),
145
+ };
146
+ }
147
+
148
+ // --- Auth ---
149
+ api.registerTool({
150
+ name: 'dim_login',
151
+ description:
152
+ 'Authenticate with DIM using the configured wallet. Must be called before using any other tools. Returns user profile info.',
153
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
154
+ async execute() {
155
+ const c = await requireClient();
156
+ if ('error' in c) return c.error;
157
+ try {
158
+ const result = await c.authenticate();
159
+ const response: Record<string, unknown> = {
160
+ success: true,
161
+ userId: result.userId,
162
+ username: result.username,
163
+ walletAddress: c.walletAddress,
164
+ };
165
+ try {
166
+ const summary = await c.sdk.referrals.getSummary();
167
+ if (!summary.hasReferrer) {
168
+ response.referralHint =
169
+ 'You have no referrer yet. Use dim_apply_referral_code to apply one and get a 10% fee discount.';
170
+ }
171
+ } catch {
172
+ /* non-critical */
173
+ }
174
+ return textResult(JSON.stringify(response, null, 2));
175
+ } catch (err) {
176
+ return textResult(
177
+ `Authentication failed: ${err instanceof Error ? err.message : String(err)}`,
178
+ true,
179
+ );
180
+ }
181
+ },
182
+ });
183
+
184
+ api.registerTool({
185
+ name: 'dim_get_profile',
186
+ description:
187
+ 'Get the current authenticated user profile including username, avatar, bio, and chess ELO rating.',
188
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
189
+ async execute() {
190
+ const c = await requireClient();
191
+ if ('error' in c) return c.error;
192
+ try {
193
+ if (!c.currentUserId)
194
+ return textResult('Not authenticated. Call dim_login first.', true);
195
+ const user = await c.sdk.users.getUserById(c.currentUserId);
196
+ return textResult(JSON.stringify(user, null, 2));
197
+ } catch (err) {
198
+ return textResult(
199
+ `Failed to get profile: ${err instanceof Error ? err.message : String(err)}`,
200
+ true,
201
+ );
202
+ }
203
+ },
204
+ });
205
+
206
+ api.registerTool({
207
+ name: 'dim_set_username',
208
+ description:
209
+ 'Set or update the username for the authenticated user. Username must be alphanumeric, 3-20 characters.',
210
+ parameters: {
211
+ type: 'object',
212
+ properties: {
213
+ username: { type: 'string', description: 'The desired username' },
214
+ },
215
+ required: ['username'],
216
+ additionalProperties: false,
217
+ },
218
+ async execute(_, params) {
219
+ const c = await requireClient();
220
+ if ('error' in c) return c.error;
221
+ const username = String(params.username ?? '');
222
+ try {
223
+ const { available, valid } =
224
+ await c.sdk.users.isUsernameAvailable(username);
225
+ if (!valid)
226
+ return textResult(
227
+ 'Invalid username. Must be alphanumeric, 3-20 characters.',
228
+ true,
229
+ );
230
+ if (!available)
231
+ return textResult(`Username "${username}" is already taken.`, true);
232
+ const user = await c.sdk.users.updateUsername(username);
233
+ return textResult(
234
+ JSON.stringify({ success: true, username: user.username }, null, 2),
235
+ );
236
+ } catch (err) {
237
+ return textResult(
238
+ `Failed to set username: ${err instanceof Error ? err.message : String(err)}`,
239
+ true,
240
+ );
241
+ }
242
+ },
243
+ });
244
+
245
+ // --- Wallet ---
246
+ api.registerTool({
247
+ name: 'dim_get_balance',
248
+ description:
249
+ 'Get the wallet balance for the authenticated user. Returns SOL and USDC amounts. USDC amounts are in minor units (1 USDC = 1,000,000 minor units).',
250
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
251
+ async execute() {
252
+ const c = await requireClient();
253
+ if ('error' in c) return c.error;
254
+ try {
255
+ const balance = await c.sdk.wallet.getBalances();
256
+ const usdcDollars = balance.usdc / 1_000_000;
257
+ return textResult(
258
+ JSON.stringify(
259
+ { ...balance, usdcFormatted: `$${usdcDollars.toFixed(2)}` },
260
+ null,
261
+ 2,
262
+ ),
263
+ );
264
+ } catch (err) {
265
+ return textResult(
266
+ `Failed to get balance: ${err instanceof Error ? err.message : String(err)}`,
267
+ true,
268
+ );
269
+ }
270
+ },
271
+ });
272
+
273
+ api.registerTool({
274
+ name: 'dim_send_usdc',
275
+ description:
276
+ '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).',
277
+ parameters: {
278
+ type: 'object',
279
+ properties: {
280
+ recipient: {
281
+ type: 'string',
282
+ description: 'Recipient username or Solana wallet address',
283
+ },
284
+ amount: {
285
+ type: 'number',
286
+ description: 'Amount in USDC dollars (e.g., 1.50 for $1.50)',
287
+ },
288
+ },
289
+ required: ['recipient', 'amount'],
290
+ additionalProperties: false,
291
+ },
292
+ async execute(_, params) {
293
+ const c = await requireClient();
294
+ if ('error' in c) return c.error;
295
+ const recipient = String(params.recipient ?? '');
296
+ const amount = Number(params.amount ?? 0);
297
+ try {
298
+ const amountMinor = Math.round(amount * 1_000_000);
299
+ const result = await c.sdk.wallet.send(recipient, amountMinor);
300
+ return textResult(
301
+ JSON.stringify(
302
+ {
303
+ success: true,
304
+ signature: result.signature,
305
+ status: result.status,
306
+ amountSent: `$${amount.toFixed(2)}`,
307
+ fee: '$0.01',
308
+ recipient,
309
+ recipientAddress: result.recipientAddress,
310
+ },
311
+ null,
312
+ 2,
313
+ ),
314
+ );
315
+ } catch (err) {
316
+ return textResult(
317
+ `Failed to send USDC: ${err instanceof Error ? err.message : String(err)}`,
318
+ true,
319
+ );
320
+ }
321
+ },
322
+ });
323
+
324
+ api.registerTool({
325
+ name: 'dim_tip_user',
326
+ description:
327
+ 'Tip a user with USDC and broadcast the tip to global chat. A 1 cent fee applies. Amount is in USDC dollars.',
328
+ parameters: {
329
+ type: 'object',
330
+ properties: {
331
+ recipientUsername: {
332
+ type: 'string',
333
+ description: 'Username of the user to tip',
334
+ },
335
+ amount: {
336
+ type: 'number',
337
+ description: 'Tip amount in USDC dollars (e.g., 1.00 for $1.00)',
338
+ },
339
+ },
340
+ required: ['recipientUsername', 'amount'],
341
+ additionalProperties: false,
342
+ },
343
+ async execute(_, params) {
344
+ const c = await requireClient();
345
+ if ('error' in c) return c.error;
346
+ const recipientUsername = String(params.recipientUsername ?? '');
347
+ const amount = Number(params.amount ?? 0);
348
+ try {
349
+ const amountMinor = Math.round(amount * 1_000_000);
350
+ const result = await c.sdk.tips.send(recipientUsername, amountMinor);
351
+ return textResult(
352
+ JSON.stringify(
353
+ {
354
+ success: true,
355
+ signature: result.signature,
356
+ tipAmount: `$${amount.toFixed(2)}`,
357
+ fee: '$0.01',
358
+ recipient: recipientUsername,
359
+ broadcastedToGlobalChat: true,
360
+ },
361
+ null,
362
+ 2,
363
+ ),
364
+ );
365
+ } catch (err) {
366
+ return textResult(
367
+ `Failed to tip user: ${err instanceof Error ? err.message : String(err)}`,
368
+ true,
369
+ );
370
+ }
371
+ },
372
+ });
373
+
374
+ api.registerTool({
375
+ name: 'dim_get_wallet_activity',
376
+ description:
377
+ 'Get recent wallet transaction activity (deposits, payouts, transfers, refunds).',
378
+ parameters: {
379
+ type: 'object',
380
+ properties: {
381
+ limit: {
382
+ type: 'number',
383
+ description: 'Max items to return (default: 20)',
384
+ },
385
+ },
386
+ additionalProperties: false,
387
+ },
388
+ async execute(_, params) {
389
+ const c = await requireClient();
390
+ if ('error' in c) return c.error;
391
+ const limit = typeof params.limit === 'number' ? params.limit : 20;
392
+ try {
393
+ const activity = await c.sdk.wallet.getActivity({ limit });
394
+ return textResult(JSON.stringify(activity, null, 2));
395
+ } catch (err) {
396
+ return textResult(
397
+ `Failed to get wallet activity: ${err instanceof Error ? err.message : String(err)}`,
398
+ true,
399
+ );
400
+ }
401
+ },
402
+ });
403
+
404
+ // --- Friends ---
405
+ api.registerTool({
406
+ name: 'dim_search_users',
407
+ description:
408
+ 'Search for DIM users by username. Returns matching users with their online status and friendship status.',
409
+ parameters: {
410
+ type: 'object',
411
+ properties: {
412
+ query: { type: 'string', description: 'Username to search for' },
413
+ limit: {
414
+ type: 'number',
415
+ description: 'Max results to return (default: 10)',
416
+ },
417
+ },
418
+ required: ['query'],
419
+ additionalProperties: false,
420
+ },
421
+ async execute(_, params) {
422
+ const c = await requireClient();
423
+ if ('error' in c) return c.error;
424
+ const query = String(params.query ?? '');
425
+ const limit = typeof params.limit === 'number' ? params.limit : 10;
426
+ try {
427
+ const result = await c.sdk.users.searchUsers(query, 1, limit);
428
+ return textResult(JSON.stringify(result, null, 2));
429
+ } catch (err) {
430
+ return textResult(
431
+ `Search failed: ${err instanceof Error ? err.message : String(err)}`,
432
+ true,
433
+ );
434
+ }
435
+ },
436
+ });
437
+
438
+ api.registerTool({
439
+ name: 'dim_send_friend_request',
440
+ description:
441
+ 'Send a friend request to a user by their user ID. If they already sent you a request, this auto-accepts.',
442
+ parameters: {
443
+ type: 'object',
444
+ properties: {
445
+ userId: {
446
+ type: 'string',
447
+ description: 'The user ID to send a friend request to',
448
+ },
449
+ },
450
+ required: ['userId'],
451
+ additionalProperties: false,
452
+ },
453
+ async execute(_, params) {
454
+ const c = await requireClient();
455
+ if ('error' in c) return c.error;
456
+ try {
457
+ const result = await c.sdk.users.addFriend(String(params.userId ?? ''));
458
+ return textResult(JSON.stringify(result, null, 2));
459
+ } catch (err) {
460
+ return textResult(
461
+ `Failed to send friend request: ${err instanceof Error ? err.message : String(err)}`,
462
+ true,
463
+ );
464
+ }
465
+ },
466
+ });
467
+
468
+ api.registerTool({
469
+ name: 'dim_accept_friend_request',
470
+ description: 'Accept an incoming friend request from a user.',
471
+ parameters: {
472
+ type: 'object',
473
+ properties: {
474
+ userId: {
475
+ type: 'string',
476
+ description: 'The user ID whose friend request to accept',
477
+ },
478
+ },
479
+ required: ['userId'],
480
+ additionalProperties: false,
481
+ },
482
+ async execute(_, params) {
483
+ const c = await requireClient();
484
+ if ('error' in c) return c.error;
485
+ try {
486
+ const result = await c.sdk.users.acceptFriendRequest(
487
+ String(params.userId ?? ''),
488
+ );
489
+ return textResult(JSON.stringify(result, null, 2));
490
+ } catch (err) {
491
+ return textResult(
492
+ `Failed to accept friend request: ${err instanceof Error ? err.message : String(err)}`,
493
+ true,
494
+ );
495
+ }
496
+ },
497
+ });
498
+
499
+ api.registerTool({
500
+ name: 'dim_list_friends',
501
+ description: "List the authenticated user's friends with pagination.",
502
+ parameters: {
503
+ type: 'object',
504
+ properties: {
505
+ page: { type: 'number', description: 'Page number (default: 1)' },
506
+ limit: {
507
+ type: 'number',
508
+ description: 'Friends per page (default: 20)',
509
+ },
510
+ search: { type: 'string', description: 'Filter friends by username' },
511
+ },
512
+ additionalProperties: false,
513
+ },
514
+ async execute(_, params) {
515
+ const c = await requireClient();
516
+ if ('error' in c) return c.error;
517
+ if (!c.currentUserId)
518
+ return textResult('Not authenticated. Call dim_login first.', true);
519
+ try {
520
+ const result = await c.sdk.users.getFriends(
521
+ c.currentUserId,
522
+ typeof params.page === 'number' ? params.page : 1,
523
+ typeof params.limit === 'number' ? params.limit : 20,
524
+ typeof params.search === 'string' ? params.search : undefined,
525
+ );
526
+ return textResult(JSON.stringify(result, null, 2));
527
+ } catch (err) {
528
+ return textResult(
529
+ `Failed to list friends: ${err instanceof Error ? err.message : String(err)}`,
530
+ true,
531
+ );
532
+ }
533
+ },
534
+ });
535
+
536
+ api.registerTool({
537
+ name: 'dim_get_incoming_friend_requests',
538
+ description: 'List pending incoming friend requests.',
539
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
540
+ async execute() {
541
+ const c = await requireClient();
542
+ if ('error' in c) return c.error;
543
+ try {
544
+ const result = await c.sdk.users.getIncomingFriendRequests();
545
+ return textResult(JSON.stringify(result, null, 2));
546
+ } catch (err) {
547
+ return textResult(
548
+ `Failed to get requests: ${err instanceof Error ? err.message : String(err)}`,
549
+ true,
550
+ );
551
+ }
552
+ },
553
+ });
554
+
555
+ // --- Chat ---
556
+ api.registerTool({
557
+ name: 'dim_send_message',
558
+ description:
559
+ "Send a chat message in a specific context (lobby, game, DM, or global chat). For DMs, the contextId is the other user's ID.",
560
+ parameters: {
561
+ type: 'object',
562
+ properties: {
563
+ contextType: {
564
+ type: 'string',
565
+ enum: ['lobby', 'game', 'dm', 'global'],
566
+ description: 'Chat context type',
567
+ },
568
+ contextId: {
569
+ type: 'string',
570
+ description:
571
+ 'Context ID: lobby ID, game ID, user ID (for DM), or "global"',
572
+ },
573
+ message: {
574
+ type: 'string',
575
+ description: 'Message text (1-500 characters)',
576
+ },
577
+ },
578
+ required: ['contextType', 'contextId', 'message'],
579
+ additionalProperties: false,
580
+ },
581
+ async execute(_, params) {
582
+ const c = await requireClient();
583
+ if ('error' in c) return c.error;
584
+ try {
585
+ const result = await c.sdk.chat.sendMessage(
586
+ {
587
+ type: params.contextType as 'lobby' | 'game' | 'dm' | 'global',
588
+ id: String(params.contextId ?? ''),
589
+ },
590
+ String(params.message ?? ''),
591
+ );
592
+ return textResult(JSON.stringify(result, null, 2));
593
+ } catch (err) {
594
+ return textResult(
595
+ `Failed to send message: ${err instanceof Error ? err.message : String(err)}`,
596
+ true,
597
+ );
598
+ }
599
+ },
600
+ });
601
+
602
+ api.registerTool({
603
+ name: 'dim_get_chat_history',
604
+ description:
605
+ 'Get chat history for a context (lobby, game, DM, or global). Returns messages in chronological order.',
606
+ parameters: {
607
+ type: 'object',
608
+ properties: {
609
+ contextType: {
610
+ type: 'string',
611
+ enum: ['lobby', 'game', 'dm', 'global'],
612
+ description: 'Chat context type',
613
+ },
614
+ contextId: {
615
+ type: 'string',
616
+ description:
617
+ 'Context ID: lobby ID, game ID, user ID (for DM), or "global"',
618
+ },
619
+ limit: {
620
+ type: 'number',
621
+ description: 'Max messages to return (default: 50)',
622
+ },
623
+ },
624
+ required: ['contextType', 'contextId'],
625
+ additionalProperties: false,
626
+ },
627
+ async execute(_, params) {
628
+ const c = await requireClient();
629
+ if ('error' in c) return c.error;
630
+ const limit = typeof params.limit === 'number' ? params.limit : 50;
631
+ try {
632
+ const messages = await c.sdk.chat.getChatHistory(
633
+ {
634
+ type: params.contextType as 'lobby' | 'game' | 'dm' | 'global',
635
+ id: String(params.contextId ?? ''),
636
+ },
637
+ limit,
638
+ );
639
+ return textResult(JSON.stringify(messages, null, 2));
640
+ } catch (err) {
641
+ return textResult(
642
+ `Failed to get chat history: ${err instanceof Error ? err.message : String(err)}`,
643
+ true,
644
+ );
645
+ }
646
+ },
647
+ });
648
+
649
+ api.registerTool({
650
+ name: 'dim_send_dm',
651
+ description: 'Send a direct message to another user by their user ID.',
652
+ parameters: {
653
+ type: 'object',
654
+ properties: {
655
+ userId: { type: 'string', description: 'The user ID to send a DM to' },
656
+ message: {
657
+ type: 'string',
658
+ description: 'Message text (1-500 characters)',
659
+ },
660
+ },
661
+ required: ['userId', 'message'],
662
+ additionalProperties: false,
663
+ },
664
+ async execute(_, params) {
665
+ const c = await requireClient();
666
+ if ('error' in c) return c.error;
667
+ try {
668
+ const result = await c.sdk.chat.sendMessage(
669
+ { type: 'dm', id: String(params.userId ?? '') },
670
+ String(params.message ?? ''),
671
+ );
672
+ return textResult(JSON.stringify(result, null, 2));
673
+ } catch (err) {
674
+ return textResult(
675
+ `Failed to send DM: ${err instanceof Error ? err.message : String(err)}`,
676
+ true,
677
+ );
678
+ }
679
+ },
680
+ });
681
+
682
+ api.registerTool({
683
+ name: 'dim_list_dm_threads',
684
+ description:
685
+ 'List all DM conversation threads with last message info and unread counts.',
686
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
687
+ async execute() {
688
+ const c = await requireClient();
689
+ if ('error' in c) return c.error;
690
+ try {
691
+ const threads = await c.sdk.chat.listDmThreads();
692
+ return textResult(JSON.stringify(threads, null, 2));
693
+ } catch (err) {
694
+ return textResult(
695
+ `Failed to list DM threads: ${err instanceof Error ? err.message : String(err)}`,
696
+ true,
697
+ );
698
+ }
699
+ },
700
+ });
701
+
702
+ // --- Games ---
703
+ api.registerTool({
704
+ name: 'dim_list_games',
705
+ description:
706
+ '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.',
707
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
708
+ async execute() {
709
+ const c = await requireClient();
710
+ if ('error' in c) return c.error;
711
+ try {
712
+ const games = await c.sdk.games.getAvailableGames();
713
+ return textResult(JSON.stringify(games, null, 2));
714
+ } catch (err) {
715
+ return textResult(
716
+ `Failed to list games: ${err instanceof Error ? err.message : String(err)}`,
717
+ true,
718
+ );
719
+ }
720
+ },
721
+ });
722
+
723
+ api.registerTool({
724
+ name: 'dim_get_game_metrics',
725
+ description:
726
+ 'Get real-time metrics for all games: active players, live games, and money in play per game type.',
727
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
728
+ async execute() {
729
+ const c = await requireClient();
730
+ if ('error' in c) return c.error;
731
+ try {
732
+ const metrics = await c.sdk.games.getMetrics();
733
+ return textResult(JSON.stringify(metrics, null, 2));
734
+ } catch (err) {
735
+ return textResult(
736
+ `Failed to get metrics: ${err instanceof Error ? err.message : String(err)}`,
737
+ true,
738
+ );
739
+ }
740
+ },
741
+ });
742
+
743
+ api.registerTool({
744
+ name: 'dim_create_lobby',
745
+ description:
746
+ '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 minor units (1 USDC = 1,000,000). A 1% fee (min 1 cent) is charged per player.',
747
+ parameters: {
748
+ type: 'object',
749
+ properties: {
750
+ gameType: {
751
+ type: 'string',
752
+ description:
753
+ 'Game type ID (e.g., "rock-paper-scissors", "chess", "tic-tac-toe", "connect-four", "nim", "dots-and-boxes")',
754
+ },
755
+ betAmount: {
756
+ type: 'number',
757
+ description:
758
+ 'Bet amount in USDC minor units (e.g., 1000000 = $1.00). Common amounts: 1000000 ($1), 5000000 ($5), 10000000 ($10)',
759
+ },
760
+ },
761
+ required: ['gameType'],
762
+ additionalProperties: false,
763
+ },
764
+ async execute(_, params) {
765
+ const c = await requireClient();
766
+ if ('error' in c) return c.error;
767
+ const gameType = String(params.gameType ?? '');
768
+ const betAmount =
769
+ typeof params.betAmount === 'number' ? params.betAmount : undefined;
770
+ try {
771
+ const lobby = await c.sdk.lobbies.createLobby(gameType, betAmount);
772
+ return textResult(
773
+ JSON.stringify(
774
+ {
775
+ lobbyId: lobby.id,
776
+ gameType: lobby.gameType,
777
+ status: lobby.status,
778
+ betAmount: lobby.betAmount,
779
+ betFormatted: lobby.betAmount
780
+ ? `$${(lobby.betAmount / 1_000_000).toFixed(2)}`
781
+ : 'Free',
782
+ maxPlayers: lobby.maxPlayers,
783
+ players: lobby.players.length,
784
+ hint: 'Use dim_join_queue to find an opponent, or share the lobbyId for someone to join directly.',
785
+ },
786
+ null,
787
+ 2,
788
+ ),
789
+ );
790
+ } catch (err) {
791
+ return textResult(
792
+ `Failed to create lobby: ${err instanceof Error ? err.message : String(err)}`,
793
+ true,
794
+ );
795
+ }
796
+ },
797
+ });
798
+
799
+ api.registerTool({
800
+ name: 'dim_join_queue',
801
+ description:
802
+ 'Join the matchmaking queue for a lobby. If the lobby is full, the game starts immediately. Otherwise, waits for an opponent. Returns the lobby (and gameId if matched).',
803
+ parameters: {
804
+ type: 'object',
805
+ properties: {
806
+ lobbyId: {
807
+ type: 'string',
808
+ description: 'The lobby ID to join the queue for',
809
+ },
810
+ },
811
+ required: ['lobbyId'],
812
+ additionalProperties: false,
813
+ },
814
+ async execute(_, params) {
815
+ const c = await requireClient();
816
+ if ('error' in c) return c.error;
817
+ try {
818
+ await c.ensureConnected();
819
+ const lobby = await c.sdk.lobbies.joinQueue(
820
+ String(params.lobbyId ?? ''),
821
+ );
822
+ const matched = lobby.status === 'active' && lobby.gameId;
823
+ return textResult(
824
+ JSON.stringify(
825
+ {
826
+ lobbyId: lobby.id,
827
+ status: lobby.status,
828
+ gameId: lobby.gameId || null,
829
+ matched: !!matched,
830
+ hint: matched
831
+ ? `Game started! Use dim_get_game_state with gameId "${lobby.gameId}" to see the game state, then dim_submit_action to play.`
832
+ : 'Waiting for an opponent. Poll dim_get_lobby to check for a match.',
833
+ },
834
+ null,
835
+ 2,
836
+ ),
837
+ );
838
+ } catch (err) {
839
+ return textResult(
840
+ `Failed to join queue: ${err instanceof Error ? err.message : String(err)}`,
841
+ true,
842
+ );
843
+ }
844
+ },
845
+ });
846
+
847
+ api.registerTool({
848
+ name: 'dim_get_lobby',
849
+ description:
850
+ 'Get the current state of a lobby including players, status, and gameId (if matched).',
851
+ parameters: {
852
+ type: 'object',
853
+ properties: {
854
+ lobbyId: { type: 'string', description: 'The lobby ID to check' },
855
+ },
856
+ required: ['lobbyId'],
857
+ additionalProperties: false,
858
+ },
859
+ async execute(_, params) {
860
+ const c = await requireClient();
861
+ if ('error' in c) return c.error;
862
+ try {
863
+ const lobby = await c.sdk.lobbies.getLobby(
864
+ String(params.lobbyId ?? ''),
865
+ );
866
+ return textResult(JSON.stringify(lobby, null, 2));
867
+ } catch (err) {
868
+ return textResult(
869
+ `Failed to get lobby: ${err instanceof Error ? err.message : String(err)}`,
870
+ true,
871
+ );
872
+ }
873
+ },
874
+ });
875
+
876
+ api.registerTool({
877
+ name: 'dim_get_game_state',
878
+ description:
879
+ 'Get the current state of an active game including round info, scores, timer, and available actions. The response format depends on the game type.',
880
+ parameters: {
881
+ type: 'object',
882
+ properties: {
883
+ gameId: { type: 'string', description: 'The game ID to get state for' },
884
+ },
885
+ required: ['gameId'],
886
+ additionalProperties: false,
887
+ },
888
+ async execute(_, params) {
889
+ const c = await requireClient();
890
+ if ('error' in c) return c.error;
891
+ try {
892
+ const state = await c.sdk.games.getGameState(
893
+ String(params.gameId ?? ''),
894
+ );
895
+ return textResult(JSON.stringify(state, null, 2));
896
+ } catch (err) {
897
+ return textResult(
898
+ `Failed to get game state: ${err instanceof Error ? err.message : String(err)}`,
899
+ true,
900
+ );
901
+ }
902
+ },
903
+ });
904
+
905
+ api.registerTool({
906
+ name: 'dim_submit_action',
907
+ description:
908
+ 'Submit a game action. The action format depends on the game type.\n\n' +
909
+ 'Rock-Paper-Scissors: gameType="rock-paper-scissors", action="play", payload={ action: "rock"|"paper"|"scissors" }\n' +
910
+ 'Chess: gameType="chess", action="move", payload={ from: "e2", to: "e4" }\n' +
911
+ 'Tic-Tac-Toe: gameType="tic-tac-toe", action="place", payload={ position: 0-8 }\n' +
912
+ 'Connect Four: gameType="connect-four", action="drop", payload={ column: 0-6 }\n' +
913
+ 'Nim: gameType="nim", action="take", payload={ heap: 0-2, count: 1-N }\n' +
914
+ 'Dots and Boxes: gameType="dots-and-boxes", action="draw", payload={ row, col, direction: "h"|"v" }',
915
+ parameters: {
916
+ type: 'object',
917
+ properties: {
918
+ gameId: {
919
+ type: 'string',
920
+ description: 'The game ID to submit an action for',
921
+ },
922
+ gameType: {
923
+ type: 'string',
924
+ description:
925
+ 'Game type (e.g., "rock-paper-scissors", "chess", "tic-tac-toe")',
926
+ },
927
+ action: {
928
+ type: 'string',
929
+ description: 'Action type (e.g., "play", "move", "place", "drop")',
930
+ },
931
+ payload: {
932
+ type: 'object',
933
+ description:
934
+ 'Action payload (game-specific, see tool description for format)',
935
+ },
936
+ },
937
+ required: ['gameId', 'gameType', 'action', 'payload'],
938
+ additionalProperties: false,
939
+ },
940
+ async execute(_, params) {
941
+ const c = await requireClient();
942
+ if ('error' in c) return c.error;
943
+ try {
944
+ await c.sdk.games.submitAction(String(params.gameId ?? ''), {
945
+ gameType: String(params.gameType ?? ''),
946
+ action: String(params.action ?? ''),
947
+ payload: (params.payload as Record<string, unknown>) ?? {},
948
+ } as never);
949
+ return textResult(
950
+ JSON.stringify(
951
+ {
952
+ success: true,
953
+ gameId: params.gameId,
954
+ action: params.action,
955
+ hint: 'Use dim_get_game_state to see the updated game state.',
956
+ },
957
+ null,
958
+ 2,
959
+ ),
960
+ );
961
+ } catch (err) {
962
+ return textResult(
963
+ `Failed to submit action: ${err instanceof Error ? err.message : String(err)}`,
964
+ true,
965
+ );
966
+ }
967
+ },
968
+ });
969
+
970
+ api.registerTool({
971
+ name: 'dim_donate_to_pot',
972
+ description:
973
+ 'Donate USDC to a game pot (spectator donation). One-call flow: prepare, sign, and submit internally.',
974
+ parameters: {
975
+ type: 'object',
976
+ properties: {
977
+ gameId: { type: 'string', description: 'The game ID to donate to' },
978
+ amount: {
979
+ type: 'number',
980
+ description: 'Amount in USDC dollars (minimum $0.10)',
981
+ },
982
+ },
983
+ required: ['gameId', 'amount'],
984
+ additionalProperties: false,
985
+ },
986
+ async execute(_, params) {
987
+ const c = await requireClient();
988
+ if ('error' in c) return c.error;
989
+ const amount = Number(params.amount ?? 0);
990
+ try {
991
+ const amountMinor = Math.round(amount * 1_000_000);
992
+ const result = await c.sdk.games.sendDonation(
993
+ String(params.gameId ?? ''),
994
+ amountMinor,
995
+ );
996
+ return textResult(
997
+ JSON.stringify(
998
+ {
999
+ success: true,
1000
+ gameId: params.gameId,
1001
+ signature: result.signature,
1002
+ status: result.status,
1003
+ amountDonated: `$${amount.toFixed(2)}`,
1004
+ amountMinor: result.amountMinor,
1005
+ escrowAddress: result.escrowAddress,
1006
+ },
1007
+ null,
1008
+ 2,
1009
+ ),
1010
+ );
1011
+ } catch (err) {
1012
+ return textResult(
1013
+ `Failed to donate to pot: ${err instanceof Error ? err.message : String(err)}`,
1014
+ true,
1015
+ );
1016
+ }
1017
+ },
1018
+ });
1019
+
1020
+ api.registerTool({
1021
+ name: 'dim_get_game',
1022
+ description:
1023
+ 'Get game info including status (active/completed/abandoned), players, and lobby IDs.',
1024
+ parameters: {
1025
+ type: 'object',
1026
+ properties: { gameId: { type: 'string', description: 'The game ID' } },
1027
+ required: ['gameId'],
1028
+ additionalProperties: false,
1029
+ },
1030
+ async execute(_, params) {
1031
+ const c = await requireClient();
1032
+ if ('error' in c) return c.error;
1033
+ try {
1034
+ const game = await c.sdk.games.getGame(String(params.gameId ?? ''));
1035
+ return textResult(JSON.stringify(game, null, 2));
1036
+ } catch (err) {
1037
+ return textResult(
1038
+ `Failed to get game: ${err instanceof Error ? err.message : String(err)}`,
1039
+ true,
1040
+ );
1041
+ }
1042
+ },
1043
+ });
1044
+
1045
+ // --- Challenges ---
1046
+ api.registerTool({
1047
+ name: 'dim_challenge_user',
1048
+ description:
1049
+ '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.',
1050
+ parameters: {
1051
+ type: 'object',
1052
+ properties: {
1053
+ gameType: {
1054
+ type: 'string',
1055
+ description:
1056
+ 'Game type (e.g., "rock-paper-scissors", "chess", "tic-tac-toe")',
1057
+ },
1058
+ amount: {
1059
+ type: 'number',
1060
+ description: 'Challenge amount in USDC dollars (e.g., 5.00 for $5)',
1061
+ },
1062
+ targetUsername: {
1063
+ type: 'string',
1064
+ description: 'Username of the user to challenge',
1065
+ },
1066
+ targetUserId: {
1067
+ type: 'string',
1068
+ description: 'User ID of the user to challenge',
1069
+ },
1070
+ },
1071
+ required: ['gameType', 'amount'],
1072
+ additionalProperties: false,
1073
+ },
1074
+ async execute(_, params) {
1075
+ const c = await requireClient();
1076
+ if ('error' in c) return c.error;
1077
+ const targetUsername =
1078
+ typeof params.targetUsername === 'string'
1079
+ ? params.targetUsername
1080
+ : undefined;
1081
+ const targetUserId =
1082
+ typeof params.targetUserId === 'string'
1083
+ ? params.targetUserId
1084
+ : undefined;
1085
+ if (!targetUsername && !targetUserId) {
1086
+ return textResult(
1087
+ 'Must provide either targetUsername or targetUserId.',
1088
+ true,
1089
+ );
1090
+ }
1091
+ try {
1092
+ const amountMinor = Math.round(Number(params.amount ?? 0) * 1_000_000);
1093
+ const result = await c.sdk.challenges.create({
1094
+ gameType: String(params.gameType ?? ''),
1095
+ amount: amountMinor,
1096
+ targetUsername,
1097
+ targetUserId,
1098
+ });
1099
+ return textResult(
1100
+ JSON.stringify(
1101
+ {
1102
+ ...result,
1103
+ amountFormatted: `$${Number(params.amount).toFixed(2)}`,
1104
+ hint: 'The challenged user will receive a notification. When they accept, a lobby is created automatically.',
1105
+ },
1106
+ null,
1107
+ 2,
1108
+ ),
1109
+ );
1110
+ } catch (err) {
1111
+ return textResult(
1112
+ `Failed to create challenge: ${err instanceof Error ? err.message : String(err)}`,
1113
+ true,
1114
+ );
1115
+ }
1116
+ },
1117
+ });
1118
+
1119
+ api.registerTool({
1120
+ name: 'dim_accept_challenge',
1121
+ description:
1122
+ 'Accept a challenge that was sent to you. Creates a lobby with the challenge amount and returns the lobby/game info.',
1123
+ parameters: {
1124
+ type: 'object',
1125
+ properties: {
1126
+ challengeId: {
1127
+ type: 'string',
1128
+ description: 'The challenge ID to accept',
1129
+ },
1130
+ },
1131
+ required: ['challengeId'],
1132
+ additionalProperties: false,
1133
+ },
1134
+ async execute(_, params) {
1135
+ const c = await requireClient();
1136
+ if ('error' in c) return c.error;
1137
+ try {
1138
+ const result = await c.sdk.challenges.accept(
1139
+ String(params.challengeId ?? ''),
1140
+ );
1141
+ return textResult(
1142
+ JSON.stringify(
1143
+ {
1144
+ ...result,
1145
+ hint: result.gameId
1146
+ ? `Game started! Use dim_get_game_state with gameId "${result.gameId}".`
1147
+ : `Lobby created: ${result.lobbyId}. Use dim_join_queue to start matchmaking.`,
1148
+ },
1149
+ null,
1150
+ 2,
1151
+ ),
1152
+ );
1153
+ } catch (err) {
1154
+ return textResult(
1155
+ `Failed to accept challenge: ${err instanceof Error ? err.message : String(err)}`,
1156
+ true,
1157
+ );
1158
+ }
1159
+ },
1160
+ });
1161
+
1162
+ // --- Referrals ---
1163
+ api.registerTool({
1164
+ name: 'dim_get_referral_summary',
1165
+ description:
1166
+ '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%.',
1167
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
1168
+ async execute() {
1169
+ const c = await requireClient();
1170
+ if ('error' in c) return c.error;
1171
+ try {
1172
+ const summary = await c.sdk.referrals.getSummary();
1173
+ const pendingDollars = summary.earnings.pending / 1_000_000;
1174
+ const claimedDollars = summary.earnings.claimed / 1_000_000;
1175
+ const result: Record<string, unknown> = {
1176
+ ...summary,
1177
+ pendingFormatted: `$${pendingDollars.toFixed(2)}`,
1178
+ claimedFormatted: `$${claimedDollars.toFixed(2)}`,
1179
+ referralRates: {
1180
+ level1: '30% of referred user game fees',
1181
+ level2: '3% of level-2 referral game fees',
1182
+ level3: '2% of level-3 referral game fees',
1183
+ },
1184
+ referredUserBenefit: '10% fee discount (0.9% instead of 1%)',
1185
+ };
1186
+ if (!summary.hasReferrer) {
1187
+ result.applyReferralHint =
1188
+ 'You have no referrer yet. Use dim_apply_referral_code to apply one and get a 10% fee discount.';
1189
+ }
1190
+ return textResult(JSON.stringify(result, null, 2));
1191
+ } catch (err) {
1192
+ return textResult(
1193
+ `Failed to get referral summary: ${err instanceof Error ? err.message : String(err)}`,
1194
+ true,
1195
+ );
1196
+ }
1197
+ },
1198
+ });
1199
+
1200
+ api.registerTool({
1201
+ name: 'dim_get_referral_tree',
1202
+ description:
1203
+ 'Get your referral tree at a specific level. Level 1 = direct referrals, Level 2 = referrals of your referrals, Level 3 = one more level deep.',
1204
+ parameters: {
1205
+ type: 'object',
1206
+ properties: {
1207
+ level: {
1208
+ type: 'string',
1209
+ enum: ['1', '2', '3'],
1210
+ description: 'Referral level to view (1, 2, or 3)',
1211
+ },
1212
+ limit: {
1213
+ type: 'number',
1214
+ description: 'Max results (default: 50, max: 200)',
1215
+ },
1216
+ cursor: { type: 'string', description: 'Pagination cursor' },
1217
+ },
1218
+ required: ['level'],
1219
+ additionalProperties: false,
1220
+ },
1221
+ async execute(_, params) {
1222
+ const c = await requireClient();
1223
+ if ('error' in c) return c.error;
1224
+ try {
1225
+ const tree = await c.sdk.referrals.getTree({
1226
+ level: parseInt(String(params.level ?? '1'), 10) as 1 | 2 | 3,
1227
+ limit: typeof params.limit === 'number' ? params.limit : 50,
1228
+ cursor: typeof params.cursor === 'string' ? params.cursor : undefined,
1229
+ });
1230
+ return textResult(JSON.stringify(tree, null, 2));
1231
+ } catch (err) {
1232
+ return textResult(
1233
+ `Failed to get referral tree: ${err instanceof Error ? err.message : String(err)}`,
1234
+ true,
1235
+ );
1236
+ }
1237
+ },
1238
+ });
1239
+
1240
+ api.registerTool({
1241
+ name: 'dim_get_referral_rewards',
1242
+ description:
1243
+ 'Get your referral reward history. Filter by status: PENDING (unclaimed), CLAIMED (already claimed), CANCELLED.',
1244
+ parameters: {
1245
+ type: 'object',
1246
+ properties: {
1247
+ status: {
1248
+ type: 'string',
1249
+ enum: ['PENDING', 'CLAIMED', 'CANCELLED'],
1250
+ description: 'Filter by reward status',
1251
+ },
1252
+ limit: {
1253
+ type: 'number',
1254
+ description: 'Max results (default: 50, max: 200)',
1255
+ },
1256
+ cursor: { type: 'string', description: 'Pagination cursor' },
1257
+ },
1258
+ additionalProperties: false,
1259
+ },
1260
+ async execute(_, params) {
1261
+ const c = await requireClient();
1262
+ if ('error' in c) return c.error;
1263
+ try {
1264
+ const rewards = await c.sdk.referrals.getRewards({
1265
+ status: params.status as
1266
+ | 'PENDING'
1267
+ | 'CLAIMED'
1268
+ | 'CANCELLED'
1269
+ | undefined,
1270
+ limit: typeof params.limit === 'number' ? params.limit : 50,
1271
+ cursor: typeof params.cursor === 'string' ? params.cursor : undefined,
1272
+ });
1273
+ return textResult(JSON.stringify(rewards, null, 2));
1274
+ } catch (err) {
1275
+ return textResult(
1276
+ `Failed to get referral rewards: ${err instanceof Error ? err.message : String(err)}`,
1277
+ true,
1278
+ );
1279
+ }
1280
+ },
1281
+ });
1282
+
1283
+ api.registerTool({
1284
+ name: 'dim_claim_referral_rewards',
1285
+ description:
1286
+ 'Claim all pending referral rewards. USDC is transferred from escrow to your wallet. Returns the claimed amount and transaction signature.',
1287
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
1288
+ async execute() {
1289
+ const c = await requireClient();
1290
+ if ('error' in c) return c.error;
1291
+ try {
1292
+ const result = await c.sdk.referrals.claimRewards();
1293
+ const claimedDollars = result.claimedAmount / 1_000_000;
1294
+ return textResult(
1295
+ JSON.stringify(
1296
+ { ...result, claimedFormatted: `$${claimedDollars.toFixed(2)}` },
1297
+ null,
1298
+ 2,
1299
+ ),
1300
+ );
1301
+ } catch (err) {
1302
+ return textResult(
1303
+ `Failed to claim rewards: ${err instanceof Error ? err.message : String(err)}`,
1304
+ true,
1305
+ );
1306
+ }
1307
+ },
1308
+ });
1309
+
1310
+ api.registerTool({
1311
+ name: 'dim_apply_referral_code',
1312
+ description:
1313
+ "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.",
1314
+ parameters: {
1315
+ type: 'object',
1316
+ properties: {
1317
+ referralCode: {
1318
+ type: 'string',
1319
+ description: "The referral code to apply (another user's username)",
1320
+ },
1321
+ },
1322
+ required: ['referralCode'],
1323
+ additionalProperties: false,
1324
+ },
1325
+ async execute(_, params) {
1326
+ const c = await requireClient();
1327
+ if ('error' in c) return c.error;
1328
+ try {
1329
+ const result = await c.sdk.referrals.applyCode(
1330
+ String(params.referralCode ?? ''),
1331
+ );
1332
+ return textResult(
1333
+ JSON.stringify(
1334
+ {
1335
+ ...result,
1336
+ message: `Referral code applied! ${result.referrerUsername} is now your referrer. You get a 10% fee discount on all games.`,
1337
+ },
1338
+ null,
1339
+ 2,
1340
+ ),
1341
+ );
1342
+ } catch (err) {
1343
+ return textResult(
1344
+ `Failed to apply referral code: ${err instanceof Error ? err.message : String(err)}`,
1345
+ true,
1346
+ );
1347
+ }
1348
+ },
1349
+ });
1350
+
1351
+ // --- Support ---
1352
+ api.registerTool({
1353
+ name: 'dim_create_support_ticket',
1354
+ description:
1355
+ '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.',
1356
+ parameters: {
1357
+ type: 'object',
1358
+ properties: {
1359
+ message: {
1360
+ type: 'string',
1361
+ description: 'Describe the issue or question (max 2000 chars)',
1362
+ },
1363
+ category: {
1364
+ type: 'string',
1365
+ enum: [
1366
+ 'BUG',
1367
+ 'FEATURE_REQUEST',
1368
+ 'QUESTION',
1369
+ 'ACCOUNT',
1370
+ 'PAYMENT',
1371
+ 'GAME',
1372
+ 'TECHNICAL',
1373
+ 'OTHER',
1374
+ ],
1375
+ description: 'Ticket category (default: OTHER)',
1376
+ },
1377
+ subject: {
1378
+ type: 'string',
1379
+ description:
1380
+ 'Short subject line (auto-generated from category if omitted)',
1381
+ },
1382
+ },
1383
+ required: ['message'],
1384
+ additionalProperties: false,
1385
+ },
1386
+ async execute(_, params) {
1387
+ const c = await requireClient();
1388
+ if ('error' in c) return c.error;
1389
+ try {
1390
+ const ticket = await c.sdk.support.create({
1391
+ message: String(params.message ?? ''),
1392
+ category: params.category as
1393
+ | 'BUG'
1394
+ | 'FEATURE_REQUEST'
1395
+ | 'QUESTION'
1396
+ | 'ACCOUNT'
1397
+ | 'PAYMENT'
1398
+ | 'GAME'
1399
+ | 'TECHNICAL'
1400
+ | 'OTHER'
1401
+ | undefined,
1402
+ subject:
1403
+ typeof params.subject === 'string' ? params.subject : undefined,
1404
+ });
1405
+ return textResult(
1406
+ JSON.stringify(
1407
+ {
1408
+ ...ticket,
1409
+ hint: 'Ticket created successfully. You can check its status with dim_get_my_tickets or add follow-up messages with dim_add_ticket_message.',
1410
+ },
1411
+ null,
1412
+ 2,
1413
+ ),
1414
+ );
1415
+ } catch (err) {
1416
+ return textResult(
1417
+ `Failed to create support ticket: ${err instanceof Error ? err.message : String(err)}`,
1418
+ true,
1419
+ );
1420
+ }
1421
+ },
1422
+ });
1423
+
1424
+ api.registerTool({
1425
+ name: 'dim_get_my_tickets',
1426
+ description:
1427
+ 'Get your support tickets. Filter by status (OPEN, IN_PROGRESS, WAITING_REPLY, RESOLVED, CLOSED) or category.',
1428
+ parameters: {
1429
+ type: 'object',
1430
+ properties: {
1431
+ status: {
1432
+ type: 'string',
1433
+ enum: ['OPEN', 'IN_PROGRESS', 'WAITING_REPLY', 'RESOLVED', 'CLOSED'],
1434
+ description: 'Filter by ticket status',
1435
+ },
1436
+ category: {
1437
+ type: 'string',
1438
+ enum: [
1439
+ 'BUG',
1440
+ 'FEATURE_REQUEST',
1441
+ 'QUESTION',
1442
+ 'ACCOUNT',
1443
+ 'PAYMENT',
1444
+ 'GAME',
1445
+ 'TECHNICAL',
1446
+ 'OTHER',
1447
+ ],
1448
+ description: 'Filter by category',
1449
+ },
1450
+ page: { type: 'number', description: 'Page number (default: 1)' },
1451
+ limit: {
1452
+ type: 'number',
1453
+ description: 'Results per page (default: 10)',
1454
+ },
1455
+ },
1456
+ additionalProperties: false,
1457
+ },
1458
+ async execute(_, params) {
1459
+ const c = await requireClient();
1460
+ if ('error' in c) return c.error;
1461
+ try {
1462
+ const tickets = await c.sdk.support.getMyTickets({
1463
+ status: params.status as any,
1464
+ category: params.category as any,
1465
+ page: typeof params.page === 'number' ? params.page : undefined,
1466
+ limit: typeof params.limit === 'number' ? params.limit : undefined,
1467
+ });
1468
+ return textResult(JSON.stringify(tickets, null, 2));
1469
+ } catch (err) {
1470
+ return textResult(
1471
+ `Failed to get tickets: ${err instanceof Error ? err.message : String(err)}`,
1472
+ true,
1473
+ );
1474
+ }
1475
+ },
1476
+ });
1477
+
1478
+ api.registerTool({
1479
+ name: 'dim_get_ticket',
1480
+ description:
1481
+ 'Get a specific support ticket with all messages. You can only view your own tickets.',
1482
+ parameters: {
1483
+ type: 'object',
1484
+ properties: {
1485
+ ticketId: { type: 'string', description: 'The ticket ID' },
1486
+ },
1487
+ required: ['ticketId'],
1488
+ additionalProperties: false,
1489
+ },
1490
+ async execute(_, params) {
1491
+ const c = await requireClient();
1492
+ if ('error' in c) return c.error;
1493
+ try {
1494
+ const ticket = await c.sdk.support.getMyTicketById(
1495
+ String(params.ticketId ?? ''),
1496
+ );
1497
+ return textResult(JSON.stringify(ticket, null, 2));
1498
+ } catch (err) {
1499
+ return textResult(
1500
+ `Failed to get ticket: ${err instanceof Error ? err.message : String(err)}`,
1501
+ true,
1502
+ );
1503
+ }
1504
+ },
1505
+ });
1506
+
1507
+ api.registerTool({
1508
+ name: 'dim_add_ticket_message',
1509
+ description:
1510
+ 'Add a follow-up message to an existing support ticket. You can only message your own tickets.',
1511
+ parameters: {
1512
+ type: 'object',
1513
+ properties: {
1514
+ ticketId: { type: 'string', description: 'The ticket ID' },
1515
+ message: {
1516
+ type: 'string',
1517
+ description: 'Your follow-up message (max 2000 chars)',
1518
+ },
1519
+ },
1520
+ required: ['ticketId', 'message'],
1521
+ additionalProperties: false,
1522
+ },
1523
+ async execute(_, params) {
1524
+ const c = await requireClient();
1525
+ if ('error' in c) return c.error;
1526
+ try {
1527
+ const msg = await c.sdk.support.addMessage(
1528
+ String(params.ticketId ?? ''),
1529
+ String(params.message ?? ''),
1530
+ );
1531
+ return textResult(
1532
+ JSON.stringify(
1533
+ { ...msg, hint: 'Message added. The DIM team will be notified.' },
1534
+ null,
1535
+ 2,
1536
+ ),
1537
+ );
1538
+ } catch (err) {
1539
+ return textResult(
1540
+ `Failed to add message: ${err instanceof Error ? err.message : String(err)}`,
1541
+ true,
1542
+ );
1543
+ }
1544
+ },
1545
+ });
1546
+
1547
+ api.registerTool({
1548
+ name: 'dim_close_ticket',
1549
+ description:
1550
+ 'Close a support ticket. Only close if your issue has been resolved.',
1551
+ parameters: {
1552
+ type: 'object',
1553
+ properties: {
1554
+ ticketId: { type: 'string', description: 'The ticket ID to close' },
1555
+ },
1556
+ required: ['ticketId'],
1557
+ additionalProperties: false,
1558
+ },
1559
+ async execute(_, params) {
1560
+ const c = await requireClient();
1561
+ if ('error' in c) return c.error;
1562
+ try {
1563
+ const ticket = await c.sdk.support.closeTicket(
1564
+ String(params.ticketId ?? ''),
1565
+ );
1566
+ return textResult(JSON.stringify(ticket, null, 2));
1567
+ } catch (err) {
1568
+ return textResult(
1569
+ `Failed to close ticket: ${err instanceof Error ? err.message : String(err)}`,
1570
+ true,
1571
+ );
1572
+ }
1573
+ },
1574
+ });
1575
+
1576
+ // --- Markets ---
1577
+ api.registerTool({
1578
+ name: 'dim_get_market',
1579
+ description:
1580
+ 'Get the prediction market state for an active game. Returns share prices (implied probabilities), total collateral locked, total volume, and resolution status.',
1581
+ parameters: {
1582
+ type: 'object',
1583
+ properties: {
1584
+ gameId: {
1585
+ type: 'string',
1586
+ description: 'The game ID to get market state for',
1587
+ },
1588
+ },
1589
+ required: ['gameId'],
1590
+ additionalProperties: false,
1591
+ },
1592
+ async execute(_, params) {
1593
+ const c = await requireClient();
1594
+ if ('error' in c) return c.error;
1595
+ try {
1596
+ const market = await c.sdk.markets.getMarket(
1597
+ String(params.gameId ?? ''),
1598
+ );
1599
+ return textResult(JSON.stringify(market, null, 2));
1600
+ } catch (err) {
1601
+ return textResult(
1602
+ `Failed to get market: ${err instanceof Error ? err.message : String(err)}`,
1603
+ true,
1604
+ );
1605
+ }
1606
+ },
1607
+ });
1608
+
1609
+ api.registerTool({
1610
+ name: 'dim_buy_shares',
1611
+ description:
1612
+ '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).',
1613
+ parameters: {
1614
+ type: 'object',
1615
+ properties: {
1616
+ gameId: { type: 'string', description: 'The game ID' },
1617
+ outcomeId: {
1618
+ type: 'string',
1619
+ description: 'The outcome (player user ID) to buy shares of',
1620
+ },
1621
+ amount: {
1622
+ type: 'number',
1623
+ description: 'Amount in USDC dollars (e.g., 1.00 for $1.00)',
1624
+ },
1625
+ },
1626
+ required: ['gameId', 'outcomeId', 'amount'],
1627
+ additionalProperties: false,
1628
+ },
1629
+ async execute(_, params) {
1630
+ const c = await requireClient();
1631
+ if ('error' in c) return c.error;
1632
+ const amount = Number(params.amount ?? 0);
1633
+ const gameId = String(params.gameId ?? '');
1634
+ const outcomeId = String(params.outcomeId ?? '');
1635
+ try {
1636
+ const amountMinor = Math.round(amount * 1_000_000);
1637
+ const { transaction: txBase64 } = await c.sdk.markets.prepareBuyOrder(
1638
+ gameId,
1639
+ outcomeId,
1640
+ amountMinor,
1641
+ );
1642
+ const tx = Transaction.from(Buffer.from(txBase64, 'base64'));
1643
+ tx.partialSign(c.getKeypair());
1644
+ const signedTxBase64 = Buffer.from(tx.serialize()).toString('base64');
1645
+ const result = await c.sdk.markets.submitBuyOrder(
1646
+ gameId,
1647
+ signedTxBase64,
1648
+ outcomeId,
1649
+ amountMinor,
1650
+ );
1651
+ return textResult(
1652
+ JSON.stringify(
1653
+ {
1654
+ success: true,
1655
+ tradeId: result.tradeId,
1656
+ sharesReceived: result.sharesReceived,
1657
+ costPerShare: result.costPerShare,
1658
+ amountSpent: `$${amount.toFixed(2)}`,
1659
+ },
1660
+ null,
1661
+ 2,
1662
+ ),
1663
+ );
1664
+ } catch (err) {
1665
+ return textResult(
1666
+ `Failed to buy shares: ${err instanceof Error ? err.message : String(err)}`,
1667
+ true,
1668
+ );
1669
+ }
1670
+ },
1671
+ });
1672
+
1673
+ api.registerTool({
1674
+ name: 'dim_sell_shares',
1675
+ description:
1676
+ 'Sell shares to exit a prediction market position. Shares are in minor units (1 share = 1,000,000 minor units).',
1677
+ parameters: {
1678
+ type: 'object',
1679
+ properties: {
1680
+ gameId: { type: 'string', description: 'The game ID' },
1681
+ outcomeId: {
1682
+ type: 'string',
1683
+ description: 'The outcome to sell shares of',
1684
+ },
1685
+ shares: {
1686
+ type: 'number',
1687
+ description:
1688
+ 'Number of shares to sell in minor units (1 share = 1,000,000)',
1689
+ },
1690
+ },
1691
+ required: ['gameId', 'outcomeId', 'shares'],
1692
+ additionalProperties: false,
1693
+ },
1694
+ async execute(_, params) {
1695
+ const c = await requireClient();
1696
+ if ('error' in c) return c.error;
1697
+ try {
1698
+ const result = await c.sdk.markets.sellShares(
1699
+ String(params.gameId ?? ''),
1700
+ String(params.outcomeId ?? ''),
1701
+ Number(params.shares ?? 0),
1702
+ );
1703
+ return textResult(
1704
+ JSON.stringify(
1705
+ {
1706
+ success: true,
1707
+ tradeId: result.tradeId,
1708
+ amountReceived: result.amountReceived,
1709
+ pricePerShare: result.pricePerShare,
1710
+ newPrices: result.newPrices,
1711
+ },
1712
+ null,
1713
+ 2,
1714
+ ),
1715
+ );
1716
+ } catch (err) {
1717
+ return textResult(
1718
+ `Failed to sell shares: ${err instanceof Error ? err.message : String(err)}`,
1719
+ true,
1720
+ );
1721
+ }
1722
+ },
1723
+ });
1724
+
1725
+ api.registerTool({
1726
+ name: 'dim_get_positions',
1727
+ description:
1728
+ 'Get your current prediction market positions for a game. Shows shares held, average cost, current value, and unrealized profit/loss.',
1729
+ parameters: {
1730
+ type: 'object',
1731
+ properties: { gameId: { type: 'string', description: 'The game ID' } },
1732
+ required: ['gameId'],
1733
+ additionalProperties: false,
1734
+ },
1735
+ async execute(_, params) {
1736
+ const c = await requireClient();
1737
+ if ('error' in c) return c.error;
1738
+ try {
1739
+ const positions = await c.sdk.markets.getMyPositions(
1740
+ String(params.gameId ?? ''),
1741
+ );
1742
+ return textResult(JSON.stringify(positions, null, 2));
1743
+ } catch (err) {
1744
+ return textResult(
1745
+ `Failed to get positions: ${err instanceof Error ? err.message : String(err)}`,
1746
+ true,
1747
+ );
1748
+ }
1749
+ },
1750
+ });
1751
+
1752
+ api.registerTool({
1753
+ name: 'dim_redeem_shares',
1754
+ description:
1755
+ 'Redeem winning shares after a prediction market has been resolved. Winners split the pool pro-rata by shares held, minus a 3% fee.',
1756
+ parameters: {
1757
+ type: 'object',
1758
+ properties: {
1759
+ gameId: {
1760
+ type: 'string',
1761
+ description: 'The game ID to redeem shares for',
1762
+ },
1763
+ },
1764
+ required: ['gameId'],
1765
+ additionalProperties: false,
1766
+ },
1767
+ async execute(_, params) {
1768
+ const c = await requireClient();
1769
+ if ('error' in c) return c.error;
1770
+ try {
1771
+ const result = await c.sdk.markets.redeemShares(
1772
+ String(params.gameId ?? ''),
1773
+ );
1774
+ return textResult(JSON.stringify(result, null, 2));
1775
+ } catch (err) {
1776
+ return textResult(
1777
+ `Failed to redeem shares: ${err instanceof Error ? err.message : String(err)}`,
1778
+ true,
1779
+ );
1780
+ }
1781
+ },
1782
+ });
1783
+
1784
+ api.registerTool({
1785
+ name: 'dim_get_market_analytics',
1786
+ description:
1787
+ 'Get platform analytics for prediction markets. Shows total markets, orders, volume, fees, unique traders, and daily trends. Requires admin privileges.',
1788
+ parameters: {
1789
+ type: 'object',
1790
+ properties: {
1791
+ type: {
1792
+ type: 'string',
1793
+ enum: ['overview', 'daily', 'markets'],
1794
+ description:
1795
+ 'overview = aggregate totals, daily = per-day breakdown, markets = paginated list of all markets',
1796
+ },
1797
+ days: {
1798
+ type: 'number',
1799
+ description: 'Number of days for daily stats (default 30)',
1800
+ },
1801
+ page: {
1802
+ type: 'number',
1803
+ description: 'Page number for markets list (default 1)',
1804
+ },
1805
+ limit: {
1806
+ type: 'number',
1807
+ description: 'Items per page for markets list (default 20)',
1808
+ },
1809
+ },
1810
+ required: ['type'],
1811
+ additionalProperties: false,
1812
+ },
1813
+ async execute(_, params) {
1814
+ const c = await requireClient();
1815
+ if ('error' in c) return c.error;
1816
+ try {
1817
+ let data: unknown;
1818
+ switch (String(params.type)) {
1819
+ case 'overview':
1820
+ data = await c.sdk.markets.getAdminStats();
1821
+ break;
1822
+ case 'daily':
1823
+ data = await c.sdk.markets.getAdminDailyStats(
1824
+ typeof params.days === 'number' ? params.days : 30,
1825
+ );
1826
+ break;
1827
+ case 'markets':
1828
+ data = await c.sdk.markets.getAdminMarkets(
1829
+ typeof params.page === 'number' ? params.page : undefined,
1830
+ typeof params.limit === 'number' ? params.limit : undefined,
1831
+ );
1832
+ break;
1833
+ default:
1834
+ return textResult(
1835
+ 'Invalid type. Use overview, daily, or markets.',
1836
+ true,
1837
+ );
1838
+ }
1839
+ return textResult(JSON.stringify(data, null, 2));
1840
+ } catch (err) {
1841
+ return textResult(
1842
+ `Failed to get market analytics: ${err instanceof Error ? err.message : String(err)}`,
1843
+ true,
1844
+ );
1845
+ }
1846
+ },
1847
+ });
1848
+ }