Skip to main content
Glama

Neo N3 MCP Server

by r3e-network
validation.ts11.1 kB
/** * Validation utilities for Neo N3 MCP Server * Provides robust validation for input parameters * * This module contains comprehensive validation functions for all Neo N3 blockchain * related parameters, ensuring data integrity and security. */ import * as neonJs from '@cityofzion/neon-js'; import { NeoNetwork } from '../services/neo-service'; import { ValidationError } from './errors'; import { logger } from './logger'; // Constants for validation const MAX_GAS_AMOUNT = 1_000_000_000; // 1 billion GAS const MIN_PASSWORD_LENGTH = 8; const MAX_PASSWORD_LENGTH = 100; const NEO_ADDRESS_PATTERN = /^[A-Za-z0-9]{34}$/; const HASH_LENGTH = 64; // 32 bytes * 2 chars per byte const SCRIPT_HASH_LENGTH = 40; // 20 bytes * 2 chars per byte const HEX_PATTERN = /^[0-9a-fA-F]+$/; const POSITIVE_NUMBER_PATTERN = /^[0-9]+(\.[0-9]+)?$/; const POSITIVE_INTEGER_PATTERN = /^[0-9]+$/; /** * Validate Neo N3 address format * @param address Neo N3 address to validate * @returns Validated address * @throws ValidationError if invalid */ export function validateAddress(address: string): string { if (!address || typeof address !== 'string') { throw new ValidationError('Address must be a non-empty string'); } // Check basic format (Neo N3 addresses are 34 characters) if (!NEO_ADDRESS_PATTERN.test(address)) { throw new ValidationError(`Invalid Neo N3 address format: ${address}`); } try { // Use NeonJS to verify address const scriptHash = neonJs.wallet.getScriptHashFromAddress(address); if (!scriptHash || scriptHash.length !== SCRIPT_HASH_LENGTH) { throw new Error('Invalid address conversion'); } } catch (error) { logger.warn(`Address validation failed: ${address}`, { error: error instanceof Error ? error.message : String(error) }); throw new ValidationError(`Invalid Neo N3 address: ${address}`); } return address; } /** * Validate transaction hash format * @param hash Transaction hash to validate * @returns Validated hash * @throws ValidationError if invalid */ export function validateHash(hash: string): string { if (!hash || typeof hash !== 'string') { throw new ValidationError('Hash must be a non-empty string'); } // Remove '0x' prefix if present const cleanHash = hash.startsWith('0x') ? hash.substring(2) : hash; // Check length (64 characters for a transaction hash without '0x') if (cleanHash.length !== HASH_LENGTH) { throw new ValidationError(`Invalid hash length: ${hash}. Expected ${HASH_LENGTH} characters (without 0x prefix).`); } // Check if hexadecimal if (!HEX_PATTERN.test(cleanHash)) { throw new ValidationError(`Invalid hash format (not hexadecimal): ${hash}`); } // Return normalized form (with 0x prefix) return `0x${cleanHash}`; } /** * Validate script hash format * @param scriptHash Script hash to validate * @returns Validated script hash * @throws ValidationError if invalid */ export function validateScriptHash(scriptHash: string): string { if (!scriptHash || typeof scriptHash !== 'string') { throw new ValidationError('Script hash must be a non-empty string'); } // Remove '0x' prefix if present const cleanHash = scriptHash.startsWith('0x') ? scriptHash.substring(2) : scriptHash; // Check length (40 characters for a Neo script hash without '0x') if (cleanHash.length !== SCRIPT_HASH_LENGTH) { throw new ValidationError(`Invalid script hash length: ${scriptHash}. Expected ${SCRIPT_HASH_LENGTH} characters (without 0x prefix).`); } // Check if hexadecimal if (!HEX_PATTERN.test(cleanHash)) { throw new ValidationError(`Invalid script hash format (not hexadecimal): ${scriptHash}`); } // Return normalized form (with 0x prefix) return `0x${cleanHash}`; } /** * Validate amount with decimal support * @param amount Amount to validate * @returns Normalized amount as string * @throws ValidationError if invalid */ export function validateAmount(amount: string | number): string { let numericAmount: number; if (typeof amount === 'string') { // Check for valid number format if (!POSITIVE_NUMBER_PATTERN.test(amount)) { throw new ValidationError(`Invalid amount format: ${amount}. Must be a positive number.`); } numericAmount = parseFloat(amount); } else if (typeof amount === 'number') { if (isNaN(amount) || !isFinite(amount)) { throw new ValidationError('Amount must be a valid number'); } numericAmount = amount; } else { throw new ValidationError('Amount must be a number or numeric string'); } // Check range if (numericAmount <= 0) { throw new ValidationError('Amount must be greater than zero'); } // Check for reasonable max value if (numericAmount > MAX_GAS_AMOUNT) { throw new ValidationError(`Amount exceeds maximum allowed (${MAX_GAS_AMOUNT})`); } // Return normalized string format with up to 8 decimal places (Neo precision) return numericAmount.toString(); } /** * Validate password * @param password Password to validate * @returns Validated password * @throws ValidationError if invalid */ export function validatePassword(password: string): string { if (!password || typeof password !== 'string') { throw new ValidationError('Password must be a non-empty string'); } // Check minimum length if (password.length < MIN_PASSWORD_LENGTH) { throw new ValidationError(`Password must be at least ${MIN_PASSWORD_LENGTH} characters long`); } // Check maximum length to prevent DoS attacks if (password.length > MAX_PASSWORD_LENGTH) { throw new ValidationError(`Password must be less than ${MAX_PASSWORD_LENGTH} characters long`); } // Return the password return password; } /** * Validate Neo network * @param network Network to validate * @returns Validated network * @throws ValidationError if invalid */ export function validateNetwork(network?: string): NeoNetwork { if (!network) { return NeoNetwork.MAINNET; // Default to mainnet } if (typeof network !== 'string') { throw new ValidationError('Network must be a string'); } const normalizedNetwork = network.toLowerCase().trim(); if (normalizedNetwork === NeoNetwork.MAINNET || normalizedNetwork === NeoNetwork.TESTNET) { return normalizedNetwork as NeoNetwork; } throw new ValidationError(`Invalid network: ${network}. Must be one of: ${NeoNetwork.MAINNET}, ${NeoNetwork.TESTNET}`); } /** * Sanitize string by removing control characters and trimming whitespace * @param input String to sanitize * @returns Sanitized string * @throws ValidationError if not a string */ export function sanitizeString(input: string): string { if (typeof input !== 'string') { throw new ValidationError('Input must be a string'); } // Remove control characters and trim whitespace return input .replace(/[\u0000-\u001F\u007F-\u009F]/g, '') // Control chars .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove script tags and content .replace(/<[^>]*>/g, '') // Remove other HTML tags .trim(); } /** * Validate boolean value * @param value Boolean value to validate * @returns Validated boolean * @throws ValidationError if invalid */ export function validateBoolean(value: boolean | string | number): boolean { if (typeof value === 'boolean') { return value; } if (typeof value === 'string') { // Be strict - no whitespace allowed if (value !== value.trim()) { throw new ValidationError(`Invalid boolean value: ${value}. No whitespace allowed.`); } const normalizedValue = value.toLowerCase(); if (normalizedValue === 'true') return true; if (normalizedValue === 'false') return false; if (normalizedValue === '1') return true; if (normalizedValue === '0') return false; throw new ValidationError(`Invalid boolean value: ${value}. Must be 'true', 'false', '1', or '0'.`); } if (typeof value === 'number') { if (value === 1) return true; if (value === 0) return false; throw new ValidationError(`Invalid boolean value: ${value}. Must be 1 or 0.`); } throw new ValidationError('Boolean value must be a boolean, string, or number.'); } /** * Validate positive integer * @param value Integer value to validate * @returns Validated integer * @throws ValidationError if invalid */ export function validateInteger(value: number | string): number { let intValue: number; if (typeof value === 'string') { if (!POSITIVE_INTEGER_PATTERN.test(value)) { throw new ValidationError(`Invalid integer format: ${value}`); } intValue = parseInt(value, 10); } else if (typeof value === 'number') { if (!Number.isInteger(value)) { throw new ValidationError('Value must be an integer'); } intValue = value; } else { throw new ValidationError('Value must be a number or numeric string'); } if (intValue < 0) { throw new ValidationError('Value must be a positive integer'); } return intValue; } /** * Validate contract name * @param contractName Contract name to validate * @param availableContracts List of available contract names * @returns Validated contract name * @throws ValidationError if invalid */ export function validateContractName(contractName: string, availableContracts: string[]): string { if (!contractName || typeof contractName !== 'string') { throw new ValidationError('Contract name must be a non-empty string'); } // Sanitize and normalize contract name (case-insensitive) const normalizedName = contractName.trim(); const lowerCaseName = normalizedName.toLowerCase(); // Find the contract name case-insensitively const matchingContract = availableContracts.find(c => c.toLowerCase() === lowerCaseName); if (availableContracts.length > 0 && !matchingContract) { throw new ValidationError( `Invalid contract name: ${normalizedName}. ` + `Available contracts: ${availableContracts.join(', ')}` ); } // Return the name with the correct casing from the available list return matchingContract || normalizedName; // Fallback to original if somehow not found after check } /** * Validate contract operation * @param operation Operation name to validate * @param availableOperations List of available operations * @returns Validated operation name * @throws ValidationError if invalid */ export function validateContractOperation(operation: string, availableOperations: string[]): string { if (!operation || typeof operation !== 'string') { throw new ValidationError('Operation name must be a non-empty string'); } // Sanitize and normalize operation name const normalizedOperation = operation.trim(); // Check if operation is in the list of available operations if (availableOperations.length > 0 && !availableOperations.includes(normalizedOperation)) { throw new ValidationError( `Invalid operation: ${normalizedOperation}. ` + `Available operations: ${availableOperations.join(', ')}` ); } return normalizedOperation; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/r3e-network/neo-n3-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server