UNPKG

@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
"use strict"; /** * 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