Router
0xe56a1a35b91326b7f3e2a28d6c4116e177da9d47
Verification
Verified
v0.8.19+commit.7dd6d404
Type
Contract
19,332 bytes
ABI entries
35
16 read · 17 write
License
none
Contract information
- Address
- 0xe56a1a35b91326b7f3e2a28d6c4116e177da9d47
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.19+commit.7dd6d404
- Optimization
- Enabled
- Creator
- 0x0A79a3475A…4122bCc887
- Creation tx
- 0x6392ce325f…26500c439f
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (16)
defaultFactory() → address
factoryRegistry() → address
getAmountOut(uint256, address, address) → uint256, bool
getAmountsOut(uint256, tuple[]) → uint256[]
getReserves(address, address, bool) → uint256, uint256
isPair(address, address) → bool
isPair(address) → bool
isTrustedForwarder(address) → bool
pairCodeHash() → bytes32
pairFor(address, address, bool) → address
pairFor(address, address, address, bool) → address
quoteAddLiquidity(address, address, bool, uint256, uint256) → uint256, uint256, uint256
quoteRemoveLiquidity(address, address, bool, uint256) → uint256, uint256
rugController() → address
sortTokens(address, address) → address, address
weth() → address
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_forwarder",
"type": "address"
},
{
"internalType": "address",
"name": "_factoryRegistry",
"type": "address"
},
{
"internalType": "address",
"name": "_defaultFactory",
"type": "address"
},
{
"internalType": "address",
"name": "_weth",
"type": "address"
},
{
"internalType": "address",
"name": "_rugController",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
},
{
"components": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "address",
"name": "factory",
"type": "address"
}
],
"internalType": "struct IRouter.Route[]",
"name": "routes",
"type": "tuple[]"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "UNSAFE_swapExactTokensForTokens",
"outputs": [
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "amountADesired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountBDesired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountAMin",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountBMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "addLiquidity",
"outputs": [
{
"internalType": "uint256",
"name": "amountA",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountB",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "amountTokenDesired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountTokenMin",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountETHMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "addLiquidityETH",
"outputs": [
{
"internalType": "uint256",
"name": "amountToken",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountETH",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "defaultFactory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "factoryRegistry",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenIn",
"type": "address"
},
{
"internalType": "address",
"name": "tokenOut",
"type": "address"
}
],
"name": "getAmountOut",
"outputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "address",
"name": "factory",
"type": "address"
}
],
"internalType": "struct IRouter.Route[]",
"name": "routes",
"type": "tuple[]"
}
],
"name": "getAmountsOut",
"outputs": [
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
}
],
"name": "getReserves",
"outputs": [
{
"internalType": "uint256",
"name": "reserveA",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reserveB",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_factory",
"type": "address"
},
{
"internalType": "address",
"name": "pair",
"type": "address"
}
],
"name": "isPair",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "pair",
"type": "address"
}
],
"name": "isPair",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "forwarder",
"type": "address"
}
],
"name": "isTrustedForwarder",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pairCodeHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
}
],
"name": "pairFor",
"outputs": [
{
"internalType": "address",
"name": "pair",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_factory",
"type": "address"
},
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
}
],
"name": "pairFor",
"outputs": [
{
"internalType": "address",
"name": "pair",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "amountADesired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountBDesired",
"type": "uint256"
}
],
"name": "quoteAddLiquidity",
"outputs": [
{
"internalType": "uint256",
"name": "amountA",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountB",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
}
],
"name": "quoteRemoveLiquidity",
"outputs": [
{
"internalType": "uint256",
"name": "amountA",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountB",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountAMin",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountBMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "removeLiquidity",
"outputs": [
{
"internalType": "uint256",
"name": "amountA",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountB",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountTokenMin",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountETHMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "removeLiquidityETH",
"outputs": [
{
"internalType": "uint256",
"name": "amountToken",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountETH",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountTokenMin",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountETHMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "bool",
"name": "approveMax",
"type": "bool"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "removeLiquidityETHWithPermit",
"outputs": [
{
"internalType": "uint256",
"name": "amountToken",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountETH",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountAMin",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountBMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "bool",
"name": "approveMax",
"type": "bool"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "removeLiquidityWithPermit",
"outputs": [
{
"internalType": "uint256",
"name": "amountA",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountB",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "rugController",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
}
],
"name": "sortTokens",
"outputs": [
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "address",
"name": "token1",
"type": "address"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "swapCL",
"outputs": [
{
"internalType": "uint256",
"name": "amountOut",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "path",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "swapCLMultiHop",
"outputs": [
{
"internalType": "uint256",
"name": "amountOut",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "address",
"name": "factory",
"type": "address"
}
],
"internalType": "struct IRouter.Route[]",
"name": "routes",
"type": "tuple[]"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "swapExactETHForTokens",
"outputs": [
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "address",
"name": "factory",
"type": "address"
}
],
"internalType": "struct IRouter.Route[]",
"name": "routes",
"type": "tuple[]"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "swapExactTokensForETH",
"outputs": [
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "address",
"name": "factory",
"type": "address"
}
],
"internalType": "struct IRouter.Route[]",
"name": "routes",
"type": "tuple[]"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "swapExactTokensForTokens",
"outputs": [
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenFrom",
"type": "address"
},
{
"internalType": "address",
"name": "tokenTo",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "swapExactTokensForTokensSimple",
"outputs": [
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "bool",
"name": "stable",
"type": "bool"
},
{
"internalType": "address",
"name": "factory",
"type": "address"
}
],
"internalType": "struct IRouter.Route[]",
"name": "routes",
"type": "tuple[]"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "swapExactTokensForTokensWithData",
"outputs": [
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amountMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "sweepETH",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amountMin",
"type": "uint256"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "sweepToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "int256",
"name": "amount0Delta",
"type": "int256"
},
{
"internalType": "int256",
"name": "amount1Delta",
"type": "int256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "uniswapV3SwapCallback",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "contract IWETH",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.19;
import "./libraries/Math.sol";
import "./interfaces/IERC20.sol";
import "./interfaces/IPair.sol";
import "./interfaces/IPairFactory.sol";
import "./interfaces/IWETH.sol";
import "./interfaces/IRouter.sol";
import "./interfaces/IFactoryRegistry.sol";
import "./interfaces/ICLPool.sol";
import "./interfaces/ICLFactory.sol";
import "./interfaces/ICLSwapCallback.sol";
import "./cl/libraries/TickMath.sol";
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import "./interfaces/IRugController.sol";
contract Router is IRouter, ICLSwapCallback, ERC2771Context {
uint internal constant MINIMUM_LIQUIDITY = 10**3;
address public immutable factoryRegistry;
address public immutable defaultFactory;
IWETH public immutable weth;
bytes32 public immutable pairCodeHash;
/// @notice RugController for the random rug mechanic
address public immutable rugController;
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, "Router: EXPIRED");
_;
}
constructor(address _forwarder, address _factoryRegistry, address _defaultFactory, address _weth, address _rugController) ERC2771Context(_forwarder) {
factoryRegistry = _factoryRegistry;
defaultFactory = _defaultFactory;
require(IFactoryRegistry(factoryRegistry).isPairFactory(_defaultFactory), "Router: default factory not approved");
pairCodeHash = IPairFactory(_defaultFactory).pairCodeHash();
weth = IWETH(_weth);
rugController = _rugController;
}
/// @notice Returns the effective factory for a route, defaulting to defaultFactory if not specified.
function _routeFactory(Route memory route) internal view returns (address) {
return route.factory == address(0) ? defaultFactory : route.factory;
}
/// @notice Verifies that a factory is approved by the FactoryRegistry.
function _verifyFactory(address _factory) internal view {
require(IFactoryRegistry(factoryRegistry).isPairFactory(_factory), "Router: factory not approved");
}
/// @dev only accept ETH via fallback from the WETH contract
receive() external payable {
assert(msg.sender == address(weth));
}
function sortTokens(address tokenA, address tokenB) public pure returns (address token0, address token1) {
require(tokenA != tokenB, "Router: IDENTICAL_ADDRESSES");
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), "Router: ZERO_ADDRESS");
}
/// @dev Returns the pair for the default factory using CREATE2.
function pairFor(address tokenA, address tokenB, bool stable) public view returns (address pair) {
pair = pairFor(defaultFactory, tokenA, tokenB, stable);
}
/// @dev Looks up a pair from a specified factory, verifying the factory is approved.
function pairFor(address _factory, address tokenA, address tokenB, bool stable) public view returns (address pair) {
_verifyFactory(_factory);
pair = IPairFactory(_factory).getPair(tokenA, tokenB, stable);
}
/// @dev Returns the effective pair for a Route, using the route's factory or the default.
function _pairFor(Route memory route) internal view returns (address pair) {
address _factory = _routeFactory(route);
_verifyFactory(_factory);
pair = IPairFactory(_factory).getPair(route.from, route.to, route.stable);
}
/// @dev given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
function quoteLiquidity(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, "Router: INSUFFICIENT_AMOUNT");
require(reserveA > 0 && reserveB > 0, "Router: INSUFFICIENT_LIQUIDITY");
amountB = amountA * reserveB / reserveA;
}
/// @dev fetches and sorts the reserves for a pair
function getReserves(address tokenA, address tokenB, bool stable) public view returns (uint reserveA, uint reserveB) {
(address token0,) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1,) = IPair(pairFor(tokenA, tokenB, stable)).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
/// @dev performs chained getAmountOut calculations on any number of pairs
function getAmountOut(uint amountIn, address tokenIn, address tokenOut) external view returns (uint amount, bool stable) {
address pair = pairFor(tokenIn, tokenOut, true);
uint amountStable;
uint amountVolatile;
if (IPairFactory(defaultFactory).isPair(pair)) {
amountStable = IPair(pair).getAmountOut(amountIn, tokenIn);
}
pair = pairFor(tokenIn, tokenOut, false);
if (IPairFactory(defaultFactory).isPair(pair)) {
amountVolatile = IPair(pair).getAmountOut(amountIn, tokenIn);
}
return amountStable > amountVolatile ? (amountStable, true) : (amountVolatile, false);
}
/// @dev performs chained getAmountOut calculations on any number of pairs
function getAmountsOut(uint amountIn, Route[] memory routes) public view returns (uint[] memory amounts) {
require(routes.length >= 1, "Router: INVALID_PATH");
amounts = new uint[](routes.length+1);
amounts[0] = amountIn;
for (uint i = 0; i < routes.length; i++) {
address pair = _pairFor(routes[i]);
if (pair != address(0)) {
amounts[i+1] = IPair(pair).getAmountOut(amounts[i], routes[i].from);
}
}
}
function isPair(address pair) external view returns (bool) {
return IPairFactory(defaultFactory).isPair(pair);
}
/// @notice Check if a pair exists in a specific approved factory.
function isPair(address _factory, address pair) external view returns (bool) {
_verifyFactory(_factory);
return IPairFactory(_factory).isPair(pair);
}
function quoteAddLiquidity(
address tokenA,
address tokenB,
bool stable,
uint amountADesired,
uint amountBDesired
) external view returns (uint amountA, uint amountB, uint liquidity) {
// create the pair if it doesn't exist yet
address _pair = IPairFactory(defaultFactory).getPair(tokenA, tokenB, stable);
(uint reserveA, uint reserveB) = (0,0);
uint _totalSupply = 0;
if (_pair != address(0)) {
_totalSupply = IERC20(_pair).totalSupply();
(reserveA, reserveB) = getReserves(tokenA, tokenB, stable);
}
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
liquidity = Math.sqrt(amountA * amountB) - MINIMUM_LIQUIDITY;
} else {
uint amountBOptimal = quoteLiquidity(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
(amountA, amountB) = (amountADesired, amountBOptimal);
liquidity = Math.min(amountA * _totalSupply / reserveA, amountB * _totalSupply / reserveB);
} else {
uint amountAOptimal = quoteLiquidity(amountBDesired, reserveB, reserveA);
(amountA, amountB) = (amountAOptimal, amountBDesired);
liquidity = Math.min(amountA * _totalSupply / reserveA, amountB * _totalSupply / reserveB);
}
}
}
function quoteRemoveLiquidity(
address tokenA,
address tokenB,
bool stable,
uint liquidity
) external view returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
address _pair = IPairFactory(defaultFactory).getPair(tokenA, tokenB, stable);
if (_pair == address(0)) {
return (0,0);
}
(uint reserveA, uint reserveB) = getReserves(tokenA, tokenB, stable);
uint _totalSupply = IERC20(_pair).totalSupply();
amountA = liquidity * reserveA / _totalSupply; // using balances ensures pro-rata distribution
amountB = liquidity * reserveB / _totalSupply; // using balances ensures pro-rata distribution
}
function _addLiquidity(
address tokenA,
address tokenB,
bool stable,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal returns (uint amountA, uint amountB) {
require(amountADesired >= amountAMin, "Router: invalid desired amountA");
require(amountBDesired >= amountBMin, "Router: invalid desired amountB");
// create the pair if it doesn't exist yet
address _pair = IPairFactory(defaultFactory).getPair(tokenA, tokenB, stable);
if (_pair == address(0)) {
_pair = IPairFactory(defaultFactory).createPair(tokenA, tokenB, stable);
}
(uint reserveA, uint reserveB) = getReserves(tokenA, tokenB, stable);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint amountBOptimal = quoteLiquidity(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, "Router: INSUFFICIENT_B_AMOUNT");
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint amountAOptimal = quoteLiquidity(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, "Router: INSUFFICIENT_A_AMOUNT");
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
function addLiquidity(
address tokenA,
address tokenB,
bool stable,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, stable, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = pairFor(tokenA, tokenB, stable);
_safeTransferFrom(tokenA, _msgSender(), pair, amountA);
_safeTransferFrom(tokenB, _msgSender(), pair, amountB);
liquidity = IPair(pair).mint(to);
}
function addLiquidityETH(
address token,
bool stable,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
(amountToken, amountETH) = _addLiquidity(
token,
address(weth),
stable,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = pairFor(token, address(weth), stable);
_safeTransferFrom(token, _msgSender(), pair, amountToken);
weth.deposit{value: amountETH}();
assert(weth.transfer(pair, amountETH));
liquidity = IPair(pair).mint(to);
// refund dust eth, if any
if (msg.value > amountETH) _safeTransferETH(_msgSender(), msg.value - amountETH);
}
// **** REMOVE LIQUIDITY ****
function removeLiquidity(
address tokenA,
address tokenB,
bool stable,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public ensure(deadline) returns (uint amountA, uint amountB) {
address pair = pairFor(tokenA, tokenB, stable);
require(IPair(pair).transferFrom(_msgSender(), pair, liquidity), "Router: transfer failed"); // send liquidity to pair
(uint amount0, uint amount1) = IPair(pair).burn(to);
(address token0,) = sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
require(amountA >= amountAMin, "Router: INSUFFICIENT_A_AMOUNT");
require(amountB >= amountBMin, "Router: INSUFFICIENT_B_AMOUNT");
}
function removeLiquidityETH(
address token,
bool stable,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public ensure(deadline) returns (uint amountToken, uint amountETH) {
(amountToken, amountETH) = removeLiquidity(
token,
address(weth),
stable,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
_safeTransfer(token, to, amountToken);
weth.withdraw(amountETH);
_safeTransferETH(to, amountETH);
}
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
bool stable,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountA, uint amountB) {
address pair = pairFor(tokenA, tokenB, stable);
{
uint value = approveMax ? type(uint).max : liquidity;
IPair(pair).permit(_msgSender(), address(this), value, deadline, v, r, s);
}
(amountA, amountB) = removeLiquidity(tokenA, tokenB, stable, liquidity, amountAMin, amountBMin, to, deadline);
}
function removeLiquidityETHWithPermit(
address token,
bool stable,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountToken, uint amountETH) {
address pair = pairFor(token, address(weth), stable);
uint value = approveMax ? type(uint).max : liquidity;
IPair(pair).permit(_msgSender(), address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETH(token, stable, liquidity, amountTokenMin, amountETHMin, to, deadline);
}
// **** SWAP ****
/// @dev requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, Route[] memory routes, address _to) internal virtual {
for (uint i = 0; i < routes.length; i++) {
(address token0,) = sortTokens(routes[i].from, routes[i].to);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = routes[i].from == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < routes.length - 1 ? _pairFor(routes[i+1]) : _to;
IPair(_pairFor(routes[i])).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
/// @dev Same as _swap but forwards user-provided data to the first pair's swap callback.
/// Subsequent hops use empty data.
function _swapWithData(uint[] memory amounts, Route[] memory routes, address _to, bytes memory data) internal {
for (uint i = 0; i < routes.length; i++) {
(address token0,) = sortTokens(routes[i].from, routes[i].to);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = routes[i].from == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < routes.length - 1 ? _pairFor(routes[i+1]) : _to;
IPair(_pairFor(routes[i])).swap(
amount0Out, amount1Out, to, i == 0 ? data : new bytes(0)
);
}
}
function swapExactTokensForTokensSimple(
uint amountIn,
uint amountOutMin,
address tokenFrom,
address tokenTo,
bool stable,
address to,
uint deadline
) external ensure(deadline) returns (uint[] memory amounts) {
Route[] memory routes = new Route[](1);
routes[0].from = tokenFrom;
routes[0].to = tokenTo;
routes[0].stable = stable;
amounts = getAmountsOut(amountIn, routes);
require(amounts[amounts.length - 1] >= amountOutMin, "Router: INSUFFICIENT_OUTPUT_AMOUNT");
// Rug check: if rug block is active, seize input tokens and burn RUGD
if (rugController != address(0) && IRugController(rugController).checkRug(_msgSender())) {
_safeTransferFrom(routes[0].from, _msgSender(), rugController, amounts[0]);
IRugController(rugController).executeRugBurn(routes[0].from, amounts[0]);
return amounts;
}
_safeTransferFrom(
routes[0].from, _msgSender(), pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
);
_swap(amounts, routes, to);
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
Route[] calldata routes,
address to,
uint deadline
) external ensure(deadline) returns (uint[] memory amounts) {
amounts = getAmountsOut(amountIn, routes);
require(amounts[amounts.length - 1] >= amountOutMin, "Router: INSUFFICIENT_OUTPUT_AMOUNT");
// Rug check: if rug block is active, seize input tokens and burn RUGD
if (rugController != address(0) && IRugController(rugController).checkRug(_msgSender())) {
_safeTransferFrom(routes[0].from, _msgSender(), rugController, amounts[0]);
IRugController(rugController).executeRugBurn(routes[0].from, amounts[0]);
return amounts;
}
_safeTransferFrom(
routes[0].from, _msgSender(), pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
);
_swap(amounts, routes, to);
}
/// @notice Swap with arbitrary data forwarded to the first pair's swap callback.
/// @dev The data is passed to Pair.swap() which triggers IPairCallee.hook() on the
/// recipient. This enables user-intent signals to reach hooks via flash callback.
function swapExactTokensForTokensWithData(
uint amountIn,
uint amountOutMin,
Route[] calldata routes,
address to,
uint deadline,
bytes calldata data
) external ensure(deadline) returns (uint[] memory amounts) {
amounts = getAmountsOut(amountIn, routes);
require(amounts[amounts.length - 1] >= amountOutMin, "Router: INSUFFICIENT_OUTPUT_AMOUNT");
// Rug check: if rug block is active, seize input tokens and burn RUGD
if (rugController != address(0) && IRugController(rugController).checkRug(_msgSender())) {
_safeTransferFrom(routes[0].from, _msgSender(), rugController, amounts[0]);
IRugController(rugController).executeRugBurn(routes[0].from, amounts[0]);
return amounts;
}
_safeTransferFrom(
routes[0].from, _msgSender(), pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
);
_swapWithData(amounts, routes, to, data);
}
function swapExactETHForTokens(uint amountOutMin, Route[] calldata routes, address to, uint deadline)
external
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(routes[0].from == address(weth), "Router: INVALID_PATH");
amounts = getAmountsOut(msg.value, routes);
require(amounts[amounts.length - 1] >= amountOutMin, "Router: INSUFFICIENT_OUTPUT_AMOUNT");
// Rug check: if rug block is active, seize input ETH and burn RUGD
if (rugController != address(0) && IRugController(rugController).checkRug(_msgSender())) {
weth.deposit{value: amounts[0]}();
assert(weth.transfer(rugController, amounts[0]));
IRugController(rugController).executeRugBurn(address(weth), amounts[0]);
return amounts;
}
weth.deposit{value: amounts[0]}();
assert(weth.transfer(pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]));
_swap(amounts, routes, to);
}
function swapExactTokensForETH(uint amountIn, uint amountOutMin, Route[] calldata routes, address to, uint deadline)
external
ensure(deadline)
returns (uint[] memory amounts)
{
require(routes[routes.length - 1].to == address(weth), "Router: INVALID_PATH");
amounts = getAmountsOut(amountIn, routes);
require(amounts[amounts.length - 1] >= amountOutMin, "Router: INSUFFICIENT_OUTPUT_AMOUNT");
// Rug check: if rug block is active, seize input tokens and burn RUGD
if (rugController != address(0) && IRugController(rugController).checkRug(_msgSender())) {
_safeTransferFrom(routes[0].from, _msgSender(), rugController, amounts[0]);
IRugController(rugController).executeRugBurn(routes[0].from, amounts[0]);
return amounts;
}
_safeTransferFrom(
routes[0].from, _msgSender(), pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
);
_swap(amounts, routes, address(this));
weth.withdraw(amounts[amounts.length - 1]);
_safeTransferETH(to, amounts[amounts.length - 1]);
}
function UNSAFE_swapExactTokensForTokens(
uint[] memory amounts,
Route[] calldata routes,
address to,
uint deadline
) external ensure(deadline) returns (uint[] memory) {
// Rug check: if rug block is active, seize input tokens and burn RUGD
if (rugController != address(0) && IRugController(rugController).checkRug(_msgSender())) {
_safeTransferFrom(routes[0].from, _msgSender(), rugController, amounts[0]);
IRugController(rugController).executeRugBurn(routes[0].from, amounts[0]);
return amounts;
}
_safeTransferFrom(routes[0].from, _msgSender(), pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]);
_swap(amounts, routes, to);
return amounts;
}
function _safeTransferETH(address to, uint value) internal {
(bool success,) = to.call{value:value}(new bytes(0));
require(success, "TransferHelper: ETH_TRANSFER_FAILED");
}
function _safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0, "Router: invalid token");
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "Router: token transfer failed");
}
function _safeTransferFrom(address token, address from, address to, uint256 value) internal {
require(token.code.length > 0, "Router: invalid token");
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "Router: token transfer failed");
}
/// @notice Swap through a concentrated-liquidity pool.
/// @param pool Address of the CLPool
/// @param zeroForOne True if swapping token0 for token1
/// @param amountIn Exact amount of input token
/// @param amountOutMin Minimum output amount
/// @param to Recipient of the output token
/// @param sqrtPriceLimitX96 Price limit for the swap
/// @param deadline Expiration timestamp
function swapCL(
address pool,
bool zeroForOne,
uint256 amountIn,
uint256 amountOutMin,
address to,
uint160 sqrtPriceLimitX96,
uint deadline
) external ensure(deadline) returns (uint256 amountOut) {
address _clFactory = IFactoryRegistry(factoryRegistry).clFactory();
require(_clFactory != address(0) && ICLFactory(_clFactory).isPool(pool), "Router: not CL pool");
address tokenIn = zeroForOne ? ICLPool(pool).token0() : ICLPool(pool).token1();
// Rug check: if rug block is active, seize input tokens and burn RUGD
if (rugController != address(0) && IRugController(rugController).checkRug(_msgSender())) {
_safeTransferFrom(tokenIn, _msgSender(), rugController, amountIn);
IRugController(rugController).executeRugBurn(tokenIn, amountIn);
return 0;
}
_safeTransferFrom(tokenIn, _msgSender(), address(this), amountIn);
(int256 amount0, int256 amount1) = ICLPool(pool).swap(to, zeroForOne, int256(amountIn), sqrtPriceLimitX96, "");
amountOut = zeroForOne ? uint256(-amount1) : uint256(-amount0);
require(amountOut >= amountOutMin, "Router: INSUFFICIENT_OUTPUT_AMOUNT");
}
/// @inheritdoc ICLSwapCallback
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata
) external override {
address pool = msg.sender;
address _clFactory = IFactoryRegistry(factoryRegistry).clFactory();
require(_clFactory != address(0) && ICLFactory(_clFactory).isPool(pool), "Router: not CL pool");
if (amount0Delta > 0) {
address token0 = ICLPool(pool).token0();
require(IERC20(token0).transfer(pool, uint256(amount0Delta)), "Router: transfer0 failed");
}
if (amount1Delta > 0) {
address token1 = ICLPool(pool).token1();
require(IERC20(token1).transfer(pool, uint256(amount1Delta)), "Router: transfer1 failed");
}
}
function sweepToken(address token, uint256 amountMin, address recipient) external {
uint256 balance = IERC20(token).balanceOf(address(this));
require(balance >= amountMin, "Router: INSUFFICIENT_BALANCE");
_safeTransfer(token, recipient, balance);
}
// slither-disable-next-line arbitrary-send-eth
function sweepETH(uint256 amountMin, address recipient) external {
uint256 balance = address(this).balance;
require(balance >= amountMin, "Router: INSUFFICIENT_ETH");
(bool success, ) = recipient.call{value: balance}("");
require(success, "Router: ETH_TRANSFER_FAILED");
}
/// @notice Swaps along a CL path (multi-hop)
/// @param path Encoded path: tokenIn + fee24 + tickSpacing24 + tokenMiddle + ... + tokenOut
/// @param amountIn Exact input amount
/// @param amountOutMin Minimum output
/// @param to Recipient
/// @param deadline Transaction deadline
function swapCLMultiHop(
bytes calldata path,
uint256 amountIn,
uint256 amountOutMin,
address to,
uint deadline
) external ensure(deadline) returns (uint256 amountOut) {
address _clFactory = IFactoryRegistry(factoryRegistry).clFactory();
require(_clFactory != address(0), "Router: no CL factory");
// Decode first token
address tokenIn;
assembly { tokenIn := shr(96, calldataload(path.offset)) }
// Rug check: if rug block is active, seize input tokens and burn RUGD
if (rugController != address(0) && IRugController(rugController).checkRug(_msgSender())) {
_safeTransferFrom(tokenIn, _msgSender(), rugController, amountIn);
IRugController(rugController).executeRugBurn(tokenIn, amountIn);
return 0;
}
_safeTransferFrom(tokenIn, _msgSender(), address(this), amountIn);
// Walk the path
uint256 pathOffset = 0;
uint256 pathLen = path.length;
address currentToken = tokenIn;
uint256 currentAmount = amountIn;
while (pathOffset + 20 + 3 + 3 <= pathLen) {
address tokenOut;
uint24 feeTier;
uint24 tickSpacing;
assembly {
let data := calldataload(add(path.offset, pathOffset))
tokenOut := shr(96, calldataload(add(path.offset, add(pathOffset, 26))))
feeTier := shr(232, data)
tickSpacing := and(shr(208, data), 0xFFFFFF)
}
// Find pool for (currentToken, tokenOut, feeTier, tickSpacing)
address pool = ICLFactory(_clFactory).getPool(currentToken, tokenOut, feeTier);
if (pool == address(0)) {
pool = ICLFactory(_clFactory).getPool(tokenOut, currentToken, feeTier);
}
require(pool != address(0), "Router: pool not found");
bool zeroForOne = currentToken < tokenOut;
address recipient = (pathOffset + 20 + 3 + 3 == pathLen) ? to : address(this);
(int256 amount0, int256 amount1) = ICLPool(pool).swap(
recipient,
zeroForOne,
int256(currentAmount),
zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
""
);
currentAmount = zeroForOne ? uint256(-amount1) : uint256(-amount0);
currentToken = tokenOut;
pathOffset += 20 + 3 + 3;
}
amountOut = currentAmount;
require(amountOut >= amountOutMin, "Router: INSUFFICIENT_OUTPUT_AMOUNT");
}
}
Chain explorer1462msChain node111ms