Documentation

V2 periphery

The ArmoryV2Router surface in full, including deadline and slippage conventions, permit variants, fee-on-transfer variants, and the native-APE wrapping paths.

On this page
ContractAddressApeScan
ArmoryV2Router0x98ca…5D7D
ArmoryV2Factory0x7AA7…50D5
WAPE0x48b6…B557

ArmoryV2Router is Uniswap's UniswapV2Router02, renamed, compiled with Solidity 0.6.6. It implements IUniswapV2Router02 unmodified, so any Router02 ABI works against it.

address public immutable factory;
address public immutable WETH;   // == WAPE on this chain
 
constructor(address _factory, address _WETH) public;
 
receive() external payable;      // asserts msg.sender == WETH

WETH means WAPE

Hardcoding an Ethereum WETH address here points every native leg at a contract that does not exist on this chain. The getter is WETH() and every native-currency function is named with ETH in it. Those names come from upstream and were not renamed. On ApeChain the wrapped asset is WAPE and the native asset is APE. Read router.WETH() rather than hardcoding.

Conventions

Deadlines. Every state-changing function except the permit wrappers carries ensure(deadline), which reverts UniswapV2Router: EXPIRED when deadline < block.timestamp. Deadlines are unix seconds. The permit variants have no ensure of their own. They pass deadline into the LP token's permit and then into the inner function, which does have it.

Slippage. Exact-input functions take amountOutMin and revert UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT. Exact-output functions take amountInMax and revert UniswapV2Router: EXCESSIVE_INPUT_AMOUNT. Liquidity functions take amountAMin / amountBMin and revert UniswapV2Router: INSUFFICIENT_A_AMOUNT or UniswapV2Router: INSUFFICIENT_B_AMOUNT.

Path. An address[] of at least 2 entries. Each adjacent pair must have a pair contract or the pair's getReserves() call reverts. Native-in paths must start with WAPE and native-out paths must end with it, both enforced by UniswapV2Router: INVALID_PATH.

Addresses are derived, not looked up. The router computes every pair address with UniswapV2Library.pairFor, using CREATE2 rather than factory.getPair. It never consults the factory mapping except inside _addLiquidity. A wrong init code hash therefore surfaces as an unrelated revert, not as a clean "pair not found".

Add liquidity

function addLiquidity(
    address tokenA, address tokenB,
    uint amountADesired, uint amountBDesired,
    uint amountAMin, uint amountBMin,
    address to, uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
 
function addLiquidityETH(
    address token,
    uint amountTokenDesired,
    uint amountTokenMin, uint amountETHMin,
    address to, uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);

_addLiquidity creates the pair if factory.getPair returns zero, then solves for the ratio. With empty reserves it takes both desired amounts verbatim (the first LP sets the price); otherwise it quotes B against A, and if that exceeds amountBDesired it re-solves the other way. Only then does it pull tokens, straight from the caller to the pair, and call pair.mint(to).

addLiquidityETH uses msg.value as amountBDesired for the WAPE side, wraps exactly amountETH, and refunds msg.value - amountETH to msg.sender. The refund goes to the caller, not to to.

Remove liquidity

function removeLiquidity(
    address tokenA, address tokenB, uint liquidity,
    uint amountAMin, uint amountBMin, address to, uint deadline
) public returns (uint amountA, uint amountB);
 
function removeLiquidityETH(
    address token, uint liquidity,
    uint amountTokenMin, uint amountETHMin, address to, uint deadline
) public returns (uint amountToken, uint amountETH);

Both transferFrom the LP token from the caller to the pair, then burn. The approval you need is on the pair (the LP token), for the router. A common integration mistake is approving the underlying tokens instead.

Permit variants

function removeLiquidityWithPermit(
    address tokenA, address tokenB, uint liquidity,
    uint amountAMin, uint amountBMin, address to, uint deadline,
    bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountA, uint amountB);
 
function removeLiquidityETHWithPermit(
    address token, uint liquidity,
    uint amountTokenMin, uint amountETHMin, address to, uint deadline,
    bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountToken, uint amountETH);

approveMax = true signs for type(uint256).max instead of exactly liquidity. The signed value must match, or the pair's ecrecover yields the wrong signer. The signature is against the pair's EIP-712 domain, name Armory V2. See V2 core.

Fee-on-transfer variants

function removeLiquidityETHSupportingFeeOnTransferTokens(
    address token, uint liquidity,
    uint amountTokenMin, uint amountETHMin, address to, uint deadline
) public returns (uint amountETH);
 
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
    address token, uint liquidity,
    uint amountTokenMin, uint amountETHMin, address to, uint deadline,
    bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountETH);

