UNPKG

linea-mcp

Version:

A Model Context Protocol server for interacting with the Linea blockchain

240 lines (239 loc) 10.7 kB
import { ethers } from 'ethers'; import BlockchainService from '../../services/blockchain.js'; import KeyManagementService from '../../services/keyManagement.js'; // Simplified DEX Router ABI (for demonstration purposes) const DEX_ROUTER_ABI = [ // Swap functions 'function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) returns (uint[] memory amounts)', 'function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) payable returns (uint[] memory amounts)', 'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) returns (uint[] memory amounts)', // Quote functions 'function getAmountsOut(uint amountIn, address[] calldata path) view returns (uint[] memory amounts)', // Factory functions 'function factory() view returns (address)', ]; // Simplified Factory ABI const FACTORY_ABI = [ 'function getPair(address tokenA, address tokenB) view returns (address pair)', 'function allPairs(uint) view returns (address pair)', 'function allPairsLength() view returns (uint)', ]; // Simplified Pair ABI const PAIR_ABI = [ 'function token0() view returns (address)', 'function token1() view returns (address)', 'function getReserves() view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)', 'function totalSupply() view returns (uint)', ]; // ERC20 ABI for token information const ERC20_ABI = [ 'function decimals() view returns (uint8)', 'function symbol() view returns (string)', 'function name() view returns (string)', 'function balanceOf(address owner) view returns (uint256)', 'function approve(address spender, uint256 amount) returns (bool)', ]; // Common DEX addresses on Linea const DEX_ADDRESSES = { // These are placeholder addresses - in a real implementation, you would use actual DEX addresses ROUTER: '0x1111111111111111111111111111111111111111', FACTORY: '0x2222222222222222222222222222222222222222', WETH: '0x3333333333333333333333333333333333333333', }; /** * Swap tokens on a DEX * @param params The parameters for swapping tokens * @returns The transaction details */ export async function swapTokens(params) { try { const { fromToken, toToken, amount, slippageTolerance } = params; // Initialize services const blockchain = new BlockchainService('mainnet'); const keyService = new KeyManagementService(); // DeFi code, update to use default wallet const wallet = keyService.getDefaultWallet(); const connectedWallet = wallet.connect(blockchain.provider); // Create router contract instance with signer const routerContract = new ethers.Contract(DEX_ADDRESSES.ROUTER, DEX_ROUTER_ABI, connectedWallet); // Determine if we're dealing with ETH or tokens const isFromETH = fromToken.toLowerCase() === 'eth'; const isToETH = toToken.toLowerCase() === 'eth'; // Set up the path for the swap const path = []; if (isFromETH) { path.push(DEX_ADDRESSES.WETH); } else { path.push(fromToken); } if (isToETH) { path.push(DEX_ADDRESSES.WETH); } else { path.push(toToken); } // Parse amount const parsedAmount = ethers.utils.parseEther(amount); // Get expected output amount const amounts = await routerContract.getAmountsOut(parsedAmount, path); const expectedOutput = amounts[amounts.length - 1]; // Calculate minimum output with slippage tolerance const slippageFactor = 1 - (slippageTolerance / 100); const minOutput = expectedOutput.mul(Math.floor(slippageFactor * 1000)).div(1000); // Set deadline to 20 minutes from now const deadline = Math.floor(Date.now() / 1000) + 1200; let tx; if (isFromETH) { // Swap ETH for tokens tx = await routerContract.swapExactETHForTokens(minOutput, path, wallet.address, deadline, { value: parsedAmount }); } else if (isToETH) { // For token to ETH swap, we need to approve the router first const tokenContract = new ethers.Contract(fromToken, ERC20_ABI, connectedWallet); // Approve the router to spend tokens const approveTx = await tokenContract.approve(DEX_ADDRESSES.ROUTER, parsedAmount); await approveTx.wait(); // Swap tokens for ETH tx = await routerContract.swapExactTokensForETH(parsedAmount, minOutput, path, wallet.address, deadline); } else { // For token to token swap, we need to approve the router first const tokenContract = new ethers.Contract(fromToken, ERC20_ABI, connectedWallet); // Approve the router to spend tokens const approveTx = await tokenContract.approve(DEX_ADDRESSES.ROUTER, parsedAmount); await approveTx.wait(); // Swap tokens for tokens tx = await routerContract.swapExactTokensForTokens(parsedAmount, minOutput, path, wallet.address, deadline); } // Wait for transaction confirmation await tx.wait(); return { success: true, transactionHash: tx.hash, fromToken: isFromETH ? 'ETH' : fromToken, toToken: isToETH ? 'ETH' : toToken, amountIn: amount, expectedAmountOut: ethers.utils.formatEther(expectedOutput), minAmountOut: ethers.utils.formatEther(minOutput), slippageTolerance, from: wallet.address, }; } catch (error) { console.error('Error in swapTokens:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; throw new Error(`Failed to swap tokens: ${errorMessage}`); } } /** * Get information about liquidity pools * @param params The parameters for getting liquidity pool information * @returns The liquidity pool information */ export async function liquidityPools(params) { try { const { poolAddress, tokenA, tokenB } = params; const blockchain = new BlockchainService('mainnet'); // Create factory contract instance const factoryContract = blockchain.createContract(DEX_ADDRESSES.FACTORY, FACTORY_ABI); // If a specific pool is requested if (poolAddress) { return await getPoolInfo(poolAddress, blockchain); } // If a token pair is specified if (tokenA && tokenB) { const pairAddress = await factoryContract.getPair(tokenA, tokenB); if (pairAddress === ethers.constants.AddressZero) { return { success: true, pools: [], message: 'No liquidity pool found for the specified token pair', }; } const poolInfo = await getPoolInfo(pairAddress, blockchain); return poolInfo; } // If no specific pool or token pair is requested, return a list of pools // In a real implementation, you would query an indexer or limit the number of pools // For this example, we'll return a placeholder response return { success: true, pools: [], message: 'To list all pools, you would need to integrate with a DEX indexer service', }; } catch (error) { console.error('Error in liquidityPools:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; throw new Error(`Failed to get liquidity pool information: ${errorMessage}`); } } /** * Get information about a specific liquidity pool * @param poolAddress The address of the pool * @param blockchain The blockchain service instance * @returns The pool information */ async function getPoolInfo(poolAddress, blockchain) { try { // Create pair contract instance const pairContract = blockchain.createContract(poolAddress, PAIR_ABI); // Get token addresses const [token0Address, token1Address] = await Promise.all([ pairContract.token0(), pairContract.token1(), ]); // Create token contract instances const token0Contract = blockchain.createContract(token0Address, ERC20_ABI); const token1Contract = blockchain.createContract(token1Address, ERC20_ABI); // Get token details const [token0Symbol, token0Name, token0Decimals, token1Symbol, token1Name, token1Decimals, reserves, totalSupply,] = await Promise.all([ token0Contract.symbol(), token0Contract.name(), token0Contract.decimals(), token1Contract.symbol(), token1Contract.name(), token1Contract.decimals(), pairContract.getReserves(), pairContract.totalSupply(), ]); // Format reserves based on token decimals const reserve0 = ethers.utils.formatUnits(reserves[0], token0Decimals); const reserve1 = ethers.utils.formatUnits(reserves[1], token1Decimals); // Calculate price ratios const price0In1 = parseFloat(reserve1) / parseFloat(reserve0); const price1In0 = parseFloat(reserve0) / parseFloat(reserve1); return { success: true, pool: { address: poolAddress, token0: { address: token0Address, symbol: token0Symbol, name: token0Name, decimals: token0Decimals, reserve: reserve0, }, token1: { address: token1Address, symbol: token1Symbol, name: token1Name, decimals: token1Decimals, reserve: reserve1, }, totalSupply: ethers.utils.formatEther(totalSupply), prices: { [`${token0Symbol}In${token1Symbol}`]: price0In1, [`${token1Symbol}In${token0Symbol}`]: price1In0, }, lastUpdatedTimestamp: reserves[2], }, }; } catch (error) { console.error('Error in getPoolInfo:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; throw new Error(`Failed to get pool information: ${errorMessage}`); } }