// SPDX-License-Identifier: MIT pragma solidity 0.8.36; import {IFluxFactory} from "./interfaces/IFluxFactory.sol"; import {IFluxPair} from "./interfaces/IFluxPair.sol"; import {IErc20} from "./interfaces/IErc20.sol"; import {FluxLibrary} from "./libraries/FluxLibrary.sol"; /// @title User-facing entrypoint for liquidity and multi-hop swaps. contract FluxRouter { address public immutable factory; modifier ensure(uint256 deadline) { require(deadline >= block.timestamp, "FluxRouter: EXPIRED"); _; } constructor(address factoryAddress) { factory = factoryAddress; } function _safeTransferFrom(address token, address from, address to, uint256 value) private { (bool success, bytes memory returndata) = token.call(abi.encodeWithSelector(IErc20.transferFrom.selector, from, to, value)); require( success && (returndata.length == 0 || abi.decode(returndata, (bool))), "FluxRouter: TRANSFER_FROM_FAILED" ); } function _addLiquidity( address tokenA, address tokenB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin ) private returns (uint256 amountA, uint256 amountB) { if (IFluxFactory(factory).getPair(tokenA, tokenB) == address(0)) { IFluxFactory(factory).createPair(tokenA, tokenB); } (uint256 reserveA, uint256 reserveB) = FluxLibrary.getReserves(factory, tokenA, tokenB); if (reserveA == 0 && reserveB == 0) { (amountA, amountB) = (amountADesired, amountBDesired); } else { uint256 amountBOptimal = FluxLibrary.quote(amountADesired, reserveA, reserveB); if (amountBOptimal <= amountBDesired) { require(amountBOptimal >= amountBMin, "FluxRouter: INSUFFICIENT_B_AMOUNT"); (amountA, amountB) = (amountADesired, amountBOptimal); } else { uint256 amountAOptimal = FluxLibrary.quote(amountBDesired, reserveB, reserveA); assert(amountAOptimal <= amountADesired); require(amountAOptimal >= amountAMin, "FluxRouter: INSUFFICIENT_A_AMOUNT"); (amountA, amountB) = (amountAOptimal, amountBDesired); } } } function addLiquidity( address tokenA, address tokenB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ) external ensure(deadline) returns (uint256 amountA, uint256 amountB, uint256 liquidity) { (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin); address pair = FluxLibrary.pairFor(factory, tokenA, tokenB); _safeTransferFrom(tokenA, msg.sender, pair, amountA); _safeTransferFrom(tokenB, msg.sender, pair, amountB); liquidity = IFluxPair(pair).mint(to); } function removeLiquidity( address tokenA, address tokenB, uint256 liquidity, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ) public ensure(deadline) returns (uint256 amountA, uint256 amountB) { address pair = FluxLibrary.pairFor(factory, tokenA, tokenB); IFluxPair(pair).transferFrom(msg.sender, pair, liquidity); (uint256 amount0, uint256 amount1) = IFluxPair(pair).burn(to); (address token0,) = FluxLibrary.sortTokens(tokenA, tokenB); (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0); require(amountA >= amountAMin, "FluxRouter: INSUFFICIENT_A_AMOUNT"); require(amountB >= amountBMin, "FluxRouter: INSUFFICIENT_B_AMOUNT"); } function _swap(uint256[] memory amounts, address[] memory path, address to) private { for (uint256 i; i < path.length - 1; i++) { (address input, address output) = (path[i], path[i + 1]); (address token0,) = FluxLibrary.sortTokens(input, output); uint256 amountOut = amounts[i + 1]; (uint256 amount0Out, uint256 amount1Out) = input == token0 ? (uint256(0), amountOut) : (amountOut, uint256(0)); address recipient = i < path.length - 2 ? FluxLibrary.pairFor(factory, output, path[i + 2]) : to; IFluxPair(FluxLibrary.pairFor(factory, input, output)).swap(amount0Out, amount1Out, recipient, new bytes(0)); } } function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external ensure(deadline) returns (uint256[] memory amounts) { amounts = FluxLibrary.getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, "FluxRouter: INSUFFICIENT_OUTPUT_AMOUNT"); _safeTransferFrom(path[0], msg.sender, FluxLibrary.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to); } function swapTokensForExactTokens( uint256 amountOut, uint256 amountInMax, address[] calldata path, address to, uint256 deadline ) external ensure(deadline) returns (uint256[] memory amounts) { amounts = FluxLibrary.getAmountsIn(factory, amountOut, path); require(amounts[0] <= amountInMax, "FluxRouter: EXCESSIVE_INPUT_AMOUNT"); _safeTransferFrom(path[0], msg.sender, FluxLibrary.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to); } function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) external pure returns (uint256 amountB) { return FluxLibrary.quote(amountA, reserveA, reserveB); } function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) external pure returns (uint256 amountOut) { return FluxLibrary.getAmountOut(amountIn, reserveIn, reserveOut); } function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) external pure returns (uint256 amountIn) { return FluxLibrary.getAmountIn(amountOut, reserveIn, reserveOut); } function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts) { return FluxLibrary.getAmountsOut(factory, amountIn, path); } function getAmountsIn(uint256 amountOut, address[] calldata path) external view returns (uint256[] memory amounts) { return FluxLibrary.getAmountsIn(factory, amountOut, path); } }