@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.
- package/README.md +264 -21
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +13 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +98 -5
- package/dist/index.js.map +1 -1
- package/dist/sdk-instance.d.ts +27 -0
- package/dist/sdk-instance.d.ts.map +1 -0
- package/dist/sdk-instance.js +64 -0
- package/dist/sdk-instance.js.map +1 -0
- package/dist/server.d.ts +13 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +29 -27
- package/dist/server.js.map +1 -1
- package/dist/tools/guide.d.ts.map +1 -1
- package/dist/tools/guide.js +159 -1
- package/dist/tools/guide.js.map +1 -1
- package/dist/tools/index.d.ts +8 -4
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +20 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/insider-detection.d.ts +175 -0
- package/dist/tools/insider-detection.d.ts.map +1 -0
- package/dist/tools/insider-detection.js +654 -0
- package/dist/tools/insider-detection.js.map +1 -0
- package/dist/tools/insider-signals.d.ts +56 -0
- package/dist/tools/insider-signals.d.ts.map +1 -0
- package/dist/tools/insider-signals.js +170 -0
- package/dist/tools/insider-signals.js.map +1 -0
- package/dist/tools/market.d.ts +25 -1
- package/dist/tools/market.d.ts.map +1 -1
- package/dist/tools/market.js +504 -12
- package/dist/tools/market.js.map +1 -1
- package/dist/tools/onchain.d.ts +240 -0
- package/dist/tools/onchain.d.ts.map +1 -0
- package/dist/tools/onchain.js +610 -0
- package/dist/tools/onchain.js.map +1 -0
- package/dist/tools/order.d.ts.map +1 -1
- package/dist/tools/order.js +13 -6
- package/dist/tools/order.js.map +1 -1
- package/dist/tools/trade.d.ts +15 -0
- package/dist/tools/trade.d.ts.map +1 -1
- package/dist/tools/trade.js +216 -39
- package/dist/tools/trade.js.map +1 -1
- package/dist/tools/trader.d.ts +4 -1
- package/dist/tools/trader.d.ts.map +1 -1
- package/dist/tools/trader.js +316 -4
- package/dist/tools/trader.js.map +1 -1
- package/dist/tools/wallet-classification.d.ts +166 -0
- package/dist/tools/wallet-classification.d.ts.map +1 -0
- package/dist/tools/wallet-classification.js +455 -0
- package/dist/tools/wallet-classification.js.map +1 -0
- package/dist/tools/wallet.d.ts +56 -7
- package/dist/tools/wallet.d.ts.map +1 -1
- package/dist/tools/wallet.js +141 -20
- package/dist/tools/wallet.js.map +1 -1
- package/dist/types.d.ts +269 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/wallet-manager.d.ts +67 -0
- package/dist/wallet-manager.d.ts.map +1 -0
- package/dist/wallet-manager.js +180 -0
- package/dist/wallet-manager.js.map +1 -0
- package/docs/01-mcp.md +554 -32
- package/docs/02-wallet-deep-research.md +344 -0
- package/docs/e2e-02/00-gap-analysis.md +211 -0
- package/docs/e2e-02/01-test-scenarios.md +530 -0
- package/docs/e2e-02/02-implementation-plan.md +190 -0
- package/docs/e2e-02/README.md +102 -0
- package/docs/reports/simonbanza-strategy-analysis-2025-12-25.md +420 -0
- package/docs/reports/smart-money-analysis-2025-12-23-cn.md +840 -0
- package/docs/reports/smart-money-trading-strategies-2025-12-25.md +440 -0
- package/docs/reports/weekly/01-v2.5.md +352 -0
- package/docs/reports/weekly/01.md +402 -0
- package/docs/reports/weekly/02-deep.md +558 -0
- package/docs/reports/weekly/02.md +505 -0
- package/docs/reports/weekly/03.md +437 -0
- package/docs/reports/weekly/04.md +418 -0
- package/docs/reports/weekly/05.md +485 -0
- package/docs/reports/weekly/06.md +436 -0
- package/docs/reports/weekly/07.md +381 -0
- package/docs/reports/weekly/08.md +502 -0
- package/docs/reports/weekly/09.md +441 -0
- package/docs/reports/weekly/10.md +511 -0
- package/docs/reports/weekly/README.md +188 -0
- package/docs/reports/weekly/prompt-v2.5.md +1019 -0
- package/docs/reports/weekly/prompt-v3.md +432 -0
- package/docs/reports/weekly/prompt.md +841 -0
- package/package.json +3 -2
- package/src/errors.ts +13 -2
- package/src/index.ts +286 -1
- package/src/sdk-instance.ts +78 -0
- package/src/server.ts +30 -28
- package/src/tools/guide.ts +160 -1
- package/src/tools/index.ts +65 -0
- package/src/tools/insider-detection.ts +899 -0
- package/src/tools/insider-signals.ts +213 -0
- package/src/tools/market.ts +569 -12
- package/src/tools/onchain.ts +738 -0
- package/src/tools/order.ts +25 -12
- package/src/tools/trade.ts +265 -53
- package/src/tools/trader.ts +350 -4
- package/src/tools/wallet-classification.ts +587 -0
- package/src/tools/wallet.ts +172 -23
- package/src/types.ts +294 -11
- 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
|
+
}
|