@catalyst-team/poly-mcp 0.1.1 → 0.1.3

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 (107) hide show
  1. package/README.md +264 -21
  2. package/dist/errors.d.ts +11 -0
  3. package/dist/errors.d.ts.map +1 -1
  4. package/dist/errors.js +13 -2
  5. package/dist/errors.js.map +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +98 -5
  8. package/dist/index.js.map +1 -1
  9. package/dist/sdk-instance.d.ts +27 -0
  10. package/dist/sdk-instance.d.ts.map +1 -0
  11. package/dist/sdk-instance.js +64 -0
  12. package/dist/sdk-instance.js.map +1 -0
  13. package/dist/server.d.ts +13 -1
  14. package/dist/server.d.ts.map +1 -1
  15. package/dist/server.js +29 -27
  16. package/dist/server.js.map +1 -1
  17. package/dist/tools/guide.d.ts.map +1 -1
  18. package/dist/tools/guide.js +159 -1
  19. package/dist/tools/guide.js.map +1 -1
  20. package/dist/tools/index.d.ts +8 -4
  21. package/dist/tools/index.d.ts.map +1 -1
  22. package/dist/tools/index.js +20 -4
  23. package/dist/tools/index.js.map +1 -1
  24. package/dist/tools/insider-detection.d.ts +175 -0
  25. package/dist/tools/insider-detection.d.ts.map +1 -0
  26. package/dist/tools/insider-detection.js +654 -0
  27. package/dist/tools/insider-detection.js.map +1 -0
  28. package/dist/tools/insider-signals.d.ts +56 -0
  29. package/dist/tools/insider-signals.d.ts.map +1 -0
  30. package/dist/tools/insider-signals.js +170 -0
  31. package/dist/tools/insider-signals.js.map +1 -0
  32. package/dist/tools/market.d.ts +25 -1
  33. package/dist/tools/market.d.ts.map +1 -1
  34. package/dist/tools/market.js +504 -12
  35. package/dist/tools/market.js.map +1 -1
  36. package/dist/tools/onchain.d.ts +240 -0
  37. package/dist/tools/onchain.d.ts.map +1 -0
  38. package/dist/tools/onchain.js +610 -0
  39. package/dist/tools/onchain.js.map +1 -0
  40. package/dist/tools/order.d.ts.map +1 -1
  41. package/dist/tools/order.js +13 -6
  42. package/dist/tools/order.js.map +1 -1
  43. package/dist/tools/trade.d.ts +15 -0
  44. package/dist/tools/trade.d.ts.map +1 -1
  45. package/dist/tools/trade.js +216 -39
  46. package/dist/tools/trade.js.map +1 -1
  47. package/dist/tools/trader.d.ts +4 -1
  48. package/dist/tools/trader.d.ts.map +1 -1
  49. package/dist/tools/trader.js +316 -4
  50. package/dist/tools/trader.js.map +1 -1
  51. package/dist/tools/wallet-classification.d.ts +166 -0
  52. package/dist/tools/wallet-classification.d.ts.map +1 -0
  53. package/dist/tools/wallet-classification.js +455 -0
  54. package/dist/tools/wallet-classification.js.map +1 -0
  55. package/dist/tools/wallet.d.ts +56 -7
  56. package/dist/tools/wallet.d.ts.map +1 -1
  57. package/dist/tools/wallet.js +141 -20
  58. package/dist/tools/wallet.js.map +1 -1
  59. package/dist/types.d.ts +269 -10
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/wallet-manager.d.ts +67 -0
  62. package/dist/wallet-manager.d.ts.map +1 -0
  63. package/dist/wallet-manager.js +180 -0
  64. package/dist/wallet-manager.js.map +1 -0
  65. package/docs/01-mcp.md +554 -32
  66. package/docs/02-wallet-deep-research.md +344 -0
  67. package/docs/e2e-02/00-gap-analysis.md +211 -0
  68. package/docs/e2e-02/01-test-scenarios.md +530 -0
  69. package/docs/e2e-02/02-implementation-plan.md +190 -0
  70. package/docs/e2e-02/README.md +102 -0
  71. package/docs/reports/simonbanza-strategy-analysis-2025-12-25.md +420 -0
  72. package/docs/reports/smart-money-analysis-2025-12-23-cn.md +840 -0
  73. package/docs/reports/smart-money-trading-strategies-2025-12-25.md +440 -0
  74. package/docs/reports/weekly/01-v2.5.md +352 -0
  75. package/docs/reports/weekly/01.md +402 -0
  76. package/docs/reports/weekly/02-deep.md +558 -0
  77. package/docs/reports/weekly/02.md +505 -0
  78. package/docs/reports/weekly/03.md +437 -0
  79. package/docs/reports/weekly/04.md +418 -0
  80. package/docs/reports/weekly/05.md +485 -0
  81. package/docs/reports/weekly/06.md +436 -0
  82. package/docs/reports/weekly/07.md +381 -0
  83. package/docs/reports/weekly/08.md +502 -0
  84. package/docs/reports/weekly/09.md +441 -0
  85. package/docs/reports/weekly/10.md +511 -0
  86. package/docs/reports/weekly/README.md +188 -0
  87. package/docs/reports/weekly/prompt-v2.5.md +1019 -0
  88. package/docs/reports/weekly/prompt-v3.md +432 -0
  89. package/docs/reports/weekly/prompt.md +841 -0
  90. package/package.json +3 -2
  91. package/src/errors.ts +13 -2
  92. package/src/index.ts +286 -1
  93. package/src/sdk-instance.ts +78 -0
  94. package/src/server.ts +30 -28
  95. package/src/tools/guide.ts +160 -1
  96. package/src/tools/index.ts +65 -0
  97. package/src/tools/insider-detection.ts +899 -0
  98. package/src/tools/insider-signals.ts +213 -0
  99. package/src/tools/market.ts +569 -12
  100. package/src/tools/onchain.ts +738 -0
  101. package/src/tools/order.ts +25 -12
  102. package/src/tools/trade.ts +265 -53
  103. package/src/tools/trader.ts +350 -4
  104. package/src/tools/wallet-classification.ts +587 -0
  105. package/src/tools/wallet.ts +172 -23
  106. package/src/types.ts +294 -11
  107. package/src/wallet-manager.ts +209 -0
