linea-mcp
Version:
A Model Context Protocol server for interacting with the Linea blockchain
240 lines (239 loc) • 10.7 kB
JavaScript
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}`);
}
}