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
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 == WETHWETH 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.
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
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.