These burn to the router, then forward the router's entire token balance to to. They return only amountETH, because the token amount that actually arrives is unknowable in advance. There is no token-for-token fee-on-transfer removal variant. That is upstream's shape, not an omission here.

The swap family

Six standard variants plus three fee-on-transfer variants.

FunctionInOutReturns
swapExactTokensForTokens(amountIn, amountOutMin, path, to, deadline)exact ERC-20ERC-20uint[] amounts
swapTokensForExactTokens(amountOut, amountInMax, path, to, deadline)ERC-20exact ERC-20uint[] amounts
swapExactETHForTokens(amountOutMin, path, to, deadline) payableexact native APEERC-20uint[] amounts
swapTokensForExactETH(amountOut, amountInMax, path, to, deadline)ERC-20exact native APEuint[] amounts
swapExactTokensForETH(amountIn, amountOutMin, path, to, deadline)exact ERC-20native APEuint[] amounts
swapETHForExactTokens(amountOut, path, to, deadline) payablenative APEexact ERC-20uint[] amounts
swapExactTokensForTokensSupportingFeeOnTransferTokens(amountIn, amountOutMin, path, to, deadline)exact ERC-20ERC-20nothing
swapExactETHForTokensSupportingFeeOnTransferTokens(amountOutMin, path, to, deadline) payableexact native APEERC-20nothing
swapExactTokensForETHSupportingFeeOnTransferTokens(amountIn, amountOutMin, path, to, deadline)exact ERC-20native APEnothing

The returned amounts array is path.length long: amounts[0] is the input, amounts[amounts.length - 1] is the output, and the intermediates are the per-hop amounts.

The fee-on-transfer variants return nothing. They cannot, because the amount that lands is only knowable by measuring. They check the recipient's balance delta against amountOutMin instead. If you need the output value from a contract, measure it yourself around the call.

Native APE wrapping paths

DirectionMechanism
Native inThe router calls WAPE.deposit{value: amounts[0]}() and transfers WAPE to the first pair. path[0] must be WAPE.
Native outThe last hop's output goes to the router, which calls WAPE.withdraw(amount) then forwards native APE to to. path[path.length - 1] must be WAPE.
Exact-output native inswapETHForExactTokens wraps only amounts[0] and refunds msg.value - amounts[0] to msg.sender.

The router's receive() is assert(msg.sender == WETH), so it accepts native APE only from the WAPE contract. Do not send APE to the router.

Fee-on-transfer plus native out

swapExactTokensForETHSupportingFeeOnTransferTokens unwraps the router's whole WAPE balance and sends it to to. Any WAPE stranded on the router from a prior failed interaction goes out with it.

Quote and amount helpers

All pure or view, all thin wrappers over UniswapV2Library:

function quote(uint amountA, uint reserveA, uint reserveB) public pure returns (uint amountB);
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut);
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) public pure returns (uint amountIn);
function getAmountsOut(uint amountIn, address[] memory path) public view returns (uint[] memory amounts);
function getAmountsIn(uint amountOut, address[] memory path) public view returns (uint[] memory amounts);

quote is the fee-free ratio conversion used for liquidity sizing. getAmountOut and getAmountIn apply the 0.3% fee. getAmountsIn rounds the required input up by one wei per hop.

These are the V2 quoting surface, and unlike V3's quoter they are genuine view functions: a plain eth_call, no simulation.

getAmountsOut lies about fee-on-transfer tokens

It is pure constant-product arithmetic on reserves. A token that taxes transfers delivers less than the quote says. Quote, then use a SupportingFeeOnTransferTokens variant with a floor you set from a measured simulation, not from getAmountsOut.

Library-level reverts you will see surfaced through these: UniswapV2Library: IDENTICAL_ADDRESSES, UniswapV2Library: ZERO_ADDRESS, UniswapV2Library: INSUFFICIENT_AMOUNT, UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT, UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT, UniswapV2Library: INSUFFICIENT_LIQUIDITY, UniswapV2Library: INVALID_PATH.

A worked quote-then-swap is in integration recipes.