@@ -0,0 +1,587 @@
1
+ /**
2
+ * Wallet Classification Tools - MCP tools for system-level wallet tagging
3
+ *
4
+ * These tools enable Agent to classify wallets with tags based on analysis.
5
+ * Uses WalletClassificationService from @catalyst-team/smart-money package.
6
+ *
7
+ * Key features:
8
+ * - System-level classification (not user-personal like StarList)
9
+ * - 22 predefined tags across 7 categories
10
+ * - Agent can dynamically add new tag definitions
11
+ * - Persistent storage in ~/.polymarket/wallet-classifications.json
12
+ */
13
+
14
+ import type { PolymarketSDK } from '@catalyst-team/poly-sdk';
15
+ import {
16
+ WalletClassificationService,
17
+ type TagCategory,
18
+ type TagDefinition,
19
+ type WalletClassification,
20
+ type ClassifyWalletOptions,
21
+ type AddTagDefinitionOptions,
22
+ type ClassificationMetrics,
23
+ } from '@catalyst-team/smart-money';
24
+ import type { ToolDefinition } from '../types.js';
25
+ import { wrapError, McpToolError, ErrorCode } from '../errors.js';
26
+
27
+ // Singleton service instance
28
+ let classificationService: WalletClassificationService | null = null;
29
+
30
+ /**
31
+ * Get or create the classification service singleton
32
+ */
33
+ function getService(): WalletClassificationService {
34
+ if (!classificationService) {
35
+ classificationService = new WalletClassificationService();
36
+ }
37
+ return classificationService;
38
+ }
39
+
40
+ // Tool definitions
41
+ export const walletClassificationToolDefinitions: ToolDefinition[] = [
42
+ // ===== Tag Definition Tools =====
43
+ {
44
+ name: 'get_tag_definitions',
45
+ description:
46
+ 'Get all available tag definitions for wallet classification. Returns predefined system tags and any agent-created tags. Optionally filter by category.',
47
+ inputSchema: {
48
+ type: 'object',
49
+ properties: {
50
+ category: {
51
+ type: 'string',
52
+ enum: [
53
+ 'trading-style',
54
+ 'market-preference',
55
+ 'scale',
56
+ 'performance',
57
+ 'activity',
58
+ 'risk-profile',
59
+ 'special',
60
+ ],
61
+ description:
62
+ 'Filter by tag category. Categories: trading-style (High frequency, swing trader), market-preference (Crypto focused, politics focused), scale (Whale, shark, fish), performance (Profitable, losing), activity (Active, dormant), risk-profile (High conviction, risk averse), special (Insider suspected, copy worthy)',
63
+ },
64
+ },
65
+ },
66
+ },
67
+ {
68
+ name: 'add_tag_definition',
69
+ description:
70
+ 'Add a new tag definition when discovering new wallet patterns. Use this when existing tags do not adequately describe a wallet behavior pattern. Tag ID must be kebab-case.',
71
+ inputSchema: {
72
+ type: 'object',
73
+ properties: {
74
+ id: {
75
+ type: 'string',
76
+ description:
77
+ 'Unique tag ID in kebab-case (e.g., "news-sensitive", "early-mover")',
78
+ },
79
+ name: {
80
+ type: 'string',
81
+ description: 'Display name (e.g., "News Sensitive", "Early Mover")',
82
+ },
83
+ description: {
84
+ type: 'string',
85
+ description: 'Detailed description of what this tag means',
86
+ },
87
+ category: {
88
+ type: 'string',
89
+ enum: [
90
+ 'trading-style',
91
+ 'market-preference',
92
+ 'scale',
93
+ 'performance',
94
+ 'activity',
95
+ 'risk-profile',
96
+ 'special',
97
+ ],
98
+ description: 'Category this tag belongs to',
99
+ },
100
+ criteria: {
101
+ type: 'string',
102
+ description:
103
+ 'Criteria for assigning this tag (e.g., "Trades within 1 hour of major news events")',
104
+ },
105
+ },
106
+ required: ['id', 'name', 'description', 'category'],
107
+ },
108
+ },
109
+ // ===== Wallet Classification Tools =====
110
+ {
111
+ name: 'get_wallet_classification',
112
+ description:
113
+ 'Get the classification for a specific wallet address, including all assigned tags, confidence score, and analysis metrics.',
114
+ inputSchema: {
115
+ type: 'object',
116
+ properties: {
117
+ address: {
118
+ type: 'string',
119
+ description: 'Wallet address (0x...)',
120
+ },
121
+ },
122
+ required: ['address'],
123
+ },
124
+ },
125
+ {
126
+ name: 'classify_wallet',
127
+ description:
128
+ 'Assign classification tags to a wallet based on analysis. Include confidence score (0-1), analysis metrics, and optional notes.',
129
+ inputSchema: {
130
+ type: 'object',
131
+ properties: {
132
+ address: {
133
+ type: 'string',
134
+ description: 'Wallet address (0x...)',
135
+ },
136
+ tags: {
137
+ type: 'array',
138
+ items: { type: 'string' },
139
+ description:
140
+ 'Array of tag IDs to assign (e.g., ["whale", "crypto-focused", "consistently-profitable"])',
141
+ },
142
+ confidence: {
143
+ type: 'number',
144
+ description: 'Classification confidence (0-1, default: 0.8)',
145
+ },
146
+ metrics: {
147
+ type: 'object',
148
+ properties: {
149
+ totalPnL: {
150
+ type: 'number',
151
+ description: 'Total PnL in USD',
152
+ },
153
+ winRate: {
154
+ type: 'number',
155
+ description: 'Win rate (0-1)',
156
+ },
157
+ primaryCategory: {
158
+ type: 'string',
159
+ description: 'Primary market category (crypto, politics, sports, etc.)',
160
+ },
161
+ tradeCount: {
162
+ type: 'number',
163
+ description: 'Total number of trades',
164
+ },
165
+ avgHoldingDays: {
166
+ type: 'number',
167
+ description: 'Average holding period in days',
168
+ },
169
+ },
170
+ description: 'Metrics captured during analysis',
171
+ },
172
+ notes: {
173
+ type: 'string',
174
+ description:
175
+ 'Additional notes from the analysis (e.g., "3 times precise positioning before ETH ETF events")',
176
+ },
177
+ },
178
+ required: ['address', 'tags'],
179
+ },
180
+ },
181
+ {
182
+ name: 'get_wallets_by_tag',
183
+ description:
184
+ 'Get all wallets with a specific tag. Useful for finding similar wallets or building watch lists.',
185
+ inputSchema: {
186
+ type: 'object',
187
+ properties: {
188
+ tagId: {
189
+ type: 'string',
190
+ description: 'Tag ID to search for (e.g., "whale", "insider-suspected")',
191
+ },
192
+ limit: {
193
+ type: 'number',
194
+ description: 'Maximum number of wallets to return (default: 50)',
195
+ },
196
+ sortBy: {
197
+ type: 'string',
198
+ enum: ['confidence', 'analyzedAt'],
199
+ description: 'Sort by field (default: confidence)',
200
+ },
201
+ sortOrder: {
202
+ type: 'string',
203
+ enum: ['asc', 'desc'],
204
+ description: 'Sort order (default: desc)',
205
+ },
206
+ },
207
+ required: ['tagId'],
208
+ },
209
+ },
210
+ {
211
+ name: 'remove_wallet_tag',
212
+ description: 'Remove a specific tag from a wallet classification.',
213
+ inputSchema: {
214
+ type: 'object',
215
+ properties: {
216
+ address: {
217
+ type: 'string',
218
+ description: 'Wallet address (0x...)',
219
+ },
220
+ tagId: {
221
+ type: 'string',
222
+ description: 'Tag ID to remove',
223
+ },
224
+ },
225
+ required: ['address', 'tagId'],
226
+ },
227
+ },
228
+ ];
229
+
230
+ // ===== Input Types =====
231
+
232
+ interface GetTagDefinitionsInput {
233
+ category?: TagCategory;
234
+ }
235
+
236
+ interface AddTagDefinitionInput {
237
+ id: string;
238
+ name: string;
239
+ description: string;
240
+ category: TagCategory;
241
+ criteria?: string;
242
+ }
243
+
244
+ interface GetWalletClassificationInput {
245
+ address: string;
246
+ }
247
+
248
+ interface ClassifyWalletInput {
249
+ address: string;
250
+ tags: string[];
251
+ confidence?: number;
252
+ metrics?: ClassificationMetrics;
253
+ notes?: string;
254
+ }
255
+
256
+ interface GetWalletsByTagInput {
257
+ tagId: string;
258
+ limit?: number;
259
+ sortBy?: 'confidence' | 'analyzedAt';
260
+ sortOrder?: 'asc' | 'desc';
261
+ }
262
+
263
+ interface RemoveWalletTagInput {
264
+ address: string;
265
+ tagId: string;
266
+ }
267
+
268
+ // ===== Tag Definition Handlers =====
269
+
270
+ /**
271
+ * Get all tag definitions
272
+ */
273
+ export async function handleGetTagDefinitions(
274
+ _sdk: PolymarketSDK,
275
+ input: GetTagDefinitionsInput
276
+ ) {
277
+ try {
278
+ const service = getService();
279
+ const tags = await service.getTagDefinitions(input.category);
280
+
281
+ // Group by category for better readability
282
+ const byCategory: Record<string, TagDefinition[]> = {};
283
+ for (const tag of tags) {
284
+ if (!byCategory[tag.category]) {
285
+ byCategory[tag.category] = [];
286
+ }
287
+ byCategory[tag.category].push(tag);
288
+ }
289
+
290
+ return {
291
+ tags: tags.map((t: TagDefinition) => ({
292
+ id: t.id,
293
+ name: t.name,
294
+ description: t.description,
295
+ category: t.category,
296
+ criteria: t.criteria,
297
+ createdBy: t.createdBy,
298
+ })),
299
+ totalCount: tags.length,
300
+ byCategory: Object.entries(byCategory).map(([category, categoryTags]) => ({
301
+ category,
302
+ count: categoryTags.length,
303
+ tags: categoryTags.map((t) => t.id),
304
+ })),
305
+ categories: [
306
+ 'trading-style',
307
+ 'market-preference',
308
+ 'scale',
309
+ 'performance',
310
+ 'activity',
311
+ 'risk-profile',
312
+ 'special',
313
+ ],
314
+ };
315
+ } catch (err) {
316
+ throw wrapError(err);
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Add a new tag definition
322
+ */
323
+ export async function handleAddTagDefinition(
324
+ _sdk: PolymarketSDK,
325
+ input: AddTagDefinitionInput
326
+ ) {
327
+ try {
328
+ // Validate tag ID format
329
+ if (!/^[a-z0-9-]+$/.test(input.id)) {
330
+ throw new McpToolError(
331
+ ErrorCode.INVALID_INPUT,
332
+ 'Tag ID must be kebab-case (lowercase letters, numbers, and hyphens only)'
333
+ );
334
+ }
335
+
336
+ const service = getService();
337
+
338
+ // Check if tag already exists
339
+ const existing = await service.getTagDefinition(input.id);
340
+ if (existing) {
341
+ throw new McpToolError(
342
+ ErrorCode.INVALID_INPUT,
343
+ `Tag "${input.id}" already exists`
344
+ );
345
+ }
346
+
347
+ const tag = await service.addTagDefinition({
348
+ id: input.id,
349
+ name: input.name,
350
+ description: input.description,
351
+ category: input.category,
352
+ criteria: input.criteria,
353
+ });
354
+
355
+ return {
356
+ success: true,
357
+ tag: {
358
+ id: tag.id,
359
+ name: tag.name,
360
+ description: tag.description,
361
+ category: tag.category,
362
+ criteria: tag.criteria,
363
+ createdBy: tag.createdBy,
364
+ createdAt: new Date(tag.createdAt).toISOString(),
365
+ },
366
+ message: `Tag "${tag.name}" (${tag.id}) created successfully`,
367
+ };
368
+ } catch (err) {
369
+ throw wrapError(err);
370
+ }
371
+ }
372
+
373
+ // ===== Wallet Classification Handlers =====
374
+
375
+ /**
376
+ * Get wallet classification
377
+ */
378
+ export async function handleGetWalletClassification(
379
+ _sdk: PolymarketSDK,
380
+ input: GetWalletClassificationInput
381
+ ) {
382
+ try {
383
+ // Validate address
384
+ if (!input.address || !input.address.startsWith('0x')) {
385
+ throw new McpToolError(
386
+ ErrorCode.INVALID_INPUT,
387
+ 'Invalid wallet address. Must start with 0x'
388
+ );
389
+ }
390
+
391
+ const service = getService();
392
+ const classification = await service.getWalletClassification(input.address);
393
+
394
+ if (!classification) {
395
+ return {
396
+ found: false,
397
+ address: input.address.toLowerCase(),
398
+ message: 'Wallet has not been classified yet',
399
+ };
400
+ }
401
+
402
+ // Get tag details for each assigned tag
403
+ const tagDetails = await Promise.all(
404
+ classification.tags.map(async (tagId: string) => {
405
+ const def = await service.getTagDefinition(tagId);
406
+ return def
407
+ ? { id: tagId, name: def.name, category: def.category }
408
+ : { id: tagId, name: tagId, category: 'unknown' };
409
+ })
410
+ );
411
+
412
+ return {
413
+ found: true,
414
+ address: classification.address,
415
+ tags: tagDetails,
416
+ tagIds: classification.tags,
417
+ confidence: classification.confidence,
418
+ analyzedAt: new Date(classification.analyzedAt).toISOString(),
419
+ analyzedBy: classification.analyzedBy,
420
+ metrics: classification.metrics || null,
421
+ notes: classification.notes || null,
422
+ };
423
+ } catch (err) {
424
+ throw wrapError(err);
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Classify a wallet with tags
430
+ */
431
+ export async function handleClassifyWallet(
432
+ _sdk: PolymarketSDK,
433
+ input: ClassifyWalletInput
434
+ ) {
435
+ try {
436
+ // Validate address
437
+ if (!input.address || !input.address.startsWith('0x')) {
438
+ throw new McpToolError(
439
+ ErrorCode.INVALID_INPUT,
440
+ 'Invalid wallet address. Must start with 0x'
441
+ );
442
+ }
443
+
444
+ // Validate tags array
445
+ if (!Array.isArray(input.tags) || input.tags.length === 0) {
446
+ throw new McpToolError(
447
+ ErrorCode.INVALID_INPUT,
448
+ 'Tags must be a non-empty array of tag IDs'
449
+ );
450
+ }
451
+
452
+ const service = getService();
453
+
454
+ // Validate all tags exist
455
+ const invalidTags: string[] = [];
456
+ for (const tagId of input.tags) {
457
+ const def = await service.getTagDefinition(tagId);
458
+ if (!def) {
459
+ invalidTags.push(tagId);
460
+ }
461
+ }
462
+
463
+ if (invalidTags.length > 0) {
464
+ throw new McpToolError(
465
+ ErrorCode.INVALID_INPUT,
466
+ `Unknown tag IDs: ${invalidTags.join(', ')}. Use get_tag_definitions to see available tags or add_tag_definition to create new ones.`
467
+ );
468
+ }
469
+
470
+ // Classify the wallet
471
+ const classification = await service.classifyWallet({
472
+ address: input.address,
473
+ tags: input.tags,
474
+ confidence: input.confidence,
475
+ analyzedBy: 'agent',
476
+ metrics: input.metrics,
477
+ notes: input.notes,
478
+ });
479
+
480
+ // Get tag details
481
+ const tagDetails = await Promise.all(
482
+ classification.tags.map(async (tagId: string) => {
483
+ const def = await service.getTagDefinition(tagId);
484
+ return def
485
+ ? { id: tagId, name: def.name, category: def.category }
486
+ : { id: tagId, name: tagId, category: 'unknown' };
487
+ })
488
+ );
489
+
490
+ return {
491
+ success: true,
492
+ address: classification.address,
493
+ tags: tagDetails,
494
+ confidence: classification.confidence,
495
+ analyzedAt: new Date(classification.analyzedAt).toISOString(),
496
+ analyzedBy: classification.analyzedBy,
497
+ metrics: classification.metrics || null,
498
+ notes: classification.notes || null,
499
+ message: `Wallet ${classification.address} classified with ${classification.tags.length} tags`,
500
+ };
501
+ } catch (err) {
502
+ throw wrapError(err);
503
+ }
504
+ }
505
+
506
+ /**
507
+ * Get wallets by tag
508
+ */
509
+ export async function handleGetWalletsByTag(
510
+ _sdk: PolymarketSDK,
511
+ input: GetWalletsByTagInput
512
+ ) {
513
+ try {
514
+ const service = getService();
515
+
516
+ // Check if tag exists
517
+ const tagDef = await service.getTagDefinition(input.tagId);
518
+ if (!tagDef) {
519
+ throw new McpToolError(
520
+ ErrorCode.INVALID_INPUT,
521
+ `Tag "${input.tagId}" not found. Use get_tag_definitions to see available tags.`
522
+ );
523
+ }
524
+
525
+ const wallets = await service.getWalletsByTag(input.tagId, {
526
+ limit: input.limit,
527
+ });
528
+
529
+ return {
530
+ tag: {
531
+ id: tagDef.id,
532
+ name: tagDef.name,
533
+ description: tagDef.description,
534
+ category: tagDef.category,
535
+ },
536
+ wallets: wallets.map((w: WalletClassification) => ({
537
+ address: w.address,
538
+ allTags: w.tags,
539
+ confidence: w.confidence,
540
+ analyzedAt: new Date(w.analyzedAt).toISOString(),
541
+ analyzedBy: w.analyzedBy,
542
+ metrics: w.metrics || null,
543
+ })),
544
+ totalCount: wallets.length,
545
+ };
546
+ } catch (err) {
547
+ throw wrapError(err);
548
+ }
549
+ }
550
+
551
+ /**
552
+ * Remove a tag from a wallet
553
+ */
554
+ export async function handleRemoveWalletTag(
555
+ _sdk: PolymarketSDK,
556
+ input: RemoveWalletTagInput
557
+ ) {
558
+ try {
559
+ // Validate address
560
+ if (!input.address || !input.address.startsWith('0x')) {
561
+ throw new McpToolError(
562
+ ErrorCode.INVALID_INPUT,
563
+ 'Invalid wallet address. Must start with 0x'
564
+ );
565
+ }
566
+
567
+ const service = getService();
568
+ const result = await service.removeWalletTag(input.address, input.tagId);
569
+
570
+ if (!result) {
571
+ return {
572
+ success: false,
573
+ address: input.address.toLowerCase(),
574
+ message: `Wallet not found or tag "${input.tagId}" not assigned to this wallet`,
575
+ };
576
+ }
577
+
578
+ return {
579
+ success: true,
580
+ address: result.address,
581
+ remainingTags: result.tags,
582
+ message: `Tag "${input.tagId}" removed from wallet ${result.address}`,
583
+ };
584
+ } catch (err) {
585
+ throw wrapError(err);
586
+ }
587
+ }