web3agent-aof
Version:
AI Framework for Onchain Operations and DeFi Interactions
180 lines • 7.99 kB
JavaScript
import { ethers } from 'ethers';
// Protocol-specific ABIs
const UNISWAP_QUOTER_ABI = [
'function quoteExactInputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160 sqrtPriceLimitX96) external returns (uint256 amountOut)',
'function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool)'
];
const INCH_QUOTER_ABI = [
'function getRate(address fromToken, address toToken, uint256 amount) external view returns (uint256)'
];
export class SmartOrderRouter {
provider;
wallet;
quoterContracts;
constructor(provider, wallet) {
this.provider = provider;
this.wallet = wallet;
this.quoterContracts = new Map([
['Uniswap', '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6'],
['1inch', '0x1111111254fb6c44bAC0beD2854e76F90643097d'],
// Add other quoter contracts
]);
}
async findBestRoute(params) {
// Get quotes from all specified protocols
const quotes = await Promise.all(params.protocols.map(protocol => this.getQuote(protocol, params)));
// Find the best single route
const bestSingleRoute = quotes.reduce((best, current) => {
return this.compareRoutes(best, current) > 0 ? best : current;
});
// If maxSplits is specified, try to find better split routes
let bestSplitRoute;
if (params.maxSplits && params.maxSplits > 1) {
bestSplitRoute = await this.findBestSplitRoute(params);
}
const usesSplit = bestSplitRoute && this.isSplitRouteBetter(bestSplitRoute, bestSingleRoute);
const expectedAmountOut = usesSplit ? bestSplitRoute.totalAmountOut : bestSingleRoute.amountOut;
const priceImpact = usesSplit ? bestSplitRoute.averagePriceImpact : bestSingleRoute.priceImpact;
const estimatedGasUsed = usesSplit ? bestSplitRoute.totalGasEstimate : bestSingleRoute.gasEstimate;
// Calculate minimum amount out based on slippage
const minAmountOut = this.calculateMinAmountOut(expectedAmountOut, params.maxSlippage);
return {
bestSingleRoute,
bestSplitRoute,
expectedAmountOut,
minAmountOut,
estimatedGasUsed,
priceImpact
};
}
async splitOrder(params) {
return await this.findBestSplitRoute(params);
}
async getQuote(protocol, params) {
switch (protocol) {
case 'Uniswap':
return await this.getUniswapQuote(params);
case '1inch':
return await this.get1inchQuote(params);
case 'Paraswap':
return await this.getParaswapQuote(params);
default:
throw new Error(`Unsupported protocol: ${protocol}`);
}
}
async getUniswapQuote(params) {
const quoterContract = await this.getQuoterContract('Uniswap');
const amountOut = await quoterContract.quoteExactInputSingle(params.tokenIn, params.tokenOut, 3000, // 0.3% fee tier
params.amount, 0 // No price limit
);
return {
protocol: 'Uniswap',
amountOut: amountOut.toString(),
priceImpact: await this.calculatePriceImpact(params, amountOut),
path: [params.tokenIn, params.tokenOut],
gasEstimate: BigInt(180000) // Approximate gas used for Uniswap swap
};
}
async get1inchQuote(params) {
const quoterContract = await this.getQuoterContract('1inch');
const amountOut = await quoterContract.getRate(params.tokenIn, params.tokenOut, params.amount);
return {
protocol: '1inch',
amountOut: amountOut.toString(),
priceImpact: await this.calculatePriceImpact(params, amountOut),
path: [params.tokenIn, params.tokenOut], // 1inch might use more complex paths internally
gasEstimate: BigInt(200000) // Approximate gas used for 1inch swap
};
}
async getParaswapQuote(params) {
// Implementation for Paraswap quote
throw new Error('Paraswap integration not implemented yet');
}
async findBestSplitRoute(params) {
const maxSplits = params.maxSplits || 4;
const totalAmount = BigInt(params.amount);
// Try different split ratios
const splitRatios = this.generateSplitRatios(maxSplits);
const splitRoutes = await Promise.all(splitRatios.map(async (ratios) => {
const routes = await Promise.all(ratios.map(async (ratio, index) => {
const splitAmount = (totalAmount * BigInt(Math.floor(ratio * 100))) / BigInt(100);
return await this.getQuote(params.protocols[index], {
...params,
amount: splitAmount.toString()
});
}));
const totalAmountOut = routes.reduce((sum, route) => sum + BigInt(route.amountOut), BigInt(0));
const averagePriceImpact = routes.reduce((sum, route) => sum + route.priceImpact, 0) / routes.length;
const totalGasEstimate = routes.reduce((sum, route) => sum + route.gasEstimate, BigInt(0));
return {
routes,
totalAmountOut: totalAmountOut.toString(),
averagePriceImpact,
totalGasEstimate
};
}));
// Find the best split route
return splitRoutes.reduce((best, current) => {
return this.compareSplitRoutes(best, current) > 0 ? best : current;
});
}
async getQuoterContract(protocol) {
const address = this.quoterContracts.get(protocol);
if (!address) {
throw new Error(`Quoter contract not found for protocol: ${protocol}`);
}
const abi = protocol === 'Uniswap' ? UNISWAP_QUOTER_ABI : INCH_QUOTER_ABI;
return new ethers.Contract(address, abi, this.provider);
}
async calculatePriceImpact(params, amountOut) {
// Implementation of price impact calculation
return 0; // Placeholder
}
calculateMinAmountOut(amountOut, slippage) {
const amount = BigInt(amountOut);
const slippageFactor = 1 - slippage;
return ((amount * BigInt(Math.floor(slippageFactor * 10000))) / BigInt(10000)).toString();
}
compareRoutes(a, b) {
const aAmount = BigInt(a.amountOut);
const bAmount = BigInt(b.amountOut);
if (aAmount !== bAmount)
return aAmount > bAmount ? 1 : -1;
// If amounts are equal, compare gas costs
return a.gasEstimate < b.gasEstimate ? 1 : -1;
}
compareSplitRoutes(a, b) {
const aAmount = BigInt(a.totalAmountOut);
const bAmount = BigInt(b.totalAmountOut);
if (aAmount !== bAmount)
return aAmount > bAmount ? 1 : -1;
// If amounts are equal, compare gas costs
return a.totalGasEstimate < b.totalGasEstimate ? 1 : -1;
}
isSplitRouteBetter(split, single) {
const splitAmount = BigInt(split.totalAmountOut);
const singleAmount = BigInt(single.amountOut);
const amountDiff = splitAmount - singleAmount;
// Calculate the gas cost difference in terms of output token
const gasDiff = split.totalGasEstimate - single.gasEstimate;
const gasPrice = BigInt(50000000000); // 50 Gwei
const gasCostDiff = gasDiff * gasPrice;
// Split route is better if the amount improvement outweighs the gas cost
return amountDiff > gasCostDiff;
}
generateSplitRatios(maxSplits) {
const ratios = [];
// Generate different split ratios
if (maxSplits >= 2) {
ratios.push([0.5, 0.5]);
}
if (maxSplits >= 3) {
ratios.push([0.4, 0.3, 0.3]);
}
if (maxSplits >= 4) {
ratios.push([0.25, 0.25, 0.25, 0.25]);
}
return ratios;
}
}
//# sourceMappingURL=router.js.map