UNPKG

infura-mcp-server

Version:

Model Context Protocol (MCP) server providing comprehensive read-only Ethereum blockchain access through Infura's infrastructure. Connect Claude Desktop, VS Code, and Cursor to 29 secure Ethereum JSON-RPC tools across 30+ networks including Ethereum, Poly

500 lines (486 loc) 16.5 kB
/** * Input Validators for Ethereum Data * * This security-focused module provides comprehensive validation functions for * Ethereum-related data types. All user inputs should be validated using these * functions before being passed to the Infura API to prevent injection attacks * and ensure data integrity. * * The module provides two types of validators: * 1. Boolean validators (isValid*) - Return true/false for conditional checks * 2. Throwing validators (validate*) - Throw ValidationError with descriptive messages * * @module validators * * @example * // Boolean validation for conditional logic * import { isValidEthAddress, isValidNetwork } from './lib/validators.js'; * * if (isValidEthAddress(userInput)) { * // Process valid address * } * * @example * // Throwing validation for fail-fast behavior * import { validateAddress, validateNetwork } from './lib/validators.js'; * * try { * validateAddress(userInput, 'fromAddress'); * validateNetwork(network); * } catch (error) { * console.error(error.message); // User-friendly error message * } */ /** * Validates an Ethereum address format. * * A valid Ethereum address consists of: * - The prefix "0x" * - Followed by exactly 40 hexadecimal characters (0-9, a-f, A-F) * * Note: This validates format only, not checksum. Both checksummed and * non-checksummed addresses are accepted. * * @function isValidEthAddress * @param {string} address - The address string to validate * @returns {boolean} True if the address matches valid Ethereum address format * * @example * isValidEthAddress('0x742d35Cc6634C0532925a3b844Bc454e4438f44e'); // true * isValidEthAddress('0x742d35cc6634c0532925a3b844bc454e4438f44e'); // true (lowercase) * isValidEthAddress('742d35Cc6634C0532925a3b844Bc454e4438f44e'); // false (missing 0x) * isValidEthAddress('0x742d35Cc6634C0532925a3b844Bc454e4438f4'); // false (39 chars) * isValidEthAddress(null); // false */ export function isValidEthAddress(address) { if (typeof address !== 'string') return false; return /^0x[a-fA-F0-9]{40}$/.test(address); } /** * Validates a transaction or block hash format. * * A valid Ethereum hash (transaction hash or block hash) consists of: * - The prefix "0x" * - Followed by exactly 64 hexadecimal characters (representing 32 bytes) * * This is used for: * - Transaction hashes (txHash) * - Block hashes * - State roots * - Any 32-byte Keccak-256 hash * * @function isValidHash * @param {string} hash - The hash string to validate * @returns {boolean} True if the hash matches valid 32-byte hash format * * @example * // Valid transaction hash * isValidHash('0xabc123def456789012345678901234567890123456789012345678901234abcd'); // true * * // Invalid: wrong length * isValidHash('0xabc123'); // false * * // Invalid: missing prefix * isValidHash('abc123def456789012345678901234567890123456789012345678901234abcd'); // false * * // Invalid: non-string input * isValidHash(123); // false */ export function isValidHash(hash) { if (typeof hash !== 'string') return false; return /^0x[a-fA-F0-9]{64}$/.test(hash); } /** * Validates an Ethereum block tag or block number. * * Valid block tags are: * - "latest": The most recent mined block * - "earliest": The genesis block (block 0) * - "pending": Pending transactions (may not be supported by all methods) * - "safe": The most recent safe head block (post-merge) * - "finalized": The most recent finalized block (post-merge) * - Hex block number: e.g., "0x1234" for a specific block height * * @function isValidBlockTag * @param {string} tag - The block tag or hex block number to validate * @returns {boolean} True if the tag is a valid block identifier * * @example * // Named tags * isValidBlockTag('latest'); // true * isValidBlockTag('earliest'); // true * isValidBlockTag('pending'); // true * isValidBlockTag('safe'); // true * isValidBlockTag('finalized'); // true * * // Hex block numbers * isValidBlockTag('0x0'); // true (genesis) * isValidBlockTag('0x10d4f'); // true * * // Invalid * isValidBlockTag('current'); // false (not a valid tag) * isValidBlockTag(100); // false (must be string) */ export function isValidBlockTag(tag) { if (typeof tag !== 'string') return false; const validTags = ['latest', 'earliest', 'pending', 'safe', 'finalized']; return validTags.includes(tag) || /^0x[a-fA-F0-9]+$/.test(tag); } /** * Validates a hexadecimal string with 0x prefix. * * A valid hex string consists of: * - The prefix "0x" * - Followed by zero or more hexadecimal characters * * This is more permissive than other validators - it allows any length * including empty hex ("0x"). Use this for arbitrary data fields like * transaction input data. * * @function isValidHexString * @param {string} hex - The hex string to validate * @returns {boolean} True if the string is a valid hex format * * @example * isValidHexString('0x'); // true (empty data) * isValidHexString('0x1234'); // true * isValidHexString('0xabcdef'); // true * isValidHexString('0xABCDEF'); // true (uppercase) * isValidHexString('1234'); // false (missing 0x) * isValidHexString('0xGHIJ'); // false (invalid hex chars) */ export function isValidHexString(hex) { if (typeof hex !== 'string') return false; return /^0x[a-fA-F0-9]*$/.test(hex); } /** * Validates a hex quantity according to Ethereum JSON-RPC specification. * * Hex quantities are used for numeric values in Ethereum and must NOT have * leading zeros (except for zero itself). This follows the Ethereum JSON-RPC * specification for QUANTITY type. * * Valid formats: * - "0x0" (zero) * - "0x1" through "0xf..." (no leading zeros) * * Invalid formats: * - "0x01" (leading zero) * - "0x" (empty) * * @function isValidHexQuantity * @param {string} quantity - The hex quantity to validate * @returns {boolean} True if the quantity follows proper hex quantity format * * @example * isValidHexQuantity('0x0'); // true (zero) * isValidHexQuantity('0x1'); // true * isValidHexQuantity('0x10'); // true * isValidHexQuantity('0xff'); // true * isValidHexQuantity('0x01'); // false (leading zero) * isValidHexQuantity('0x'); // false (empty) * isValidHexQuantity('0x00'); // false (leading zero) */ export function isValidHexQuantity(quantity) { if (typeof quantity !== 'string') return false; // Allow 0x0 or 0x followed by non-zero-leading hex return /^0x(0|[1-9a-fA-F][a-fA-F0-9]*)$/.test(quantity); } /** * Validates a transaction or log index in hex format. * * Indices are used to reference specific items within a block: * - Transaction index within a block * - Log index within a transaction receipt * - Uncle index within a block * * Unlike hex quantities, indices may have leading zeros. The validation * simply ensures the format is "0x" followed by at least one hex digit. * * @function isValidIndex * @param {string} index - The index to validate * @returns {boolean} True if the index is valid hex format * * @example * isValidIndex('0x0'); // true (first item) * isValidIndex('0x1'); // true * isValidIndex('0x01'); // true (leading zeros allowed) * isValidIndex('0xff'); // true * isValidIndex('0x'); // false (empty) * isValidIndex('1'); // false (missing 0x) */ export function isValidIndex(index) { if (typeof index !== 'string') return false; return /^0x[a-fA-F0-9]+$/.test(index); } /** * List of allowed Infura network identifiers. * * This constant serves as an allowlist for network validation, preventing * URL injection attacks by ensuring only known network names are used in * API endpoint construction. * * Networks are grouped by category: * - **Ethereum**: mainnet, sepolia (testnet) * - **Layer 2 & Sidechains**: Arbitrum, Optimism, Polygon, Base, Linea, zkSync, Scroll, Blast, Mantle * - **Other Networks**: Avalanche, BSC, Celo, Palm, StarkNet, opBNB, Swellchain, Unichain * * Each network has mainnet and testnet variants where applicable. * * @constant {string[]} * @see {@link https://docs.metamask.io/services/get-started/endpoints/|Infura Endpoints Documentation} * * @example * import { ALLOWED_NETWORKS } from './lib/validators.js'; * * console.log(ALLOWED_NETWORKS.length); // 40 networks * console.log(ALLOWED_NETWORKS.includes('mainnet')); // true * console.log(ALLOWED_NETWORKS.includes('polygon-mainnet')); // true */ export const ALLOWED_NETWORKS = [ // Ethereum 'mainnet', 'sepolia', // Layer 2 & Sidechains 'arbitrum-mainnet', 'arbitrum-sepolia', 'optimism-mainnet', 'optimism-sepolia', 'polygon-mainnet', 'polygon-amoy', 'base-mainnet', 'base-sepolia', 'linea-mainnet', 'linea-sepolia', 'zksync-mainnet', 'zksync-sepolia', 'scroll-mainnet', 'scroll-sepolia', 'blast-mainnet', 'blast-sepolia', 'mantle-mainnet', 'mantle-sepolia', // Other Networks 'avalanche-mainnet', 'avalanche-fuji', 'bsc-mainnet', 'bsc-testnet', 'celo-mainnet', 'celo-alfajores', 'palm-mainnet', 'palm-testnet', 'starknet-mainnet', 'starknet-sepolia', 'opbnb-mainnet', 'opbnb-testnet', 'swellchain-mainnet', 'swellchain-testnet', 'unichain-mainnet', 'unichain-sepolia', ]; /** * Checks if a network identifier is in the allowed networks list. * * This is a security-critical function that prevents URL injection attacks * by validating network names against a known allowlist before they are * used to construct API endpoint URLs. * * @function isValidNetwork * @param {string} network - The network identifier to validate * @returns {boolean} True if the network is in the allowed list * * @example * isValidNetwork('mainnet'); // true * isValidNetwork('polygon-mainnet'); // true * isValidNetwork('sepolia'); // true * isValidNetwork('invalid-net'); // false * isValidNetwork('mainnet.evil.com'); // false (injection attempt) * isValidNetwork(null); // false */ export function isValidNetwork(network) { if (typeof network !== 'string') return false; return ALLOWED_NETWORKS.includes(network); } /** * Custom error class for input validation failures. * * ValidationError extends the standard Error class with an additional * `field` property that identifies which input field failed validation. * This enables precise error reporting to users. * * @class ValidationError * @extends Error * * @property {string} name - Always 'ValidationError' * @property {string} message - Human-readable error description * @property {string} field - The name of the field that failed validation * * @example * // Creating a validation error * const error = new ValidationError( * 'Invalid Ethereum address format', * 'fromAddress' * ); * * console.log(error.name); // 'ValidationError' * console.log(error.field); // 'fromAddress' * console.log(error.message); // 'Invalid Ethereum address format' * * @example * // Catching validation errors * try { * validateAddress(input, 'walletAddress'); * } catch (error) { * if (error instanceof ValidationError) { * console.error(`Field "${error.field}" is invalid: ${error.message}`); * } * } */ export class ValidationError extends Error { /** * Creates a new ValidationError instance. * * @param {string} message - A descriptive error message * @param {string} field - The name of the field that failed validation */ constructor(message, field) { super(message); this.name = 'ValidationError'; this.field = field; } } /** * Validates an Ethereum address and throws a ValidationError if invalid. * * This is the throwing variant of isValidEthAddress for use in fail-fast * validation scenarios. The error message includes format guidance to help * users correct their input. * * @function validateAddress * @param {string} address - The address to validate * @param {string} [fieldName='address'] - The field name for error messages * @returns {void} * @throws {ValidationError} When the address format is invalid * * @example * // Valid address - no error thrown * validateAddress('0x742d35Cc6634C0532925a3b844Bc454e4438f44e'); * * // Custom field name for better error messages * validateAddress(userInput, 'recipientAddress'); * * // Invalid address - throws ValidationError * validateAddress('invalid'); * // Throws: ValidationError: Invalid Ethereum address for 'address'... */ export function validateAddress(address, fieldName = 'address') { if (!isValidEthAddress(address)) { throw new ValidationError( `Invalid Ethereum address for '${fieldName}'. ` + `Expected format: 0x followed by 40 hexadecimal characters (e.g., 0x742d35Cc6634C0532925a3b844Bc9e7595f...).`, fieldName ); } } /** * Validates a transaction or block hash and throws a ValidationError if invalid. * * This is the throwing variant of isValidHash for use in fail-fast validation * scenarios. Use this for transaction hashes, block hashes, or any 32-byte hash. * * @function validateHash * @param {string} hash - The hash to validate * @param {string} [fieldName='hash'] - The field name for error messages * @returns {void} * @throws {ValidationError} When the hash format is invalid * * @example * // Valid hash - no error thrown * validateHash('0xabc123def456789012345678901234567890123456789012345678901234abcd'); * * // Custom field name * validateHash(txHash, 'transactionHash'); * * // Invalid hash - throws ValidationError * validateHash('0xshort'); * // Throws: ValidationError: Invalid hash for 'hash'... */ export function validateHash(hash, fieldName = 'hash') { if (!isValidHash(hash)) { throw new ValidationError( `Invalid hash for '${fieldName}'. ` + `Expected format: 0x followed by 64 hexadecimal characters.`, fieldName ); } } /** * Validates a block tag and throws a ValidationError if invalid. * * This is the throwing variant of isValidBlockTag for use in fail-fast * validation scenarios. The error message lists all valid named tags * plus the hex format option. * * @function validateBlockTag * @param {string} tag - The tag to validate * @param {string} [fieldName='tag'] - The field name for error messages * @returns {void} * @throws {ValidationError} When the block tag is invalid * * @example * // Valid tags - no error thrown * validateBlockTag('latest'); * validateBlockTag('0x10d4f'); * * // Custom field name * validateBlockTag(blockParam, 'blockNumber'); * * // Invalid tag - throws ValidationError * validateBlockTag('newest'); * // Throws: ValidationError: Invalid block tag for 'tag'... */ export function validateBlockTag(tag, fieldName = 'tag') { if (!isValidBlockTag(tag)) { throw new ValidationError( `Invalid block tag for '${fieldName}'. ` + `Expected: 'latest', 'earliest', 'pending', 'safe', 'finalized', or a hex block number (e.g., '0x1234').`, fieldName ); } } /** * Validates a network identifier and throws a ValidationError if not allowed. * * This security-critical function ensures that only known network identifiers * are accepted, preventing URL injection attacks. The error message includes * example valid networks and a link to documentation. * * @function validateNetwork * @param {string} network - The network identifier to validate * @returns {void} * @throws {ValidationError} When the network is not in the allowed list * * @example * // Valid networks - no error thrown * validateNetwork('mainnet'); * validateNetwork('polygon-mainnet'); * validateNetwork('arbitrum-sepolia'); * * // Invalid network - throws ValidationError with helpful message * validateNetwork('invalid-network'); * // Throws: ValidationError: Invalid network: 'invalid-network'. Valid networks include: mainnet, sepolia, ... * * // Injection attempt - safely rejected * validateNetwork('mainnet.evil.com/attack'); * // Throws: ValidationError */ export function validateNetwork(network) { if (!isValidNetwork(network)) { const examples = ALLOWED_NETWORKS.slice(0, 5).join(', '); throw new ValidationError( `Invalid network: '${network}'. Valid networks include: ${examples}, and ${ALLOWED_NETWORKS.length - 5} more. ` + `See https://github.com/qbandev/infura-mcp-server#supported-networks for the full list.`, 'network' ); } }