JAMMRouter 合约
JAMMRouter是JAMM DEX的用户接口层合约,为用户提供友好的交易和流动性管理功能。它封装了复杂的底层逻辑,支持多跳交换、自动路径计算、截止时间保护,并与WJU深度集成,使用户能够直接使用JU代币进行交易。
合约基本信息
contract JAMMRouter is IJAMMRouter02 {
address public immutable override factory;
address public immutable override WJU;
constructor(address _factory, address _WJU) {
factory = _factory;
WJU = _WJU;
}
}
不可变变量:
factory
: JAMMFactory合约地址WJU
: WJU包装代币合约地址
修饰符
截止时间保护
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, "JAMMRouter: EXPIRED");
_;
}
所有交易函数都使用此修饰符,确保交易在指定时间内执行。
ETH接收
receive() external payable {
assert(msg.sender == WJU); // 只接受来自WJU合约的ETH
}
Router只接受来自WJU合约的ETH,用于WJU解包装时的ETH转账。
流动性管理
内部流动性计算
function _addLiquidity(
address tokenA,
address tokenB,
uint24 fee,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal virtual returns (uint amountA, uint amountB) {
// 如果交易对不存在则创建
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) {
// 首次添加流动性
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
// 计算最优数量
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);
}
}
}
计算逻辑:
检查交易对是否存在,不存在则创建
获取当前储备量
如果是首次添加,使用期望数量
否则计算最优比例,确保不超过最小数量要求
添加流动性
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);
}
添加JU流动性
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);
// 退还多余的JU
if (msg.value > amountETH)
TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}
JU处理流程:
计算最优数量
转入代币到交易对
将JU包装为WJU并转入交易对
铸造LP代币
退还多余的JU
移除流动性
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); // 发送LP代币到交易对
(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");
}
移除JU流动性
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支持
带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优势:
无需预先调用approve
一次交易完成授权和操作
减少Gas消耗和交易步骤
代币交换
内部交换逻辑
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);
}
}
多跳交换逻辑:
验证路径和费率数组的长度关系
遍历交换路径
计算每一跳的输出数量
确定下一跳的接收地址
调用交易对的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);
}
精确输出交换
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代币交换
用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);
}
卖出代币换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]);
}
支持转账费代币
内部交换逻辑(支持转账费)
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
);
}
}
转账费代币处理:
不预先计算数量,而是基于实际到账数量
每一跳都重新计算输入数量
适用于转账时收费的代币
支持转账费的交换函数
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"
);
}
查询函数
价格查询
function quote(
uint amountA,
uint reserveA,
uint reserveB
) public pure virtual override returns (uint amountB) {
return JAMMLibrary.quote(amountA, reserveA, reserveB);
}
输出数量计算
function getAmountOut(
uint amountIn,
uint reserveIn,
uint reserveOut,
uint24 fee
) public pure virtual override returns (uint amountOut) {
return JAMMLibrary.getAmountOut(amountIn, reserveIn, reserveOut, fee);
}
输入数量计算
function getAmountIn(
uint amountOut,
uint reserveIn,
uint reserveOut,
uint24 fee
) public pure virtual override returns (uint amountIn) {
return JAMMLibrary.getAmountIn(amountOut, reserveIn, reserveOut, fee);
}
多跳数量计算
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);
}
使用示例
代币交换
const router = new ethers.Contract(routerAddress, routerABI, signer);
// 精确输入交换
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代币交换
// 用JU购买代币
const tx = await router.swapExactETHForTokens(
amountOutMin,
[WJU_ADDRESS, tokenB],
[100],
to,
referrer,
deadline,
{ value: ethers.utils.parseEther("1.0") }
);
添加流动性
// 添加代币流动性
const tx = await router.addLiquidity(
tokenA,
tokenB,
100, // 1%费率
ethers.utils.parseEther("10"),
ethers.utils.parseEther("10"),
ethers.utils.parseEther("9"),
ethers.utils.parseEther("9"),
to,
deadline
);
价格查询
// 查询输出数量
const amountOut = await router.getAmountOut(
ethers.utils.parseEther("1"),
reserve0,
reserve1,
100
);
// 查询多跳输出
const amounts = await router.getAmountsOut(
ethers.utils.parseEther("1"),
[tokenA, tokenB, tokenC],
[100, 200]
);
Last updated