aere-contracts/contracts/AereSwapRouter.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
2026-07-20 01:02:37 +03:00

333 lines
16 KiB
Solidity

// 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");
}
}