diff --git a/src/core/chains.ts b/src/core/chains.ts index 75115d3..b0ba4f9 100644 --- a/src/core/chains.ts +++ b/src/core/chains.ts @@ -289,26 +289,25 @@ export const rpcUrlMap: Record = { */ export function resolveChainId(chainIdentifier: number | string): number { if (typeof chainIdentifier === 'number') { + if (!chainMap[chainIdentifier]) { + throw new Error(`Unsupported chain ID: ${chainIdentifier}. Use a supported network.`); + } return chainIdentifier; } - - // Convert to lowercase for case-insensitive matching + const networkName = chainIdentifier.toLowerCase(); - - // Check if the network name is in our map + const chainId = networkNameMap[networkName]; if (chainId !== undefined) { return chainId; } - - // Try parsing as a number + const parsedId = parseInt(networkName); - if (!isNaN(parsedId)) { + if (!isNaN(parsedId) && chainMap[parsedId]) { return parsedId; } - - // Default to mainnet if not found - return DEFAULT_CHAIN_ID; + + throw new Error(`Unknown network: "${chainIdentifier}". Supported: ${getSupportedNetworks().slice(0, 10).join(', ')}...`); } /** @@ -320,17 +319,19 @@ export function resolveChainId(chainIdentifier: number | string): number { export function getChain(chainIdentifier: number | string = DEFAULT_CHAIN_ID): Chain { if (typeof chainIdentifier === 'string') { const networkName = chainIdentifier.toLowerCase(); - // Try to get from direct network name mapping first if (networkNameMap[networkName]) { - return chainMap[networkNameMap[networkName]] || mainnet; + const chain = chainMap[networkNameMap[networkName]]; + if (!chain) throw new Error(`Chain config missing for network: ${chainIdentifier}`); + return chain; } - - // If not found, throw an error throw new Error(`Unsupported network: ${chainIdentifier}`); } - - // If it's a number, return the chain from chainMap - return chainMap[chainIdentifier] || mainnet; + + const chain = chainMap[chainIdentifier]; + if (!chain) { + throw new Error(`Unsupported chain ID: ${chainIdentifier}`); + } + return chain; } /** diff --git a/src/core/services/transfer.ts b/src/core/services/transfer.ts index 06f8f75..0f966a7 100644 --- a/src/core/services/transfer.ts +++ b/src/core/services/transfer.ts @@ -13,6 +13,23 @@ import { getPublicClient, getWalletClient } from './clients.js'; import { getChain } from '../chains.js'; import { resolveAddress } from './ens.js'; +/** + * Validates that a string represents a positive numeric amount. + * Prevents negative values, non-numeric strings, and excessively large amounts. + */ +function validateAmount(amount: string, label = 'Amount'): void { + if (!/^\d+(\.\d+)?$/.test(amount)) { + throw new Error(`${label} must be a positive number (got: "${amount}")`); + } + const parsed = parseFloat(amount); + if (parsed <= 0) { + throw new Error(`${label} must be greater than zero`); + } + if (parsed > 1e18) { + throw new Error(`${label} exceeds maximum allowed value`); + } +} + // Standard ERC20 ABI for transfers const erc20TransferAbi = [ { @@ -137,6 +154,7 @@ export async function transferETH( : privateKey as Hex; const client = getWalletClient(formattedKey, network); + validateAmount(amount, 'ETH amount'); const amountWei = parseEther(amount); return client.sendTransaction({ @@ -195,11 +213,12 @@ export async function transferERC20( const symbol = await contract.read.symbol(); // Parse the amount with the correct number of decimals + validateAmount(amount, 'Token amount'); const rawAmount = parseUnits(amount, decimals); - + // Create wallet client for sending the transaction const walletClient = getWalletClient(formattedKey, network); - + // Send the transaction const hash = await walletClient.writeContract({ address: tokenAddress, @@ -271,11 +290,12 @@ export async function approveERC20( const symbol = await contract.read.symbol(); // Parse the amount with the correct number of decimals + validateAmount(amount, 'Approval amount'); const rawAmount = parseUnits(amount, decimals); - + // Create wallet client for sending the transaction const walletClient = getWalletClient(formattedKey, network); - + // Send the transaction const hash = await walletClient.writeContract({ address: tokenAddress, @@ -412,6 +432,7 @@ export async function transferERC1155( const fromAddress = walletClient.account!.address; // Parse amount to bigint + validateAmount(amount, 'ERC1155 amount'); const amountBigInt = BigInt(amount); // Send the transaction diff --git a/src/core/tools.ts b/src/core/tools.ts index f10ed2d..78bb3f3 100644 --- a/src/core/tools.ts +++ b/src/core/tools.ts @@ -5,6 +5,17 @@ import * as services from "./services/index.js"; import { type Address, type Hex, type Hash } from 'viem'; import { normalize } from 'viem/ens'; +/** + * Validates an Ethereum address format (0x + 40 hex chars). + * Throws if invalid. Allows ENS names (containing '.') to pass through. + */ +function validateAddressOrEns(value: string, label = 'Address'): void { + if (value.includes('.')) return; + if (!/^0x[a-fA-F0-9]{40}$/.test(value)) { + throw new Error(`${label} must be a valid Ethereum address (0x + 40 hex chars) or ENS name`); + } +} + /** * Register all EVM-related tools with the MCP server * @@ -666,6 +677,7 @@ export function registerEVMTools(server: McpServer) { }, async ({ contractAddress, functionName, args = [], abiJson, network = "ethereum" }) => { try { + validateAddressOrEns(contractAddress, 'Contract address'); const client = await services.getPublicClient(network); let abi: any[] | undefined; @@ -772,6 +784,7 @@ export function registerEVMTools(server: McpServer) { }, async ({ contractAddress, functionName, args = [], value, abiJson, network = "ethereum" }) => { try { + validateAddressOrEns(contractAddress, 'Contract address'); const privateKey = getConfiguredPrivateKey(); const senderAddress = getWalletAddressFromKey(); const client = await services.getPublicClient(network); @@ -1010,6 +1023,7 @@ export function registerEVMTools(server: McpServer) { }, async ({ to, amount, network = "ethereum" }) => { try { + validateAddressOrEns(to, 'Recipient address'); const privateKey = getConfiguredPrivateKey(); const senderAddress = getWalletAddressFromKey(); const txHash = await services.transferETH(privateKey, to as Address, amount, network); @@ -1055,6 +1069,8 @@ export function registerEVMTools(server: McpServer) { }, async ({ tokenAddress, to, amount, network = "ethereum" }) => { try { + validateAddressOrEns(tokenAddress, 'Token address'); + validateAddressOrEns(to, 'Recipient address'); const privateKey = getConfiguredPrivateKey(); const senderAddress = getWalletAddressFromKey(); const result = await services.transferERC20(tokenAddress as Address, to as Address, amount, privateKey, network);