@gala-chain/launchpad-mcp-server 1.22.3 → 1.23.0

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 (153) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/README.md +83 -8
  3. package/dist/constants/mcpToolNames.d.ts +69 -11
  4. package/dist/constants/mcpToolNames.d.ts.map +1 -1
  5. package/dist/constants/mcpToolNames.js +47 -9
  6. package/dist/constants/mcpToolNames.js.map +1 -1
  7. package/dist/generated/version.d.ts +1 -1
  8. package/dist/generated/version.js +1 -1
  9. package/dist/prompts/balances.d.ts +24 -0
  10. package/dist/prompts/balances.d.ts.map +1 -0
  11. package/dist/prompts/balances.js +191 -0
  12. package/dist/prompts/balances.js.map +1 -0
  13. package/dist/prompts/creation-utils.d.ts +20 -0
  14. package/dist/prompts/creation-utils.d.ts.map +1 -0
  15. package/dist/prompts/creation-utils.js +115 -0
  16. package/dist/prompts/creation-utils.js.map +1 -0
  17. package/dist/prompts/index.d.ts +9 -2
  18. package/dist/prompts/index.d.ts.map +1 -1
  19. package/dist/prompts/index.js +23 -2
  20. package/dist/prompts/index.js.map +1 -1
  21. package/dist/prompts/pools.d.ts +64 -0
  22. package/dist/prompts/pools.d.ts.map +1 -0
  23. package/dist/prompts/pools.js +548 -0
  24. package/dist/prompts/pools.js.map +1 -0
  25. package/dist/prompts/social.d.ts +16 -0
  26. package/dist/prompts/social.d.ts.map +1 -0
  27. package/dist/prompts/social.js +97 -0
  28. package/dist/prompts/social.js.map +1 -0
  29. package/dist/prompts/trading-calculations.d.ts +52 -0
  30. package/dist/prompts/trading-calculations.d.ts.map +1 -0
  31. package/dist/prompts/trading-calculations.js +479 -0
  32. package/dist/prompts/trading-calculations.js.map +1 -0
  33. package/dist/prompts/transfers.d.ts +16 -0
  34. package/dist/prompts/transfers.d.ts.map +1 -0
  35. package/dist/prompts/transfers.js +100 -0
  36. package/dist/prompts/transfers.js.map +1 -0
  37. package/dist/prompts/utility-tools.d.ts +56 -0
  38. package/dist/prompts/utility-tools.d.ts.map +1 -0
  39. package/dist/prompts/utility-tools.js +338 -0
  40. package/dist/prompts/utility-tools.js.map +1 -0
  41. package/dist/prompts/utility.d.ts +8 -0
  42. package/dist/prompts/utility.d.ts.map +1 -1
  43. package/dist/prompts/utility.js +49 -2
  44. package/dist/prompts/utility.js.map +1 -1
  45. package/dist/server.d.ts +17 -0
  46. package/dist/server.d.ts.map +1 -1
  47. package/dist/server.js +81 -3
  48. package/dist/server.js.map +1 -1
  49. package/dist/tools/index.d.ts +9 -1
  50. package/dist/tools/index.d.ts.map +1 -1
  51. package/dist/tools/index.js +10 -2
  52. package/dist/tools/index.js.map +1 -1
  53. package/dist/tools/utils/getConfig.d.ts +2 -0
  54. package/dist/tools/utils/getConfig.d.ts.map +1 -1
  55. package/dist/tools/utils/getConfig.js +12 -3
  56. package/dist/tools/utils/getConfig.js.map +1 -1
  57. package/dist/tools/utils/getEnvironment.d.ts +8 -0
  58. package/dist/tools/utils/getEnvironment.d.ts.map +1 -0
  59. package/dist/tools/utils/getEnvironment.js +19 -0
  60. package/dist/tools/utils/getEnvironment.js.map +1 -0
  61. package/dist/tools/utils/index.d.ts.map +1 -1
  62. package/dist/tools/utils/index.js +4 -0
  63. package/dist/tools/utils/index.js.map +1 -1
  64. package/dist/tools/utils/switchEnvironment.d.ts +8 -0
  65. package/dist/tools/utils/switchEnvironment.d.ts.map +1 -0
  66. package/dist/tools/utils/switchEnvironment.js +30 -0
  67. package/dist/tools/utils/switchEnvironment.js.map +1 -0
  68. package/dist/types/mcp.d.ts +1 -1
  69. package/dist/types/mcp.d.ts.map +1 -1
  70. package/dist/utils/tool-factory.d.ts +30 -0
  71. package/dist/utils/tool-factory.d.ts.map +1 -1
  72. package/dist/utils/tool-factory.js +31 -0
  73. package/dist/utils/tool-factory.js.map +1 -1
  74. package/docs/AI-AGENT-PATTERNS.md +555 -0
  75. package/docs/CONSTRAINTS-REFERENCE.md +454 -0
  76. package/docs/PROMPT-TOOL-MAPPING.md +352 -0
  77. package/docs/examples/default-values-pattern.md +240 -0
  78. package/docs/examples/tool-factory-pattern.md +217 -0
  79. package/jest.config.js +94 -0
  80. package/package.json +1 -1
  81. package/src/__tests__/integration/fetchTokenDetails.integration.test.ts +258 -0
  82. package/src/__tests__/integration/poolTools.integration.test.ts +185 -0
  83. package/src/__tests__/server.test.ts +256 -0
  84. package/src/constants/mcpToolNames.ts +181 -0
  85. package/src/index.ts +19 -0
  86. package/src/prompts/__tests__/promptStructure.test.ts +137 -0
  87. package/src/prompts/__tests__/registry.test.ts +359 -0
  88. package/src/prompts/analysis.ts +429 -0
  89. package/src/prompts/balances.ts +198 -0
  90. package/src/prompts/create-token.ts +123 -0
  91. package/src/prompts/creation-utils.ts +118 -0
  92. package/src/prompts/dex-trading.ts +86 -0
  93. package/src/prompts/discover-tokens.ts +86 -0
  94. package/src/prompts/index.ts +175 -0
  95. package/src/prompts/liquidity-positions.ts +270 -0
  96. package/src/prompts/pools.ts +571 -0
  97. package/src/prompts/portfolio.ts +242 -0
  98. package/src/prompts/social.ts +100 -0
  99. package/src/prompts/trading-calculations.ts +499 -0
  100. package/src/prompts/trading.ts +191 -0
  101. package/src/prompts/transfers.ts +103 -0
  102. package/src/prompts/utility-tools.ts +349 -0
  103. package/src/prompts/utility.ts +92 -0
  104. package/src/prompts/utils/workflowTemplates.ts +511 -0
  105. package/src/schemas/common-schemas.ts +393 -0
  106. package/src/scripts/test-all-prompts.ts +184 -0
  107. package/src/server.ts +367 -0
  108. package/src/tools/__tests__/dex-tools.test.ts +562 -0
  109. package/src/tools/__tests__/liquidity-positions.test.ts +673 -0
  110. package/src/tools/balance/index.ts +174 -0
  111. package/src/tools/creation/index.ts +182 -0
  112. package/src/tools/dex/index.ts +226 -0
  113. package/src/tools/dex/liquidity-positions.ts +547 -0
  114. package/src/tools/index.ts +94 -0
  115. package/src/tools/pools/fetchAllPools.ts +47 -0
  116. package/src/tools/pools/fetchAllPriceHistory.ts +119 -0
  117. package/src/tools/pools/fetchPoolDetails.ts +27 -0
  118. package/src/tools/pools/fetchPoolDetailsForCalculation.ts +22 -0
  119. package/src/tools/pools/fetchPools.ts +47 -0
  120. package/src/tools/pools/fetchPriceHistory.ts +124 -0
  121. package/src/tools/pools/fetchTokenDetails.ts +77 -0
  122. package/src/tools/pools/index.ts +284 -0
  123. package/src/tools/social/index.ts +64 -0
  124. package/src/tools/trading/index.ts +605 -0
  125. package/src/tools/transfers/index.ts +75 -0
  126. package/src/tools/utils/clearCache.ts +36 -0
  127. package/src/tools/utils/createWallet.ts +19 -0
  128. package/src/tools/utils/explainSdkUsage.ts +1446 -0
  129. package/src/tools/utils/getAddress.ts +12 -0
  130. package/src/tools/utils/getCacheInfo.ts +14 -0
  131. package/src/tools/utils/getConfig.ts +21 -0
  132. package/src/tools/utils/getEnvironment.ts +17 -0
  133. package/src/tools/utils/getEthereumAddress.ts +12 -0
  134. package/src/tools/utils/getUrlByTokenName.ts +12 -0
  135. package/src/tools/utils/getVersion.ts +25 -0
  136. package/src/tools/utils/getWallet.ts +25 -0
  137. package/src/tools/utils/hasWallet.ts +15 -0
  138. package/src/tools/utils/index.ts +37 -0
  139. package/src/tools/utils/isTokenGraduated.ts +16 -0
  140. package/src/tools/utils/setWallet.ts +41 -0
  141. package/src/tools/utils/switchEnvironment.ts +28 -0
  142. package/src/types/mcp.ts +72 -0
  143. package/src/utils/__tests__/validation.test.ts +147 -0
  144. package/src/utils/constraints.ts +155 -0
  145. package/src/utils/default-values.ts +208 -0
  146. package/src/utils/error-handler.ts +69 -0
  147. package/src/utils/error-templates.ts +273 -0
  148. package/src/utils/response-formatter.ts +51 -0
  149. package/src/utils/tool-factory.ts +303 -0
  150. package/src/utils/tool-registry.ts +296 -0
  151. package/src/utils/validation.ts +371 -0
  152. package/tests/wallet-management-integration.test.ts +284 -0
  153. package/tsconfig.json +23 -0
@@ -0,0 +1,371 @@
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
+ }
@@ -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
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "CommonJS",
5
+ "moduleResolution": "node",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "sourceMap": true,
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "noEmit": false,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "resolveJsonModule": true,
18
+ "allowSyntheticDefaultImports": true,
19
+ "types": ["node", "jest"]
20
+ },
21
+ "include": ["src/**/*"],
22
+ "exclude": ["node_modules", "dist", "tests", "src/scripts", "src/**/__tests__"]
23
+ }