@gala-chain/launchpad-mcp-server
Version:
MCP server for Gala Launchpad SDK with 55 tools + 14 slash commands - Production-grade AI agent integration with comprehensive validation, optimized performance, and 80%+ test coverage
276 lines • 10.1 kB
JavaScript
;
/**
* Validation Utilities for MCP Prompts
*
* Input validation functions for slash command arguments.
* Prevents malformed inputs and provides clear error messages.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidationError = void 0;
exports.validateTokenName = validateTokenName;
exports.validateNumericAmount = validateNumericAmount;
exports.validateSlippage = validateSlippage;
exports.validateAddress = validateAddress;
exports.validateTokenSymbol = validateTokenSymbol;
exports.validatePaginationLimit = validatePaginationLimit;
exports.validateTokenList = validateTokenList;
exports.safeValidate = safeValidate;
/**
* Validation error class for better error handling
*/
class ValidationError extends Error {
field;
constructor(field, message) {
super(`Validation error for '${field}': ${message}`);
this.field = field;
this.name = 'ValidationError';
}
}
exports.ValidationError = ValidationError;
/**
* Validate token name format
*
* Token names must be 2-20 characters, alphanumeric with hyphens and underscores.
*
* @param name - Token name to validate
* @param fieldName - Field name for error messages (default: 'tokenName')
* @throws {ValidationError} If token name is invalid
*
* @example
* ```typescript
* validateTokenName('anime'); // ✅ Valid
* validateTokenName('test-123'); // ✅ Valid
* validateTokenName('my_token'); // ✅ Valid
* validateTokenName('a'); // ❌ Throws: too short
* validateTokenName('token@#$'); // ❌ Throws: invalid characters
* ```
*/
function validateTokenName(name, fieldName = 'tokenName') {
if (!name || typeof name !== 'string') {
throw new ValidationError(fieldName, 'Token name is required');
}
if (name.length < 2 || name.length > 20) {
throw new ValidationError(fieldName, `Token name must be 2-20 characters (got ${name.length})`);
}
const tokenNameRegex = /^[a-zA-Z0-9-_]+$/;
if (!tokenNameRegex.test(name)) {
throw new ValidationError(fieldName, 'Token name can only contain letters, numbers, hyphens, and underscores');
}
}
/**
* Validate numeric amount
*
* Amounts must be positive numbers (integers or decimals).
*
* @param amount - Amount to validate (string or number)
* @param fieldName - Field name for error messages
* @throws {ValidationError} If amount is invalid
*
* @example
* ```typescript
* validateNumericAmount('100', 'galaAmount'); // ✅ Valid
* validateNumericAmount('99.5', 'tokenAmount'); // ✅ Valid
* validateNumericAmount('-5', 'amount'); // ❌ Throws: negative
* validateNumericAmount('abc', 'amount'); // ❌ Throws: not a number
* ```
*/
function validateNumericAmount(amount, fieldName) {
if (amount === null || amount === undefined || amount === '') {
throw new ValidationError(fieldName, 'Amount is required');
}
const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount;
if (isNaN(numAmount)) {
throw new ValidationError(fieldName, `Amount must be a valid number (got: ${amount})`);
}
if (numAmount <= 0) {
throw new ValidationError(fieldName, `Amount must be positive (got: ${numAmount})`);
}
if (!isFinite(numAmount)) {
throw new ValidationError(fieldName, 'Amount must be a finite number');
}
}
/**
* Validate slippage tolerance percentage
*
* Slippage must be between 0.01% and 100%.
*
* @param slippage - Slippage percentage to validate
* @param fieldName - Field name for error messages (default: 'slippage')
* @throws {ValidationError} If slippage is invalid
*
* @example
* ```typescript
* validateSlippage('1'); // ✅ Valid (1%)
* validateSlippage('0.5'); // ✅ Valid (0.5%)
* validateSlippage('50'); // ✅ Valid (50%)
* validateSlippage('0'); // ❌ Throws: too low
* validateSlippage('150'); // ❌ Throws: too high
* ```
*/
function validateSlippage(slippage, fieldName = 'slippage') {
if (slippage === null || slippage === undefined || slippage === '') {
throw new ValidationError(fieldName, 'Slippage is required');
}
const numSlippage = typeof slippage === 'string' ? parseFloat(slippage) : slippage;
if (isNaN(numSlippage)) {
throw new ValidationError(fieldName, `Slippage must be a valid number (got: ${slippage})`);
}
if (numSlippage < 0.01 || numSlippage > 100) {
throw new ValidationError(fieldName, `Slippage must be between 0.01% and 100% (got: ${numSlippage}%)`);
}
}
/**
* Validate wallet address format
*
* Supports both GalaChain format (eth|0x...) and standard Ethereum format (0x...).
*
* @param address - Wallet address to validate
* @param fieldName - Field name for error messages (default: 'address')
* @throws {ValidationError} If address is invalid
*
* @example
* ```typescript
* validateAddress('eth|0x1234567890abcdef1234567890abcdef12345678'); // ✅ Valid
* validateAddress('0x1234567890abcdef1234567890abcdef12345678'); // ✅ Valid
* validateAddress('invalid'); // ❌ Throws: invalid format
* ```
*/
function validateAddress(address, fieldName = 'address') {
if (!address || typeof address !== 'string') {
throw new ValidationError(fieldName, 'Address is required');
}
// GalaChain format: eth|0x{40 hex chars}
const galachainRegex = /^eth\|0x[a-fA-F0-9]{40}$/;
// Standard Ethereum format: 0x{40 hex chars}
const ethereumRegex = /^0x[a-fA-F0-9]{40}$/;
if (!galachainRegex.test(address) && !ethereumRegex.test(address)) {
throw new ValidationError(fieldName, 'Address must be in GalaChain format (eth|0x...) or Ethereum format (0x...)');
}
}
/**
* Validate token symbol format
*
* Symbols must be 1-8 uppercase letters.
*
* @param symbol - Token symbol to validate
* @param fieldName - Field name for error messages (default: 'symbol')
* @throws {ValidationError} If symbol is invalid
*
* @example
* ```typescript
* validateTokenSymbol('GALA'); // ✅ Valid
* validateTokenSymbol('TEST'); // ✅ Valid
* validateTokenSymbol('test'); // ❌ Throws: must be uppercase
* validateTokenSymbol('TOOLONG123'); // ❌ Throws: too long
* ```
*/
function validateTokenSymbol(symbol, fieldName = 'symbol') {
if (!symbol || typeof symbol !== 'string') {
throw new ValidationError(fieldName, 'Symbol is required');
}
if (symbol.length < 1 || symbol.length > 8) {
throw new ValidationError(fieldName, `Symbol must be 1-8 characters (got ${symbol.length})`);
}
const symbolRegex = /^[A-Z]+$/;
if (!symbolRegex.test(symbol)) {
throw new ValidationError(fieldName, 'Symbol must be uppercase letters only (A-Z)');
}
}
/**
* Validate pagination limit
*
* Limits must be positive integers within reasonable range (1-100).
*
* @param limit - Pagination limit to validate
* @param max - Maximum allowed limit (default: 100)
* @param fieldName - Field name for error messages (default: 'limit')
* @throws {ValidationError} If limit is invalid
*
* @example
* ```typescript
* validatePaginationLimit('20'); // ✅ Valid
* validatePaginationLimit('1'); // ✅ Valid
* validatePaginationLimit('0'); // ❌ Throws: too low
* validatePaginationLimit('150'); // ❌ Throws: too high
* ```
*/
function validatePaginationLimit(limit, max = 100, fieldName = 'limit') {
if (limit === null || limit === undefined || limit === '') {
throw new ValidationError(fieldName, 'Limit is required');
}
const numLimit = typeof limit === 'string' ? parseInt(limit, 10) : limit;
if (isNaN(numLimit) || !Number.isInteger(numLimit)) {
throw new ValidationError(fieldName, `Limit must be a valid integer (got: ${limit})`);
}
if (numLimit < 1 || numLimit > max) {
throw new ValidationError(fieldName, `Limit must be between 1 and ${max} (got: ${numLimit})`);
}
}
/**
* Validate comma-separated token list
*
* For batch operations that accept multiple token names.
*
* @param tokens - Comma-separated token names
* @param fieldName - Field name for error messages (default: 'tokens')
* @throws {ValidationError} If token list is invalid
*
* @example
* ```typescript
* validateTokenList('anime,test,dragon'); // ✅ Valid
* validateTokenList('token1'); // ✅ Valid (single token)
* validateTokenList(''); // ❌ Throws: empty list
* validateTokenList('token1,invalid@#$'); // ❌ Throws: invalid token name
* ```
*/
function validateTokenList(tokens, fieldName = 'tokens') {
if (!tokens || typeof tokens !== 'string') {
throw new ValidationError(fieldName, 'Token list is required');
}
const tokenArray = tokens.split(',').map((t) => t.trim());
if (tokenArray.length === 0 || tokenArray.some((t) => !t)) {
throw new ValidationError(fieldName, 'Token list cannot be empty');
}
// Validate each token name
tokenArray.forEach((token, index) => {
try {
validateTokenName(token, `${fieldName}[${index}]`);
}
catch (error) {
if (error instanceof ValidationError) {
throw new ValidationError(fieldName, `Invalid token at position ${index + 1}: ${token}`);
}
throw error;
}
});
return tokenArray;
}
/**
* Safe validation wrapper that doesn't throw
*
* Useful for non-critical validation where you want to continue with a warning.
*
* @param validationFn - Validation function to execute
* @returns Object with success flag and optional error
*
* @example
* ```typescript
* const result = safeValidate(() => validateTokenName('anime'));
* if (!result.success) {
* console.warn('Validation warning:', result.error);
* }
* ```
*/
function safeValidate(validationFn) {
try {
validationFn();
return { success: true };
}
catch (error) {
if (error instanceof ValidationError) {
return { success: false, error: error.message };
}
return { success: false, error: String(error) };
}
}
//# sourceMappingURL=validation.js.map