feat(router): add constant-product swap router

This commit is contained in:
dualfroz committed 2026-08-26 17:12:00 +02:00
1 parent 3c6d80aabb
commit e04164cd24
1 file changed
+158
+158
View File
@@ -0,0 +1,158 @@
// 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);
}
}