// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /** * @title AereSwapRouter * @notice Periphery router for AereSwap constant-product AMM (factory + pairs * already deployed). ABI-compatible with the canonical x*y=k router * interface so existing wallets, aggregators, and SDKs work against * AERE without code changes. * Adds: * - addLiquidity (with deadline + slippage min-amounts) * - addLiquidityAERE (native AERE auto-wraps to WAERE) * - removeLiquidity / removeLiquidityAERE * - swapExactTokensForTokens * - swapExactAEREForTokens / swapExactTokensForAERE (auto-wrap/unwrap) * - quote / getAmountOut / getAmountIn / getAmountsOut / getAmountsIn helpers * * Designed to plug into the already-deployed AereSwapFactory * at 0xf0a8df7BDc25721892475B21271e52D77B0e84DC. */ interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); function balanceOf(address account) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); } interface IWAERE { function deposit() external payable; function withdraw(uint256) external; function transfer(address to, uint256 amount) external returns (bool); function balanceOf(address) external view returns (uint256); } interface IAereSwapFactory { function getPair(address tokenA, address tokenB) external view returns (address pair); function createPair(address tokenA, address tokenB) external returns (address pair); function WAERE() external view returns (address); } interface IAereSwapPair { function token0() external view returns (address); function token1() external view returns (address); function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 ts); function mint(address to) external returns (uint256 liquidity); function burn(address to) external returns (uint256 amount0, uint256 amount1); function swap(uint256 amount0Out, uint256 amount1Out, address to) external; function totalSupply() external view returns (uint256); function balanceOf(address) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); } contract AereSwapRouter { address public immutable factory; address public immutable WAERE; modifier ensure(uint256 deadline) { require(block.timestamp <= deadline, "AereRouter: EXPIRED"); _; } constructor(address _factory, address _WAERE) { factory = _factory; WAERE = _WAERE; } // Allow this contract to receive native AERE from WAERE.withdraw(). receive() external payable { require(msg.sender == WAERE, "AereRouter: only WAERE"); } // ═════════════════════════════════════════════════════════════════════ // LIQUIDITY // ═════════════════════════════════════════════════════════════════════ function _addLiquidity( address tokenA, address tokenB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin ) internal returns (uint256 amountA, uint256 amountB) { if (IAereSwapFactory(factory).getPair(tokenA, tokenB) == address(0)) { IAereSwapFactory(factory).createPair(tokenA, tokenB); } (uint256 rA, uint256 rB) = getReserves(factory, tokenA, tokenB); if (rA == 0 && rB == 0) { (amountA, amountB) = (amountADesired, amountBDesired); } else { uint256 amountBOptimal = quote(amountADesired, rA, rB); if (amountBOptimal <= amountBDesired) { require(amountBOptimal >= amountBMin, "AereRouter: INSUFFICIENT_B_AMOUNT"); (amountA, amountB) = (amountADesired, amountBOptimal); } else { uint256 amountAOptimal = quote(amountBDesired, rB, rA); assert(amountAOptimal <= amountADesired); require(amountAOptimal >= amountAMin, "AereRouter: 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 = pairFor(factory, tokenA, tokenB); _safeTransferFrom(tokenA, msg.sender, pair, amountA); _safeTransferFrom(tokenB, msg.sender, pair, amountB); liquidity = IAereSwapPair(pair).mint(to); } function addLiquidityAERE( address token, uint256 amountTokenDesired, uint256 amountTokenMin, uint256 amountAEREMin, address to, uint256 deadline ) external payable ensure(deadline) returns (uint256 amountToken, uint256 amountAERE, uint256 liquidity) { (amountToken, amountAERE) = _addLiquidity( token, WAERE, amountTokenDesired, msg.value, amountTokenMin, amountAEREMin ); address pair = pairFor(factory, token, WAERE); _safeTransferFrom(token, msg.sender, pair, amountToken); IWAERE(WAERE).deposit{ value: amountAERE }(); require(IWAERE(WAERE).transfer(pair, amountAERE), "AereRouter: WAERE_TRANSFER_FAIL"); liquidity = IAereSwapPair(pair).mint(to); // refund any dust if (msg.value > amountAERE) _safeTransferAERE(msg.sender, msg.value - amountAERE); } 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 = pairFor(factory, tokenA, tokenB); require(IAereSwapPair(pair).transferFrom(msg.sender, pair, liquidity), "AereRouter: LP_TRANSFER_FAIL"); (uint256 amount0, uint256 amount1) = IAereSwapPair(pair).burn(to); (address t0, ) = sortTokens(tokenA, tokenB); (amountA, amountB) = tokenA == t0 ? (amount0, amount1) : (amount1, amount0); require(amountA >= amountAMin, "AereRouter: INSUFFICIENT_A_AMOUNT"); require(amountB >= amountBMin, "AereRouter: INSUFFICIENT_B_AMOUNT"); } function removeLiquidityAERE( address token, uint256 liquidity, uint256 amountTokenMin, uint256 amountAEREMin, address to, uint256 deadline ) external ensure(deadline) returns (uint256 amountToken, uint256 amountAERE) { (amountToken, amountAERE) = removeLiquidity( token, WAERE, liquidity, amountTokenMin, amountAEREMin, address(this), deadline ); _safeTransfer(token, to, amountToken); IWAERE(WAERE).withdraw(amountAERE); _safeTransferAERE(to, amountAERE); } // ═════════════════════════════════════════════════════════════════════ // SWAPS // ═════════════════════════════════════════════════════════════════════ function _swap(uint256[] memory amounts, address[] memory path, address _to) internal { for (uint256 i = 0; i < path.length - 1; i++) { (address input, address output) = (path[i], path[i + 1]); (address t0, ) = sortTokens(input, output); uint256 amountOut = amounts[i + 1]; (uint256 amount0Out, uint256 amount1Out) = input == t0 ? (uint256(0), amountOut) : (amountOut, uint256(0)); address to = i < path.length - 2 ? pairFor(factory, output, path[i + 2]) : _to; IAereSwapPair(pairFor(factory, input, output)).swap(amount0Out, amount1Out, to); } } function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external ensure(deadline) returns (uint256[] memory amounts) { amounts = getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, "AereRouter: INSUFFICIENT_OUTPUT_AMOUNT"); _safeTransferFrom(path[0], msg.sender, pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to); } function swapExactAEREForTokens( uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external payable ensure(deadline) returns (uint256[] memory amounts) { require(path[0] == WAERE, "AereRouter: INVALID_PATH"); amounts = getAmountsOut(factory, msg.value, path); require(amounts[amounts.length - 1] >= amountOutMin, "AereRouter: INSUFFICIENT_OUTPUT_AMOUNT"); IWAERE(WAERE).deposit{ value: amounts[0] }(); require(IWAERE(WAERE).transfer(pairFor(factory, path[0], path[1]), amounts[0]), "AereRouter: WAERE_FAIL"); _swap(amounts, path, to); } function swapExactTokensForAERE( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external ensure(deadline) returns (uint256[] memory amounts) { require(path[path.length - 1] == WAERE, "AereRouter: INVALID_PATH"); amounts = getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, "AereRouter: INSUFFICIENT_OUTPUT_AMOUNT"); _safeTransferFrom(path[0], msg.sender, pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, address(this)); IWAERE(WAERE).withdraw(amounts[amounts.length - 1]); _safeTransferAERE(to, amounts[amounts.length - 1]); } // ═════════════════════════════════════════════════════════════════════ // LIBRARY (constant-product AMM math, inlined) // ═════════════════════════════════════════════════════════════════════ function sortTokens(address tokenA, address tokenB) public pure returns (address token0, address token1) { require(tokenA != tokenB, "AereRouter: IDENTICAL_ADDRESSES"); (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); require(token0 != address(0), "AereRouter: ZERO_ADDRESS"); } function pairFor(address _factory, address tokenA, address tokenB) public view returns (address pair) { pair = IAereSwapFactory(_factory).getPair(tokenA, tokenB); require(pair != address(0), "AereRouter: PAIR_NOT_EXISTS"); } function getReserves(address _factory, address tokenA, address tokenB) public view returns (uint256 reserveA, uint256 reserveB) { (address t0, ) = sortTokens(tokenA, tokenB); (uint112 r0, uint112 r1, ) = IAereSwapPair(pairFor(_factory, tokenA, tokenB)).getReserves(); (reserveA, reserveB) = tokenA == t0 ? (uint256(r0), uint256(r1)) : (uint256(r1), uint256(r0)); } function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) public pure returns (uint256 amountB) { require(amountA > 0, "AereRouter: INSUFFICIENT_AMOUNT"); require(reserveA > 0 && reserveB > 0, "AereRouter: INSUFFICIENT_LIQUIDITY"); amountB = (amountA * reserveB) / reserveA; } // 0.3% fee — matches AereSwapPair.swap() math (1000-3 = 997 numerator). function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) public pure returns (uint256 amountOut) { require(amountIn > 0, "AereRouter: INSUFFICIENT_INPUT"); require(reserveIn > 0 && reserveOut > 0, "AereRouter: INSUFFICIENT_LIQUIDITY"); uint256 amountInWithFee = amountIn * 997; uint256 numerator = amountInWithFee * reserveOut; uint256 denominator = reserveIn * 1000 + amountInWithFee; amountOut = numerator / denominator; } function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) public pure returns (uint256 amountIn) { require(amountOut > 0, "AereRouter: INSUFFICIENT_OUTPUT"); require(reserveIn > 0 && reserveOut > 0, "AereRouter: INSUFFICIENT_LIQUIDITY"); uint256 numerator = reserveIn * amountOut * 1000; uint256 denominator = (reserveOut - amountOut) * 997; amountIn = (numerator / denominator) + 1; } function getAmountsOut(address _factory, uint256 amountIn, address[] memory path) public view returns (uint256[] memory amounts) { require(path.length >= 2, "AereRouter: INVALID_PATH"); amounts = new uint256[](path.length); amounts[0] = amountIn; for (uint256 i = 0; i < path.length - 1; i++) { (uint256 rI, uint256 rO) = getReserves(_factory, path[i], path[i + 1]); amounts[i + 1] = getAmountOut(amounts[i], rI, rO); } } function getAmountsIn(address _factory, uint256 amountOut, address[] memory path) public view returns (uint256[] memory amounts) { require(path.length >= 2, "AereRouter: INVALID_PATH"); amounts = new uint256[](path.length); amounts[amounts.length - 1] = amountOut; for (uint256 i = path.length - 1; i > 0; i--) { (uint256 rI, uint256 rO) = getReserves(_factory, path[i - 1], path[i]); amounts[i - 1] = getAmountIn(amounts[i], rI, rO); } } // ═════════════════════════════════════════════════════════════════════ // SAFE TRANSFERS // ═════════════════════════════════════════════════════════════════════ function _safeTransfer(address token, address to, uint256 value) private { (bool ok, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value)); require(ok && (data.length == 0 || abi.decode(data, (bool))), "AereRouter: TRANSFER_FAILED"); } function _safeTransferFrom(address token, address from, address to, uint256 value) private { (bool ok, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value)); require(ok && (data.length == 0 || abi.decode(data, (bool))), "AereRouter: TRANSFER_FROM_FAILED"); } function _safeTransferAERE(address to, uint256 value) private { (bool ok, ) = to.call{ value: value }(""); require(ok, "AereRouter: AERE_TRANSFER_FAILED"); } }