JAMMRouter Contract
Overview
JAMMRouter is the user interface layer contract of JAMM DEX, providing user-friendly trading and liquidity management functions. It encapsulates complex underlying logic, supports multi-hop swaps, automatic path calculation, deadline protection, and is deeply integrated with WJU, enabling users to trade directly using JU tokens.
Contract Basic Information
contract JAMMRouter is IJAMMRouter02 {
address public immutable override factory;
address public immutable override WJU;
constructor(address _factory, address _WJU) {
factory = _factory;
WJU = _WJU;
}
}
Immutable Variables:
factory
: JAMMFactory contract addressWJU
: WJU wrapped token contract address
Modifiers
Deadline Protection
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, "JAMMRouter: EXPIRED");
_;
}
All trading functions use this modifier to ensure transactions execute within the specified time.
ETH Reception
receive() external payable {
assert(msg.sender == WJU); // Only accept ETH from WJU contract
}
Router only accepts ETH from WJU contract, used for ETH transfers during WJU unwrapping.
Liquidity Management
Internal Liquidity Calculation
function _addLiquidity(
address tokenA,
address tokenB,
uint24 fee,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal virtual returns (uint amountA, uint amountB) {
// Create pair if it doesn't exist
if (IJAMMFactory(factory).getPair(tokenA, tokenB, fee) == address(0)) {
IJAMMFactory(factory).createPair(tokenA, tokenB, fee);
}
(uint reserveA, uint reserveB) = JAMMLibrary.getReserves(
factory,
tokenA,
tokenB,
fee
);
if (reserveA == 0 && reserveB == 0) {
// First liquidity addition
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
// Calculate optimal amounts
uint amountBOptimal = JAMMLibrary.quote(
amountADesired,
reserveA,
reserveB
);
if (amountBOptimal <= amountBDesired) {
require(
amountBOptimal >= amountBMin,
"JAMMRouter: INSUFFICIENT_B_AMOUNT"
);
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint amountAOptimal = JAMMLibrary.quote(
amountBDesired,
reserveB,
reserveA
);
assert(amountAOptimal <= amountADesired);
require(
amountAOptimal >= amountAMin,
"JAMMRouter: INSUFFICIENT_A_AMOUNT"
);
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
Calculation Logic:
Check if trading pair exists, create if not
Get current reserves
If first addition, use desired amounts
Otherwise calculate optimal ratio, ensuring minimum amount requirements
Add Liquidity
function addLiquidity(
address tokenA,
address tokenB,
uint24 fee,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
)
external
virtual
override
ensure(deadline)
returns (uint amountA, uint amountB, uint liquidity)
{
(amountA, amountB) = _addLiquidity(
tokenA,
tokenB,
fee,
amountADesired,
amountBDesired,
amountAMin,
amountBMin
);
address pair = JAMMLibrary.pairFor(factory, tokenA, tokenB, fee);
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = IJAMMPair(pair).mint(to);
}
Add JU Liquidity
function addLiquidityETH(
address token,
uint24 fee,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
)
external
payable
virtual
override
ensure(deadline)
returns (uint amountToken, uint amountETH, uint liquidity)
{
(amountToken, amountETH) = _addLiquidity(
token,
WJU,
fee,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = JAMMLibrary.pairFor(factory, token, WJU, fee);
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
IWJU(WJU).deposit{value: amountETH}();
assert(IWJU(WJU).transfer(pair, amountETH));
liquidity = IJAMMPair(pair).mint(to);
// Refund excess JU
if (msg.value > amountETH)
TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}
JU Processing Flow:
Calculate optimal amounts
Transfer tokens to trading pair
Wrap JU to WJU and transfer to trading pair
Mint LP tokens
Refund excess JU
Remove Liquidity
function removeLiquidity(
address tokenA,
address tokenB,
uint24 fee,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
)
public
virtual
override
ensure(deadline)
returns (uint amountA, uint amountB)
{
address pair = JAMMLibrary.pairFor(factory, tokenA, tokenB, fee);
IJAMMPair(pair).transferFrom(msg.sender, pair, liquidity); // Send LP tokens to pair
(uint amount0, uint amount1) = IJAMMPair(pair).burn(to);
(address token0, ) = JAMMLibrary.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0
? (amount0, amount1)
: (amount1, amount0);
require(amountA >= amountAMin, "JAMMRouter: INSUFFICIENT_A_AMOUNT");
require(amountB >= amountBMin, "JAMMRouter: INSUFFICIENT_B_AMOUNT");
}
Remove JU Liquidity
function removeLiquidityETH(
address token,
uint24 fee,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
)
public
virtual
override
ensure(deadline)
returns (uint amountToken, uint amountETH)
{
(amountToken, amountETH) = removeLiquidity(
token,
WJU,
fee,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, amountToken);
IWJU(WJU).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
Permit Support
Remove Liquidity with Permit
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint24 fee,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external virtual override returns (uint amountA, uint amountB) {
IJAMMPair(JAMMLibrary.pairFor(factory, tokenA, tokenB, fee)).permit(
msg.sender,
address(this),
approveMax ? type(uint).max : liquidity,
deadline,
v,
r,
s
);
(amountA, amountB) = removeLiquidity(
tokenA,
tokenB,
fee,
liquidity,
amountAMin,
amountBMin,
to,
deadline
);
}
Permit Advantages:
No need to pre-call approve
Complete authorization and operation in one transaction
Reduce gas consumption and transaction steps
Token Swapping
Internal Swap Logic
function _swap(
uint[] memory amounts,
address[] memory path,
uint24[] memory fees,
address _to,
address _referrer
) internal virtual {
require(path.length - fees.length == 1, "JAMMRouter: INVALID_PATH");
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0, ) = JAMMLibrary.sortTokens(input, output);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = input == token0
? (uint(0), amountOut)
: (amountOut, uint(0));
address to = i < path.length - 2
? JAMMLibrary.pairFor(factory, output, path[i + 2], fees[i + 1])
: _to;
IJAMMPair(JAMMLibrary.pairFor(factory, input, output, fees[i]))
.swap(amount0Out, amount1Out, to, new bytes(0), _referrer);
}
}
Multi-hop Swap Logic:
Validate relationship between path and fee array lengths
Iterate through swap path
Calculate output amount for each hop
Determine recipient address for next hop
Call trading pair's swap function
Exact Input Swap
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
uint24[] calldata fees,
address to,
address referrer,
uint deadline
)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
amounts = JAMMLibrary.getAmountsOut(factory, amountIn, path, fees);
require(
amounts[amounts.length - 1] >= amountOutMin,
"JAMMRouter: INSUFFICIENT_OUTPUT_AMOUNT"
);
TransferHelper.safeTransferFrom(
path[0],
msg.sender,
JAMMLibrary.pairFor(factory, path[0], path[1], fees[0]),
amounts[0]
);
_swap(amounts, path, fees, to, referrer);
}
Exact Output Swap
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
uint24[] calldata fees,
address to,
address referrer,
uint deadline
)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
amounts = JAMMLibrary.getAmountsIn(factory, amountOut, path, fees);
require(
amounts[0] <= amountInMax,
"JAMMRouter: EXCESSIVE_INPUT_AMOUNT"
);
TransferHelper.safeTransferFrom(
path[0],
msg.sender,
JAMMLibrary.pairFor(factory, path[0], path[1], fees[0]),
amounts[0]
);
_swap(amounts, path, fees, to, referrer);
}
JU Token Swapping
Buy Tokens with JU
function swapExactETHForTokens(
uint amountOutMin,
address[] calldata path,
uint24[] calldata fees,
address to,
address referrer,
uint deadline
)
external
payable
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WJU, "JAMMRouter: INVALID_PATH");
amounts = JAMMLibrary.getAmountsOut(factory, msg.value, path, fees);
require(
amounts[amounts.length - 1] >= amountOutMin,
"JAMMRouter: INSUFFICIENT_OUTPUT_AMOUNT"
);
IWJU(WJU).deposit{value: amounts[0]}();
assert(
IWJU(WJU).transfer(
JAMMLibrary.pairFor(factory, path[0], path[1], fees[0]),
amounts[0]
)
);
_swap(amounts, path, fees, to, referrer);
}
Sell Tokens for JU
function swapExactTokensForETH(
uint amountIn,
uint amountOutMin,
address[] calldata path,
uint24[] calldata fees,
address to,
address referrer,
uint deadline
)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WJU, "JAMMRouter: INVALID_PATH");
amounts = JAMMLibrary.getAmountsOut(factory, amountIn, path, fees);
require(
amounts[amounts.length - 1] >= amountOutMin,
"JAMMRouter: INSUFFICIENT_OUTPUT_AMOUNT"
);
TransferHelper.safeTransferFrom(
path[0],
msg.sender,
JAMMLibrary.pairFor(factory, path[0], path[1], fees[0]),
amounts[0]
);
_swap(amounts, path, fees, address(this), referrer);
IWJU(WJU).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
Fee-on-Transfer Token Support
Internal Swap Logic (Fee-on-Transfer Support)
function _swapSupportingFeeOnTransferTokens(
address[] memory path,
uint24[] memory fees,
address _to,
address _referrer
) internal virtual {
for (uint i; i < path.length - 1; i++) {
SwapData memory data;
(data.input, data.output) = (path[i], path[i + 1]);
(data.token0, ) = JAMMLibrary.sortTokens(data.input, data.output);
IJAMMPair pair = IJAMMPair(
JAMMLibrary.pairFor(factory, data.input, data.output, fees[i])
);
(uint reserve0, uint reserve1, ) = pair.getReserves();
(uint reserveInput, uint reserveOutput) = data.input == data.token0
? (reserve0, reserve1)
: (reserve1, reserve0);
data.amountInput =
IERC20(data.input).balanceOf(address(pair)) -
reserveInput;
data.amountOutput = JAMMLibrary.getAmountOut(
data.amountInput,
reserveInput,
reserveOutput,
fees[i]
);
(data.amount0Out, data.amount1Out) = data.input == data.token0
? (uint(0), data.amountOutput)
: (data.amountOutput, uint(0));
address to = i < path.length - 2
? JAMMLibrary.pairFor(
factory,
data.output,
path[i + 2],
fees[i + 1]
)
: _to;
pair.swap(
data.amount0Out,
data.amount1Out,
to,
new bytes(0),
_referrer
);
}
}
Fee-on-Transfer Token Handling:
Don't pre-calculate amounts, base on actual received amounts
Recalculate input amount for each hop
Suitable for tokens that charge fees on transfer
Fee-on-Transfer Swap Functions
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
uint24[] calldata fees,
address to,
address referrer,
uint deadline
) external virtual override ensure(deadline) {
TransferHelper.safeTransferFrom(
path[0],
msg.sender,
JAMMLibrary.pairFor(factory, path[0], path[1], fees[0]),
amountIn
);
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, fees, to, referrer);
require(
IERC20(path[path.length - 1]).balanceOf(to) - balanceBefore >=
amountOutMin,
"JAMMRouter: INSUFFICIENT_OUTPUT_AMOUNT"
);
}
Query Functions
Price Query
function quote(
uint amountA,
uint reserveA,
uint reserveB
) public pure virtual override returns (uint amountB) {
return JAMMLibrary.quote(amountA, reserveA, reserveB);
}
Output Amount Calculation
function getAmountOut(
uint amountIn,
uint reserveIn,
uint reserveOut,
uint24 fee
) public pure virtual override returns (uint amountOut) {
return JAMMLibrary.getAmountOut(amountIn, reserveIn, reserveOut, fee);
}
Input Amount Calculation
function getAmountIn(
uint amountOut,
uint reserveIn,
uint reserveOut,
uint24 fee
) public pure virtual override returns (uint amountIn) {
return JAMMLibrary.getAmountIn(amountOut, reserveIn, reserveOut, fee);
}
Multi-hop Amount Calculation
function getAmountsOut(
uint amountIn,
address[] memory path,
uint24[] memory fees
) public view virtual override returns (uint[] memory amounts) {
return JAMMLibrary.getAmountsOut(factory, amountIn, path, fees);
}
function getAmountsIn(
uint amountOut,
address[] memory path,
uint24[] memory fees
) public view virtual override returns (uint[] memory amounts) {
return JAMMLibrary.getAmountsIn(factory, amountOut, path, fees);
}
Usage Examples
Token Swapping
const router = new ethers.Contract(routerAddress, routerABI, signer);
// Exact input swap
const amountIn = ethers.utils.parseEther("1.0");
const amountOutMin = ethers.utils.parseEther("0.95");
const path = [tokenA, tokenB];
const fees = [100]; // 1%
const to = signer.address;
const referrer = ethers.constants.AddressZero;
const deadline = Math.floor(Date.now() / 1000) + 60 * 20;
const tx = await router.swapExactTokensForTokens(
amountIn,
amountOutMin,
path,
fees,
to,
referrer,
deadline
);
JU Token Swapping
// Buy tokens with JU
const tx = await router.swapExactETHForTokens(
amountOutMin,
[WJU_ADDRESS, tokenB],
[100],
to,
referrer,
deadline,
{ value: ethers.utils.parseEther("1.0") }
);
Add Liquidity
// Add token liquidity
const tx = await router.addLiquidity(
tokenA,
tokenB,
100, // 1% fee rate
ethers.utils.parseEther("10"),
ethers.utils.parseEther("10"),
ethers.utils.parseEther("9"),
ethers.utils.parseEther("9"),
to,
deadline
);
Price Query
// Query output amount
const amountOut = await router.getAmountOut(
ethers.utils.parseEther("1"),
reserve0,
reserve1,
100
);
// Query multi-hop output
const amounts = await router.getAmountsOut(
ethers.utils.parseEther("1"),
[tokenA, tokenB, tokenC],
[100, 200]
);
Last updated