@gala-chain/launchpad-mcp-server 1.23.1 → 1.24.1
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/CHANGELOG.md +20 -0
- package/README.md +34 -6
- package/dist/constants/mcpToolNames.d.ts +6 -2
- package/dist/constants/mcpToolNames.d.ts.map +1 -1
- package/dist/constants/mcpToolNames.js +4 -2
- package/dist/constants/mcpToolNames.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/prompts/explore-dex-pools.d.ts +20 -0
- package/dist/prompts/explore-dex-pools.d.ts.map +1 -0
- package/dist/prompts/explore-dex-pools.js +132 -0
- package/dist/prompts/explore-dex-pools.js.map +1 -0
- package/dist/prompts/index.d.ts +3 -2
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +6 -3
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/pools.js.map +1 -1
- package/dist/tools/dex/fetchAllDexPools.d.ts +9 -0
- package/dist/tools/dex/fetchAllDexPools.d.ts.map +1 -0
- package/dist/tools/dex/fetchAllDexPools.js +45 -0
- package/dist/tools/dex/fetchAllDexPools.js.map +1 -0
- package/dist/tools/dex/fetchDexPools.d.ts +9 -0
- package/dist/tools/dex/fetchDexPools.d.ts.map +1 -0
- package/dist/tools/dex/fetchDexPools.js +58 -0
- package/dist/tools/dex/fetchDexPools.js.map +1 -0
- package/dist/tools/dex/index.d.ts +4 -3
- package/dist/tools/dex/index.d.ts.map +1 -1
- package/dist/tools/dex/index.js +9 -4
- package/dist/tools/dex/index.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +3 -3
- package/docs/AI-AGENT-PATTERNS.md +555 -0
- package/docs/CONSTRAINTS-REFERENCE.md +454 -0
- package/docs/PROMPT-TOOL-MAPPING.md +352 -0
- package/docs/examples/default-values-pattern.md +240 -0
- package/docs/examples/tool-factory-pattern.md +217 -0
- package/jest.config.js +94 -0
- package/package.json +3 -3
- package/src/__tests__/integration/fetchTokenDetails.integration.test.ts +258 -0
- package/src/__tests__/integration/poolTools.integration.test.ts +185 -0
- package/src/__tests__/server.test.ts +255 -0
- package/src/constants/mcpToolNames.ts +183 -0
- package/src/index.ts +19 -0
- package/src/prompts/__tests__/promptStructure.test.ts +187 -0
- package/src/prompts/__tests__/registry.test.ts +349 -0
- package/src/prompts/analysis.ts +380 -0
- package/src/prompts/balances.ts +182 -0
- package/src/prompts/create-token.ts +123 -0
- package/src/prompts/creation-utils.ts +103 -0
- package/src/prompts/dex-trading.ts +86 -0
- package/src/prompts/discover-tokens.ts +86 -0
- package/src/prompts/explore-dex-pools.ts +138 -0
- package/src/prompts/index.ts +178 -0
- package/src/prompts/liquidity-positions.ts +237 -0
- package/src/prompts/pools.ts +496 -0
- package/src/prompts/portfolio.ts +208 -0
- package/src/prompts/social.ts +94 -0
- package/src/prompts/trading-calculations.ts +414 -0
- package/src/prompts/trading.ts +160 -0
- package/src/prompts/transfers.ts +97 -0
- package/src/prompts/utility-tools.ts +266 -0
- package/src/prompts/utility.ts +77 -0
- package/src/prompts/utils/handlerHelpers.ts +55 -0
- package/src/prompts/utils/textTemplates.ts +73 -0
- package/src/prompts/utils/workflowTemplates.ts +511 -0
- package/src/schemas/common-schemas.ts +393 -0
- package/src/scripts/test-all-prompts.ts +184 -0
- package/src/server.ts +367 -0
- package/src/tools/__tests__/dex-tools.test.ts +562 -0
- package/src/tools/__tests__/liquidity-positions.test.ts +673 -0
- package/src/tools/balance/index.ts +174 -0
- package/src/tools/creation/index.ts +182 -0
- package/src/tools/dex/fetchAllDexPools.ts +45 -0
- package/src/tools/dex/fetchDexPools.ts +58 -0
- package/src/tools/dex/index.ts +231 -0
- package/src/tools/dex/liquidity-positions.ts +547 -0
- package/src/tools/index.ts +94 -0
- package/src/tools/pools/fetchAllPools.ts +47 -0
- package/src/tools/pools/fetchAllPriceHistory.ts +119 -0
- package/src/tools/pools/fetchPoolDetails.ts +27 -0
- package/src/tools/pools/fetchPoolDetailsForCalculation.ts +22 -0
- package/src/tools/pools/fetchPools.ts +47 -0
- package/src/tools/pools/fetchPriceHistory.ts +124 -0
- package/src/tools/pools/fetchTokenDetails.ts +77 -0
- package/src/tools/pools/index.ts +284 -0
- package/src/tools/social/index.ts +64 -0
- package/src/tools/trading/index.ts +605 -0
- package/src/tools/transfers/index.ts +75 -0
- package/src/tools/utils/clearCache.ts +36 -0
- package/src/tools/utils/createWallet.ts +19 -0
- package/src/tools/utils/explainSdkUsage.ts +1446 -0
- package/src/tools/utils/getAddress.ts +12 -0
- package/src/tools/utils/getCacheInfo.ts +14 -0
- package/src/tools/utils/getConfig.ts +21 -0
- package/src/tools/utils/getEnvironment.ts +17 -0
- package/src/tools/utils/getEthereumAddress.ts +12 -0
- package/src/tools/utils/getUrlByTokenName.ts +12 -0
- package/src/tools/utils/getVersion.ts +25 -0
- package/src/tools/utils/getWallet.ts +25 -0
- package/src/tools/utils/hasWallet.ts +15 -0
- package/src/tools/utils/index.ts +37 -0
- package/src/tools/utils/isTokenGraduated.ts +16 -0
- package/src/tools/utils/setWallet.ts +41 -0
- package/src/tools/utils/switchEnvironment.ts +28 -0
- package/src/types/mcp.ts +72 -0
- package/src/utils/__tests__/validation.test.ts +147 -0
- package/src/utils/constraints.ts +155 -0
- package/src/utils/default-values.ts +208 -0
- package/src/utils/error-handler.ts +69 -0
- package/src/utils/error-templates.ts +273 -0
- package/src/utils/response-formatter.ts +51 -0
- package/src/utils/tool-factory.ts +303 -0
- package/src/utils/tool-registry.ts +296 -0
- package/src/utils/validation.ts +429 -0
- package/tests/wallet-management-integration.test.ts +284 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Utilities for MCP Prompts
|
|
3
|
+
*
|
|
4
|
+
* Input validation functions for slash command arguments.
|
|
5
|
+
* Prevents malformed inputs and provides clear error messages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validation error class for better error handling
|
|
10
|
+
*/
|
|
11
|
+
export class ValidationError extends Error {
|
|
12
|
+
constructor(
|
|
13
|
+
public field: string,
|
|
14
|
+
message: string
|
|
15
|
+
) {
|
|
16
|
+
super(`Validation error for '${field}': ${message}`);
|
|
17
|
+
this.name = 'ValidationError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate token name format
|
|
23
|
+
*
|
|
24
|
+
* Token names must be 2-20 characters, alphanumeric with hyphens and underscores.
|
|
25
|
+
*
|
|
26
|
+
* @param name - Token name to validate
|
|
27
|
+
* @param fieldName - Field name for error messages (default: 'tokenName')
|
|
28
|
+
* @throws {ValidationError} If token name is invalid
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* validateTokenName('anime'); // ✅ Valid
|
|
33
|
+
* validateTokenName('test-123'); // ✅ Valid
|
|
34
|
+
* validateTokenName('my_token'); // ✅ Valid
|
|
35
|
+
* validateTokenName('a'); // ❌ Throws: too short
|
|
36
|
+
* validateTokenName('token@#$'); // ❌ Throws: invalid characters
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function validateTokenName(name: string, fieldName = 'tokenName'): void {
|
|
40
|
+
if (!name || typeof name !== 'string') {
|
|
41
|
+
throw new ValidationError(fieldName, 'Token name is required');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (name.length < 2 || name.length > 20) {
|
|
45
|
+
throw new ValidationError(
|
|
46
|
+
fieldName,
|
|
47
|
+
`Token name must be 2-20 characters (got ${name.length})`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const tokenNameRegex = /^[a-zA-Z0-9-_]+$/;
|
|
52
|
+
if (!tokenNameRegex.test(name)) {
|
|
53
|
+
throw new ValidationError(
|
|
54
|
+
fieldName,
|
|
55
|
+
'Token name can only contain letters, numbers, hyphens, and underscores'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validate numeric amount
|
|
62
|
+
*
|
|
63
|
+
* Amounts must be positive numbers (integers or decimals).
|
|
64
|
+
*
|
|
65
|
+
* @param amount - Amount to validate (string or number)
|
|
66
|
+
* @param fieldName - Field name for error messages
|
|
67
|
+
* @throws {ValidationError} If amount is invalid
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* validateNumericAmount('100', 'galaAmount'); // ✅ Valid
|
|
72
|
+
* validateNumericAmount('99.5', 'tokenAmount'); // ✅ Valid
|
|
73
|
+
* validateNumericAmount('-5', 'amount'); // ❌ Throws: negative
|
|
74
|
+
* validateNumericAmount('abc', 'amount'); // ❌ Throws: not a number
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function validateNumericAmount(
|
|
78
|
+
amount: string | number,
|
|
79
|
+
fieldName: string
|
|
80
|
+
): void {
|
|
81
|
+
if (amount === null || amount === undefined || amount === '') {
|
|
82
|
+
throw new ValidationError(fieldName, 'Amount is required');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount;
|
|
86
|
+
|
|
87
|
+
if (isNaN(numAmount)) {
|
|
88
|
+
throw new ValidationError(fieldName, `Amount must be a valid number (got: ${amount})`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (numAmount <= 0) {
|
|
92
|
+
throw new ValidationError(
|
|
93
|
+
fieldName,
|
|
94
|
+
`Amount must be positive (got: ${numAmount})`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!isFinite(numAmount)) {
|
|
99
|
+
throw new ValidationError(fieldName, 'Amount must be a finite number');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate slippage tolerance percentage
|
|
105
|
+
*
|
|
106
|
+
* Slippage must be between 0.01% and 100%.
|
|
107
|
+
*
|
|
108
|
+
* @param slippage - Slippage percentage to validate
|
|
109
|
+
* @param fieldName - Field name for error messages (default: 'slippage')
|
|
110
|
+
* @throws {ValidationError} If slippage is invalid
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* validateSlippage('1'); // ✅ Valid (1%)
|
|
115
|
+
* validateSlippage('0.5'); // ✅ Valid (0.5%)
|
|
116
|
+
* validateSlippage('50'); // ✅ Valid (50%)
|
|
117
|
+
* validateSlippage('0'); // ❌ Throws: too low
|
|
118
|
+
* validateSlippage('150'); // ❌ Throws: too high
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export function validateSlippage(
|
|
122
|
+
slippage: string | number,
|
|
123
|
+
fieldName = 'slippage'
|
|
124
|
+
): void {
|
|
125
|
+
if (slippage === null || slippage === undefined || slippage === '') {
|
|
126
|
+
throw new ValidationError(fieldName, 'Slippage is required');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const numSlippage = typeof slippage === 'string' ? parseFloat(slippage) : slippage;
|
|
130
|
+
|
|
131
|
+
if (isNaN(numSlippage)) {
|
|
132
|
+
throw new ValidationError(
|
|
133
|
+
fieldName,
|
|
134
|
+
`Slippage must be a valid number (got: ${slippage})`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (numSlippage < 0.01 || numSlippage > 100) {
|
|
139
|
+
throw new ValidationError(
|
|
140
|
+
fieldName,
|
|
141
|
+
`Slippage must be between 0.01% and 100% (got: ${numSlippage}%)`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Validate wallet address format
|
|
148
|
+
*
|
|
149
|
+
* Supports both GalaChain format (eth|0x...) and standard Ethereum format (0x...).
|
|
150
|
+
*
|
|
151
|
+
* @param address - Wallet address to validate
|
|
152
|
+
* @param fieldName - Field name for error messages (default: 'address')
|
|
153
|
+
* @throws {ValidationError} If address is invalid
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* validateAddress('eth|0x1234567890abcdef1234567890abcdef12345678'); // ✅ Valid
|
|
158
|
+
* validateAddress('0x1234567890abcdef1234567890abcdef12345678'); // ✅ Valid
|
|
159
|
+
* validateAddress('invalid'); // ❌ Throws: invalid format
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export function validateAddress(address: string, fieldName = 'address'): void {
|
|
163
|
+
if (!address || typeof address !== 'string') {
|
|
164
|
+
throw new ValidationError(fieldName, 'Address is required');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// GalaChain format: eth|0x{40 hex chars}
|
|
168
|
+
const galachainRegex = /^eth\|0x[a-fA-F0-9]{40}$/;
|
|
169
|
+
// Standard Ethereum format: 0x{40 hex chars}
|
|
170
|
+
const ethereumRegex = /^0x[a-fA-F0-9]{40}$/;
|
|
171
|
+
|
|
172
|
+
if (!galachainRegex.test(address) && !ethereumRegex.test(address)) {
|
|
173
|
+
throw new ValidationError(
|
|
174
|
+
fieldName,
|
|
175
|
+
'Address must be in GalaChain format (eth|0x...) or Ethereum format (0x...)'
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Validate token symbol format
|
|
182
|
+
*
|
|
183
|
+
* Symbols must be 1-8 uppercase letters.
|
|
184
|
+
*
|
|
185
|
+
* @param symbol - Token symbol to validate
|
|
186
|
+
* @param fieldName - Field name for error messages (default: 'symbol')
|
|
187
|
+
* @throws {ValidationError} If symbol is invalid
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* validateTokenSymbol('GALA'); // ✅ Valid
|
|
192
|
+
* validateTokenSymbol('TEST'); // ✅ Valid
|
|
193
|
+
* validateTokenSymbol('test'); // ❌ Throws: must be uppercase
|
|
194
|
+
* validateTokenSymbol('TOOLONG123'); // ❌ Throws: too long
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
export function validateTokenSymbol(symbol: string, fieldName = 'symbol'): void {
|
|
198
|
+
if (!symbol || typeof symbol !== 'string') {
|
|
199
|
+
throw new ValidationError(fieldName, 'Symbol is required');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (symbol.length < 1 || symbol.length > 8) {
|
|
203
|
+
throw new ValidationError(
|
|
204
|
+
fieldName,
|
|
205
|
+
`Symbol must be 1-8 characters (got ${symbol.length})`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const symbolRegex = /^[A-Z]+$/;
|
|
210
|
+
if (!symbolRegex.test(symbol)) {
|
|
211
|
+
throw new ValidationError(
|
|
212
|
+
fieldName,
|
|
213
|
+
'Symbol must be uppercase letters only (A-Z)'
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Validate pagination limit
|
|
220
|
+
*
|
|
221
|
+
* Limits must be positive integers within reasonable range (1-100).
|
|
222
|
+
*
|
|
223
|
+
* @param limit - Pagination limit to validate
|
|
224
|
+
* @param max - Maximum allowed limit (default: 100)
|
|
225
|
+
* @param fieldName - Field name for error messages (default: 'limit')
|
|
226
|
+
* @throws {ValidationError} If limit is invalid
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```typescript
|
|
230
|
+
* validatePaginationLimit('20'); // ✅ Valid
|
|
231
|
+
* validatePaginationLimit('1'); // ✅ Valid
|
|
232
|
+
* validatePaginationLimit('0'); // ❌ Throws: too low
|
|
233
|
+
* validatePaginationLimit('150'); // ❌ Throws: too high
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
export function validatePaginationLimit(
|
|
237
|
+
limit: string | number,
|
|
238
|
+
max = 100,
|
|
239
|
+
fieldName = 'limit'
|
|
240
|
+
): void {
|
|
241
|
+
if (limit === null || limit === undefined || limit === '') {
|
|
242
|
+
throw new ValidationError(fieldName, 'Limit is required');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const numLimit = typeof limit === 'string' ? parseInt(limit, 10) : limit;
|
|
246
|
+
|
|
247
|
+
if (isNaN(numLimit) || !Number.isInteger(numLimit)) {
|
|
248
|
+
throw new ValidationError(
|
|
249
|
+
fieldName,
|
|
250
|
+
`Limit must be a valid integer (got: ${limit})`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (numLimit < 1 || numLimit > max) {
|
|
255
|
+
throw new ValidationError(
|
|
256
|
+
fieldName,
|
|
257
|
+
`Limit must be between 1 and ${max} (got: ${numLimit})`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Validate comma-separated token list
|
|
264
|
+
*
|
|
265
|
+
* For batch operations that accept multiple token names.
|
|
266
|
+
*
|
|
267
|
+
* @param tokens - Comma-separated token names
|
|
268
|
+
* @param fieldName - Field name for error messages (default: 'tokens')
|
|
269
|
+
* @throws {ValidationError} If token list is invalid
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```typescript
|
|
273
|
+
* validateTokenList('anime,test,dragon'); // ✅ Valid
|
|
274
|
+
* validateTokenList('token1'); // ✅ Valid (single token)
|
|
275
|
+
* validateTokenList(''); // ❌ Throws: empty list
|
|
276
|
+
* validateTokenList('token1,invalid@#$'); // ❌ Throws: invalid token name
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export function validateTokenList(tokens: string, fieldName = 'tokens'): string[] {
|
|
280
|
+
if (!tokens || typeof tokens !== 'string') {
|
|
281
|
+
throw new ValidationError(fieldName, 'Token list is required');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const tokenArray = tokens.split(',').map((t) => t.trim());
|
|
285
|
+
|
|
286
|
+
if (tokenArray.length === 0 || tokenArray.some((t) => !t)) {
|
|
287
|
+
throw new ValidationError(fieldName, 'Token list cannot be empty');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Validate each token name
|
|
291
|
+
tokenArray.forEach((token, index) => {
|
|
292
|
+
try {
|
|
293
|
+
validateTokenName(token, `${fieldName}[${index}]`);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
if (error instanceof ValidationError) {
|
|
296
|
+
throw new ValidationError(
|
|
297
|
+
fieldName,
|
|
298
|
+
`Invalid token at position ${index + 1}: ${token}`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return tokenArray;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Validate URL format
|
|
310
|
+
*
|
|
311
|
+
* URLs must be well-formed HTTP/HTTPS URLs.
|
|
312
|
+
*
|
|
313
|
+
* @param url - URL to validate
|
|
314
|
+
* @param fieldName - Field name for error messages (default: 'url')
|
|
315
|
+
* @throws {ValidationError} If URL is invalid
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* validateUrl('https://example.com'); // ✅ Valid
|
|
320
|
+
* validateUrl('https://twitter.com/user'); // ✅ Valid
|
|
321
|
+
* validateUrl('http://localhost:3000'); // ✅ Valid
|
|
322
|
+
* validateUrl('not-a-url'); // ❌ Throws: invalid format
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
export function validateUrl(url: string, fieldName = 'url'): void {
|
|
326
|
+
if (!url || typeof url !== 'string') {
|
|
327
|
+
throw new ValidationError(fieldName, 'URL is required');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const urlObj = new URL(url);
|
|
332
|
+
if (!['http:', 'https:'].includes(urlObj.protocol)) {
|
|
333
|
+
throw new ValidationError(fieldName, 'URL must use HTTP or HTTPS protocol');
|
|
334
|
+
}
|
|
335
|
+
} catch (error) {
|
|
336
|
+
if (error instanceof ValidationError) {
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
throw new ValidationError(fieldName, `Invalid URL format: ${url}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Safe validation wrapper that doesn't throw
|
|
345
|
+
*
|
|
346
|
+
* Useful for non-critical validation where you want to continue with a warning.
|
|
347
|
+
*
|
|
348
|
+
* @param validationFn - Validation function to execute
|
|
349
|
+
* @returns Object with success flag and optional error
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* const result = safeValidate(() => validateTokenName('anime'));
|
|
354
|
+
* if (!result.success) {
|
|
355
|
+
* console.warn('Validation warning:', result.error);
|
|
356
|
+
* }
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
export function safeValidate(
|
|
360
|
+
validationFn: () => void
|
|
361
|
+
): { success: boolean; error?: string } {
|
|
362
|
+
try {
|
|
363
|
+
validationFn();
|
|
364
|
+
return { success: true };
|
|
365
|
+
} catch (error) {
|
|
366
|
+
if (error instanceof ValidationError) {
|
|
367
|
+
return { success: false, error: error.message };
|
|
368
|
+
}
|
|
369
|
+
return { success: false, error: String(error) };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Validate optional field if present
|
|
375
|
+
*
|
|
376
|
+
* Convenience wrapper for validating optional fields. Only runs validation
|
|
377
|
+
* if the value is defined and not null.
|
|
378
|
+
*
|
|
379
|
+
* @param value - Value to validate (can be undefined)
|
|
380
|
+
* @param validator - Validation function to run if value is present
|
|
381
|
+
* @throws {ValidationError} If validation fails
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```typescript
|
|
385
|
+
* validateOptional(args.slippage, (val) => validateSlippage(val));
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
export function validateOptional<T>(
|
|
389
|
+
value: T | undefined | null,
|
|
390
|
+
validator: (val: T) => void
|
|
391
|
+
): void {
|
|
392
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
393
|
+
validator(value as T);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Validate optional slippage field
|
|
399
|
+
*
|
|
400
|
+
* @param slippage - Slippage value to validate (optional)
|
|
401
|
+
* @throws {ValidationError} If slippage is invalid
|
|
402
|
+
*/
|
|
403
|
+
export function validateOptionalSlippage(slippage?: string | number): void {
|
|
404
|
+
validateOptional(slippage, (val) => validateSlippage(val));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Validate optional pagination limit field
|
|
409
|
+
*
|
|
410
|
+
* @param limit - Limit value to validate (optional)
|
|
411
|
+
* @param max - Maximum allowed limit (default: 100)
|
|
412
|
+
* @throws {ValidationError} If limit is invalid
|
|
413
|
+
*/
|
|
414
|
+
export function validateOptionalLimit(
|
|
415
|
+
limit?: string | number,
|
|
416
|
+
max: number = 100
|
|
417
|
+
): void {
|
|
418
|
+
validateOptional(limit, (val) => validatePaginationLimit(val, max));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Validate optional address field
|
|
423
|
+
*
|
|
424
|
+
* @param address - Address value to validate (optional)
|
|
425
|
+
* @throws {ValidationError} If address is invalid
|
|
426
|
+
*/
|
|
427
|
+
export function validateOptionalAddress(address?: string): void {
|
|
428
|
+
validateOptional(address, (val) => validateAddress(val));
|
|
429
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Tests for MCP Wallet Management Tools
|
|
3
|
+
*
|
|
4
|
+
* Tests the three new wallet management tools:
|
|
5
|
+
* - gala_launchpad_has_wallet
|
|
6
|
+
* - gala_launchpad_get_wallet
|
|
7
|
+
* - gala_launchpad_set_wallet
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { LaunchpadMCPServer } from '../src/server';
|
|
11
|
+
import { WalletUtils } from '@gala-chain/launchpad-sdk';
|
|
12
|
+
|
|
13
|
+
describe('MCP Wallet Management Integration Tests', () => {
|
|
14
|
+
let server: LaunchpadMCPServer;
|
|
15
|
+
let testPrivateKey: string;
|
|
16
|
+
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
server = new LaunchpadMCPServer();
|
|
19
|
+
// Generate a test private key
|
|
20
|
+
const wallet = WalletUtils.generateWallet();
|
|
21
|
+
testPrivateKey = wallet.privateKey;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('Server Initialization Modes', () => {
|
|
25
|
+
it('should initialize in read-only mode when PRIVATE_KEY is not set', async () => {
|
|
26
|
+
const readOnlyServer = new LaunchpadMCPServer();
|
|
27
|
+
// Clear PRIVATE_KEY from environment
|
|
28
|
+
const originalKey = process.env.PRIVATE_KEY;
|
|
29
|
+
delete process.env.PRIVATE_KEY;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await readOnlyServer.initialize();
|
|
33
|
+
// SDK should be initialized
|
|
34
|
+
expect(readOnlyServer['sdk']).toBeDefined();
|
|
35
|
+
} finally {
|
|
36
|
+
// Restore original key
|
|
37
|
+
if (originalKey) {
|
|
38
|
+
process.env.PRIVATE_KEY = originalKey;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should initialize in full-access mode when PRIVATE_KEY is set', async () => {
|
|
44
|
+
process.env.PRIVATE_KEY = testPrivateKey;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await server.initialize();
|
|
48
|
+
expect(server['sdk']).toBeDefined();
|
|
49
|
+
} finally {
|
|
50
|
+
delete process.env.PRIVATE_KEY;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle missing PRIVATE_KEY gracefully', async () => {
|
|
55
|
+
const originalKey = process.env.PRIVATE_KEY;
|
|
56
|
+
delete process.env.PRIVATE_KEY;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Should not throw during initialization
|
|
60
|
+
const readOnlyServer = new LaunchpadMCPServer();
|
|
61
|
+
await readOnlyServer.initialize();
|
|
62
|
+
expect(readOnlyServer['sdk']).toBeDefined();
|
|
63
|
+
} finally {
|
|
64
|
+
if (originalKey) {
|
|
65
|
+
process.env.PRIVATE_KEY = originalKey;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('Wallet Tool Availability', () => {
|
|
72
|
+
it('should list all wallet management tools', async () => {
|
|
73
|
+
await server.initialize();
|
|
74
|
+
// Get the server instance
|
|
75
|
+
const serverInstance = server as any;
|
|
76
|
+
// The tools should be registered in the MCP server
|
|
77
|
+
expect(serverInstance.server).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should have gala_launchpad_has_wallet tool registered', () => {
|
|
81
|
+
// Tools are registered in the tools/index.ts
|
|
82
|
+
// This is a static verification test
|
|
83
|
+
const fs = require('fs');
|
|
84
|
+
const toolsIndexPath = require.resolve('../src/tools/utils/index.ts');
|
|
85
|
+
// This is a static check - the tool should be imported
|
|
86
|
+
expect(toolsIndexPath).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should have gala_launchpad_get_wallet tool registered', () => {
|
|
90
|
+
// This is a static verification test
|
|
91
|
+
const fs = require('fs');
|
|
92
|
+
const toolsIndexPath = require.resolve('../src/tools/utils/index.ts');
|
|
93
|
+
expect(toolsIndexPath).toBeDefined();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should have gala_launchpad_set_wallet tool registered', () => {
|
|
97
|
+
// This is a static verification test
|
|
98
|
+
const fs = require('fs');
|
|
99
|
+
const toolsIndexPath = require.resolve('../src/tools/utils/index.ts');
|
|
100
|
+
expect(toolsIndexPath).toBeDefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('Wallet Tool Functionality', () => {
|
|
105
|
+
beforeEach(async () => {
|
|
106
|
+
// Initialize server without wallet (read-only mode)
|
|
107
|
+
delete process.env.PRIVATE_KEY;
|
|
108
|
+
await server.initialize();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should handle wallet tool execution in read-only mode', async () => {
|
|
112
|
+
// In read-only mode, hasWallet should return false
|
|
113
|
+
const sdk = (server as any).sdk;
|
|
114
|
+
expect(sdk.hasWallet()).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle wallet tool execution in full-access mode', async () => {
|
|
118
|
+
// Create new server with wallet
|
|
119
|
+
const fullAccessServer = new LaunchpadMCPServer();
|
|
120
|
+
process.env.PRIVATE_KEY = testPrivateKey;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
await fullAccessServer.initialize();
|
|
124
|
+
const sdk = (fullAccessServer as any).sdk;
|
|
125
|
+
expect(sdk.hasWallet()).toBe(true);
|
|
126
|
+
} finally {
|
|
127
|
+
delete process.env.PRIVATE_KEY;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should support dynamic wallet configuration via setWallet tool', async () => {
|
|
132
|
+
const sdk = (server as any).sdk;
|
|
133
|
+
|
|
134
|
+
// Initially in read-only mode
|
|
135
|
+
expect(sdk.hasWallet()).toBe(false);
|
|
136
|
+
|
|
137
|
+
// Upgrade to full-access by setting wallet
|
|
138
|
+
const wallet = WalletUtils.generateWallet();
|
|
139
|
+
sdk.setWallet(wallet.wallet);
|
|
140
|
+
|
|
141
|
+
// Now should have wallet
|
|
142
|
+
expect(sdk.hasWallet()).toBe(true);
|
|
143
|
+
expect(sdk.getWallet()).toBeDefined();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('Tool Registration Count', () => {
|
|
148
|
+
it('should have exactly 57 tools registered (54 original + 3 new wallet tools)', async () => {
|
|
149
|
+
// This verifies the tool count was updated correctly
|
|
150
|
+
// The count is defined in src/tools/index.ts
|
|
151
|
+
const registryModule = require('../dist/tools/index.js');
|
|
152
|
+
expect(registryModule.tools.length).toBeGreaterThanOrEqual(57);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('Error Handling', () => {
|
|
157
|
+
beforeEach(async () => {
|
|
158
|
+
// Initialize server in read-only mode
|
|
159
|
+
delete process.env.PRIVATE_KEY;
|
|
160
|
+
await server.initialize();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should throw error when trying to perform signing operation without wallet', async () => {
|
|
164
|
+
const sdk = (server as any).sdk;
|
|
165
|
+
|
|
166
|
+
// Attempting to buy tokens should fail due to missing wallet
|
|
167
|
+
try {
|
|
168
|
+
await sdk.buyTokens('test', '10', 'native', 0.01);
|
|
169
|
+
fail('Should have thrown an error');
|
|
170
|
+
} catch (error) {
|
|
171
|
+
expect(error).toBeDefined();
|
|
172
|
+
expect(String(error)).toContain('Wallet');
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should allow read-only operations in read-only mode', async () => {
|
|
177
|
+
const sdk = (server as any).sdk;
|
|
178
|
+
|
|
179
|
+
// Fetching pools should work without wallet (may fail due to network, but not wallet)
|
|
180
|
+
try {
|
|
181
|
+
await sdk.fetchPools({ type: 'recent', page: 1, limit: 1 });
|
|
182
|
+
} catch (error: any) {
|
|
183
|
+
// Should not be a wallet validation error
|
|
184
|
+
if (error.message) {
|
|
185
|
+
expect(error.message).not.toContain('Wallet is required');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should accept valid wallet in setWallet call', async () => {
|
|
191
|
+
const sdk = (server as any).sdk;
|
|
192
|
+
const wallet = WalletUtils.generateWallet();
|
|
193
|
+
|
|
194
|
+
// Should not throw
|
|
195
|
+
expect(() => {
|
|
196
|
+
sdk.setWallet(wallet.wallet);
|
|
197
|
+
}).not.toThrow();
|
|
198
|
+
|
|
199
|
+
// Wallet should be set
|
|
200
|
+
expect(sdk.hasWallet()).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should reject invalid wallet in setWallet call', async () => {
|
|
204
|
+
const sdk = (server as any).sdk;
|
|
205
|
+
|
|
206
|
+
// Should throw on invalid wallet
|
|
207
|
+
expect(() => {
|
|
208
|
+
sdk.setWallet({} as any);
|
|
209
|
+
}).toThrow();
|
|
210
|
+
|
|
211
|
+
expect(() => {
|
|
212
|
+
sdk.setWallet(null as any);
|
|
213
|
+
}).toThrow();
|
|
214
|
+
|
|
215
|
+
expect(() => {
|
|
216
|
+
sdk.setWallet('not-a-wallet' as any);
|
|
217
|
+
}).toThrow();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('Operational Modes', () => {
|
|
222
|
+
it('should support upgrade from read-only to full-access mode', async () => {
|
|
223
|
+
// Start in read-only mode
|
|
224
|
+
delete process.env.PRIVATE_KEY;
|
|
225
|
+
const readOnlyServer = new LaunchpadMCPServer();
|
|
226
|
+
await readOnlyServer.initialize();
|
|
227
|
+
|
|
228
|
+
const sdk = (readOnlyServer as any).sdk;
|
|
229
|
+
expect(sdk.hasWallet()).toBe(false);
|
|
230
|
+
|
|
231
|
+
// Upgrade to full-access
|
|
232
|
+
const wallet = WalletUtils.generateWallet();
|
|
233
|
+
sdk.setWallet(wallet.wallet);
|
|
234
|
+
|
|
235
|
+
expect(sdk.hasWallet()).toBe(true);
|
|
236
|
+
expect(sdk.getAddress()).toBeDefined();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should support switching wallets in full-access mode', async () => {
|
|
240
|
+
process.env.PRIVATE_KEY = testPrivateKey;
|
|
241
|
+
const fullAccessServer = new LaunchpadMCPServer();
|
|
242
|
+
await fullAccessServer.initialize();
|
|
243
|
+
|
|
244
|
+
const sdk = (fullAccessServer as any).sdk;
|
|
245
|
+
const address1 = sdk.getAddress();
|
|
246
|
+
|
|
247
|
+
// Switch to different wallet
|
|
248
|
+
const newWallet = WalletUtils.generateWallet();
|
|
249
|
+
sdk.setWallet(newWallet.wallet);
|
|
250
|
+
const address2 = sdk.getAddress();
|
|
251
|
+
|
|
252
|
+
expect(address1).not.toBe(address2);
|
|
253
|
+
expect(sdk.hasWallet()).toBe(true);
|
|
254
|
+
|
|
255
|
+
delete process.env.PRIVATE_KEY;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should maintain SDK state across operational mode transitions', async () => {
|
|
259
|
+
// Start in read-only
|
|
260
|
+
delete process.env.PRIVATE_KEY;
|
|
261
|
+
const server = new LaunchpadMCPServer();
|
|
262
|
+
await server.initialize();
|
|
263
|
+
|
|
264
|
+
const sdk = (server as any).sdk;
|
|
265
|
+
|
|
266
|
+
// Upgrade to full-access
|
|
267
|
+
const wallet = WalletUtils.generateWallet();
|
|
268
|
+
sdk.setWallet(wallet.wallet);
|
|
269
|
+
|
|
270
|
+
// SDK should still be operational
|
|
271
|
+
expect(sdk.hasWallet()).toBe(true);
|
|
272
|
+
|
|
273
|
+
// Should be able to fetch pools
|
|
274
|
+
try {
|
|
275
|
+
await sdk.fetchPools({ type: 'recent', page: 1, limit: 1 });
|
|
276
|
+
} catch (error: any) {
|
|
277
|
+
// Network error is ok, wallet error is not
|
|
278
|
+
if (error.message) {
|
|
279
|
+
expect(error.message).not.toContain('Wallet is required');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
});
|