batch_check_addresses
Check multiple Ethereum addresses simultaneously for Aave V3 liquidation risks by analyzing health factors and positions.
Instructions
Batch check multiple Ethereum addresses for liquidation opportunities. Returns a summary of all addresses with their health status.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| addresses | Yes | Array of Ethereum addresses to check (max 20 addresses) |
Implementation Reference
- src/index.ts:399-480 (handler)Main handler for 'batch_check_addresses' tool execution. Validates input addresses, calls batchAnalyzeLiquidation on AaveClient, processes results into summary statistics, and returns formatted JSON response.case 'batch_check_addresses': { const addresses = args?.addresses as string[]; if (!Array.isArray(addresses) || addresses.length === 0) { throw new McpError( ErrorCode.InvalidParams, 'addresses parameter is required and must be a non-empty array' ); } if (addresses.length > 20) { throw new McpError( ErrorCode.InvalidParams, 'Maximum 20 addresses allowed per batch request' ); } // Validate all addresses first const invalidAddresses = addresses.filter( (addr) => typeof addr !== 'string' || !aaveClient.isValidAddress(addr) ); if (invalidAddresses.length > 0) { throw new McpError( ErrorCode.InvalidParams, `Invalid Ethereum addresses: ${invalidAddresses.join(', ')}` ); } const results = await aaveClient.batchAnalyzeLiquidation(addresses); // Calculate summary statistics let liquidatable = 0; let atRisk = 0; let healthy = 0; let failed = 0; const formattedResults = results.map((r) => { // Count statistics if (r.error) { failed++; } else if (r.opportunity?.riskLevel === 'HIGH') { liquidatable++; } else if (r.opportunity) { atRisk++; } else { healthy++; } return { address: r.address, status: r.error ? 'ERROR' : r.opportunity ? r.opportunity.riskLevel === 'HIGH' ? 'LIQUIDATABLE' : 'AT_RISK' : 'HEALTHY', healthFactor: r.opportunity?.healthFactor || 'N/A', totalDebtUSD: r.opportunity?.totalDebtUSD || '0', riskLevel: r.opportunity?.riskLevel || 'NONE', error: r.error, }; }); const summary = { totalChecked: addresses.length, successful: addresses.length - failed, failed, liquidatable, atRisk, healthy, results: formattedResults, }; return { content: [ { type: 'text', text: JSON.stringify(summary, null, 2), }, ], }; }
- src/index.ts:129-146 (schema)Input schema definition for the batch_check_addresses tool, specifying an array of Ethereum addresses (max 20).{ name: 'batch_check_addresses', description: 'Batch check multiple Ethereum addresses for liquidation opportunities. Returns a summary of all addresses with their health status.', inputSchema: { type: 'object', properties: { addresses: { type: 'array', items: { type: 'string', }, description: 'Array of Ethereum addresses to check (max 20 addresses)', }, }, required: ['addresses'], }, },
- src/index.ts:48-164 (registration)Registration of all tools including batch_check_addresses via the ListToolsRequestHandler.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'get_user_health', description: 'Get health factor and account data for a specific Ethereum address on Aave V3. Returns collateral, debt, and liquidation status.', inputSchema: { type: 'object', properties: { address: { type: 'string', description: 'Ethereum address to check (must be a valid address)', }, }, required: ['address'], }, }, { name: 'analyze_liquidation', description: 'Analyze a user position for liquidation opportunity. Returns detailed information including collateral assets, debt assets, risk level, and potential profit.', inputSchema: { type: 'object', properties: { address: { type: 'string', description: 'Ethereum address to analyze (must be a valid address)', }, }, required: ['address'], }, }, { name: 'get_user_positions', description: 'Get detailed breakdown of a user collateral and debt positions across all Aave V3 assets.', inputSchema: { type: 'object', properties: { address: { type: 'string', description: 'Ethereum address to query', }, }, required: ['address'], }, }, { name: 'get_aave_reserves', description: 'Get list of all available reserves (assets) in Aave V3 protocol with their configuration.', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_asset_price', description: 'Get current price for a specific asset from Aave oracle.', inputSchema: { type: 'object', properties: { assetAddress: { type: 'string', description: 'Token contract address', }, }, required: ['assetAddress'], }, }, { name: 'get_protocol_status', description: 'Get general Aave V3 protocol status including current block number.', inputSchema: { type: 'object', properties: {}, }, }, { name: 'batch_check_addresses', description: 'Batch check multiple Ethereum addresses for liquidation opportunities. Returns a summary of all addresses with their health status.', inputSchema: { type: 'object', properties: { addresses: { type: 'array', items: { type: 'string', }, description: 'Array of Ethereum addresses to check (max 20 addresses)', }, }, required: ['addresses'], }, }, { name: 'validate_address', description: 'Validate if a string is a valid Ethereum address format.', inputSchema: { type: 'object', properties: { address: { type: 'string', description: 'Address string to validate', }, }, required: ['address'], }, }, ], }; });
- src/aave-client.ts:308-339 (helper)Core helper function batchAnalyzeLiquidation that concurrently analyzes multiple addresses for liquidation opportunities by calling analyzeLiquidationOpportunity on each.async batchAnalyzeLiquidation( addresses: string[] ): Promise<BatchAnalysisResult[]> { const results: BatchAnalysisResult[] = new Array(addresses.length); const concurrency = Math.min(5, addresses.length); let currentIndex = 0; const worker = async () => { while (true) { const idx = currentIndex++; if (idx >= addresses.length) { break; } const address = addresses[idx]; try { const opportunity = await this.analyzeLiquidationOpportunity(address); results[idx] = { address, opportunity }; } catch (error) { results[idx] = { address, opportunity: null, error: error instanceof Error ? error.message : String(error), }; } } }; await Promise.all(Array.from({ length: concurrency }, () => worker())); return results; }
- src/aave-client.ts:205-296 (helper)Supporting helper function analyzeLiquidationOpportunity used by batch_check to analyze single address positions, compute health factor, risk level, and estimated liquidation profit.async analyzeLiquidationOpportunity( userAddress: string ): Promise<LiquidationOpportunity | null> { const accountData = await this.getUserAccountData(userAddress); // Only return if liquidatable or at risk if (!accountData.isLiquidatable && !accountData.isAtRisk) { return null; } const { collateral, debt } = await this.getUserReserves(userAddress); // Calculate risk level const hf = parseFloat(accountData.healthFactorFormatted); let riskLevel: 'HIGH' | 'MEDIUM' | 'LOW'; if (hf < LIQUIDATION_THRESHOLD) { riskLevel = 'HIGH'; } else if (hf < 1.02) { riskLevel = 'MEDIUM'; } else { // 1.02 <= hf < WARNING_THRESHOLD (1.05) riskLevel = 'LOW'; } // Format values in USD (base is already in USD with 8 decimals from oracle) const totalCollateralUSD = ethers.formatUnits(accountData.totalCollateralBase, 8); const totalDebtUSD = ethers.formatUnits(accountData.totalDebtBase, 8); const availableBorrowsUSD = ethers.formatUnits(accountData.availableBorrowsBase, 8); // Calculate potential profit with actual liquidation bonus let potentialProfit = '0'; if (accountData.isLiquidatable) { const debtValue = parseFloat(totalDebtUSD); // Close factor: can liquidate up to 50% of debt const maxDebtByCloseFactor = debtValue * 0.5; // Fetch prices for collateral assets to avoid overstating profit const priceMap = collateral.length ? await this.getAssetsPrices(collateral.map((c) => c.asset)) : new Map<string, string>(); let bestEstimatedProfit = 0; for (const c of collateral) { const priceStr = priceMap.get(c.asset); const price = priceStr ? parseFloat(priceStr) : 0; if (!price) { continue; } // Convert collateral balance to USD using token decimals and oracle price (8 decimals) const collateralAmount = parseFloat(ethers.formatUnits(c.currentATokenBalance, c.decimals)); const collateralValueUSD = collateralAmount * price; // liquidationBonus is in bps, e.g. 10500 => 5% bonus const bonusPercentage = Math.max(0, Number(c.liquidationBonus) - 10000) / 10000; if (bonusPercentage <= 0) { continue; } // Debt that can be covered by this collateral considering bonus const maxDebtByCollateral = collateralValueUSD / (1 + bonusPercentage); // Actual liquidatable debt for this collateral const liquidatableDebt = Math.min(maxDebtByCloseFactor, maxDebtByCollateral, debtValue); const estimatedProfit = liquidatableDebt * bonusPercentage; if (estimatedProfit > bestEstimatedProfit) { bestEstimatedProfit = estimatedProfit; } } potentialProfit = bestEstimatedProfit.toFixed(2); } return { userAddress, healthFactor: accountData.healthFactorFormatted, totalCollateralUSD, totalDebtUSD, availableBorrowsUSD, liquidationThreshold: parseFloat( ethers.formatUnits(accountData.currentLiquidationThreshold, 4) ), collateralAssets: collateral, debtAssets: debt, potentialProfit, riskLevel, gasWarning: 'Profit calculation does not include Gas costs. Actual profit will be lower.', }; }