Copy contract IXFIMultiHopRouter is IIXFIRouter {
using SafeERC20 for IERC20;
struct HopData {
address pool;
address tokenIn;
address tokenOut;
uint256 fee;
bytes swapData;
}
uint256 public constant MAX_HOPS = 4;
mapping(address => bool) public authorizedCallers;
event MultiHopSwap(
address indexed user,
address indexed tokenIn,
address indexed tokenOut,
uint256 amountIn,
uint256 amountOut,
uint256 hops
);
function swapExactTokensForTokens(
SwapParams calldata params
) external payable override returns (uint256 amountOut) {
require(params.deadline >= block.timestamp, "Expired");
require(params.pools.length <= MAX_HOPS, "Too many hops");
require(params.pools.length > 0, "No pools specified");
Route memory route = _decodeRoute(params);
return _executeMultiHopSwap(route, params);
}
function _executeMultiHopSwap(
Route memory route,
SwapParams calldata params
) internal returns (uint256 finalAmount) {
uint256 currentAmount = params.amountIn;
// Transfer initial tokens
if (params.tokenIn != address(0)) {
IERC20(params.tokenIn).safeTransferFrom(
msg.sender,
address(this),
params.amountIn
);
}
// Execute each hop
for (uint256 i = 0; i < route.pools.length; i++) {
currentAmount = _executeHop(
route.tokens[i],
route.tokens[i + 1],
route.pools[i],
currentAmount,
route.swapData[i],
i == route.pools.length - 1 ? params.recipient : address(this)
);
}
require(currentAmount >= params.amountOutMin, "Insufficient output");
emit MultiHopSwap(
msg.sender,
params.tokenIn,
params.tokenOut,
params.amountIn,
currentAmount,
route.pools.length
);
return currentAmount;
}
function _executeHop(
address tokenIn,
address tokenOut,
address pool,
uint256 amountIn,
bytes memory swapData,
address recipient
) internal returns (uint256 amountOut) {
string memory dexType = _getDEXType(pool);
if (keccak256(abi.encodePacked(dexType)) == keccak256("UNISWAP_V2")) {
return _executeUniswapV2Hop(tokenIn, tokenOut, pool, amountIn, recipient);
} else if (keccak256(abi.encodePacked(dexType)) == keccak256("UNISWAP_V3")) {
return _executeUniswapV3Hop(tokenIn, tokenOut, pool, amountIn, swapData, recipient);
} else if (keccak256(abi.encodePacked(dexType)) == keccak256("CURVE")) {
return _executeCurveHop(tokenIn, tokenOut, pool, amountIn, swapData, recipient);
} else {
revert("Unsupported DEX type");
}
}
function _executeUniswapV2Hop(
address tokenIn,
address tokenOut,
address pool,
uint256 amountIn,
address recipient
) internal returns (uint256 amountOut) {
IUniswapV2Pair pair = IUniswapV2Pair(pool);
(uint256 reserve0, uint256 reserve1,) = pair.getReserves();
bool token0IsInput = tokenIn < tokenOut;
(uint256 reserveIn, uint256 reserveOut) = token0IsInput
? (reserve0, reserve1)
: (reserve1, reserve0);
amountOut = _getAmountOut(amountIn, reserveIn, reserveOut);
IERC20(tokenIn).safeTransfer(pool, amountIn);
(uint256 amount0Out, uint256 amount1Out) = token0IsInput
? (uint256(0), amountOut)
: (amountOut, uint256(0));
pair.swap(amount0Out, amount1Out, recipient, "");
return amountOut;
}
function _executeUniswapV3Hop(
address tokenIn,
address tokenOut,
address pool,
uint256 amountIn,
bytes memory swapData,
address recipient
) internal returns (uint256 amountOut) {
// Decode fee from swap data
uint24 fee = abi.decode(swapData, (uint24));
IUniswapV3Pool v3Pool = IUniswapV3Pool(pool);
bool zeroForOne = tokenIn < tokenOut;
IERC20(tokenIn).safeTransfer(pool, amountIn);
(int256 amount0, int256 amount1) = v3Pool.swap(
recipient,
zeroForOne,
int256(amountIn),
zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
""
);
amountOut = uint256(-(zeroForOne ? amount1 : amount0));
return amountOut;
}
function _executeCurveHop(
address tokenIn,
address tokenOut,
address pool,
uint256 amountIn,
bytes memory swapData,
address recipient
) internal returns (uint256 amountOut) {
// Decode token indices for Curve
(int128 i, int128 j, uint256 minAmountOut) = abi.decode(swapData, (int128, int128, uint256));
IERC20(tokenIn).safeApprove(pool, amountIn);
ICurvePool curvePool = ICurvePool(pool);
uint256 balanceBefore = IERC20(tokenOut).balanceOf(recipient);
curvePool.exchange(i, j, amountIn, minAmountOut);
uint256 balanceAfter = IERC20(tokenOut).balanceOf(recipient);
amountOut = balanceAfter - balanceBefore;
return amountOut;
}
function quote(
address tokenA,
address tokenB,
uint256 amountIn
) external view override returns (uint256 amountOut, Route memory optimalRoute) {
// Find optimal multi-hop route
Route[] memory candidateRoutes = _findCandidateRoutes(tokenA, tokenB, amountIn);
uint256 bestOutput = 0;
uint256 bestRouteIndex = 0;
for (uint256 i = 0; i < candidateRoutes.length; i++) {
try this.getAmountsOut(amountIn, candidateRoutes[i]) returns (uint256[] memory amounts) {
uint256 output = amounts[amounts.length - 1];
if (output > bestOutput) {
bestOutput = output;
bestRouteIndex = i;
}
} catch {
// Skip failed routes
}
}
require(bestOutput > 0, "No viable route found");
return (bestOutput, candidateRoutes[bestRouteIndex]);
}
function _findCandidateRoutes(
address tokenA,
address tokenB,
uint256 amountIn
) internal view returns (Route[] memory routes) {
// Implementation would use graph algorithms to find optimal paths
// This is simplified for demonstration
address[] memory intermediateTokens = _getPopularIntermediateTokens();
routes = new Route[](intermediateTokens.length + 1);
// Direct route
routes[0] = _createDirectRoute(tokenA, tokenB);
// Routes through intermediate tokens
for (uint256 i = 0; i < intermediateTokens.length; i++) {
routes[i + 1] = _create2HopRoute(tokenA, tokenB, intermediateTokens[i]);
}
return routes;
}
function getRouterType() external pure override returns (string memory) {
return "MULTI_HOP";
}
}