├── images ├── logo1.png ├── test.png ├── maths00.png ├── maths01.png ├── maths02.png ├── maths03.png ├── maths04.png ├── maths05.png ├── maths06.png ├── maths07.png └── maths03bis.png ├── .gitignore ├── contracts ├── IUniswapV2Callee.sol ├── IERC20.sol ├── IUniswapV2Factory.sol ├── IUniswapV2Router.sol ├── UniswapV2SingleHopSwap.sol ├── UniswapV2FlashSwap.sol ├── UniswapV2MultiHopSwap.sol ├── IUniswapV2Pair.sol └── UniswapV2Liquidity.sol ├── hardhat.config.js ├── package.json ├── test ├── config.js └── uniswap.test.js └── README.md /images/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/logo1.png -------------------------------------------------------------------------------- /images/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/test.png -------------------------------------------------------------------------------- /images/maths00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths00.png -------------------------------------------------------------------------------- /images/maths01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths01.png -------------------------------------------------------------------------------- /images/maths02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths02.png -------------------------------------------------------------------------------- /images/maths03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths03.png -------------------------------------------------------------------------------- /images/maths04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths04.png -------------------------------------------------------------------------------- /images/maths05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths05.png -------------------------------------------------------------------------------- /images/maths06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths06.png -------------------------------------------------------------------------------- /images/maths07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths07.png -------------------------------------------------------------------------------- /images/maths03bis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths03bis.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | # Hardhat files 9 | cache 10 | artifacts 11 | 12 | -------------------------------------------------------------------------------- /contracts/IUniswapV2Callee.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IUniswapV2Callee { 4 | function uniswapV2Call( 5 | address sender, 6 | uint amount0, 7 | uint amount1, 8 | bytes calldata data 9 | ) external; 10 | } 11 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox"); 2 | require("dotenv").config(); 3 | 4 | /** @type import('hardhat/config').HardhatUserConfig */ 5 | module.exports = { 6 | solidity: { 7 | compilers: [{ version: "0.8.17" }, { version: "0.4.19" }], 8 | }, 9 | networks: { 10 | hardhat: { 11 | forking: { 12 | url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`, 13 | }, 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uniswapv2", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "dotenv": "^16.0.3", 14 | "hardhat": "^2.13.0" 15 | }, 16 | "devDependencies": { 17 | "@nomicfoundation/hardhat-toolbox": "^2.0.2", 18 | "prettier": "^2.8.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IERC20 { 5 | function totalSupply() external view returns (uint256); 6 | 7 | function balanceOf(address _owner) external view returns (uint256); 8 | 9 | function transfer(address to, uint256 amount) external; 10 | 11 | function allowance( 12 | address from, 13 | address to, 14 | uint256 amount 15 | ) external view returns (bool); 16 | 17 | function approve(address sender, uint256 amount) external; 18 | 19 | function transferFrom( 20 | address from, 21 | address to, 22 | uint256 amount 23 | ) external; 24 | 25 | event Transfer(address indexed from, address indexed to, uint256 amount); 26 | 27 | event Approval(address indexed from, address indexed to, uint256 amount); 28 | } 29 | -------------------------------------------------------------------------------- /contracts/IUniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IUniswapV2Factory { 5 | event PairCreated( 6 | address indexed token0, 7 | address indexed token1, 8 | address pair, 9 | uint 10 | ); 11 | 12 | function feeTo() external view returns (address); 13 | 14 | function feeToSetter() external view returns (address); 15 | 16 | function getPair(address tokenA, address tokenB) 17 | external 18 | view 19 | returns (address pair); 20 | 21 | function allPairs(uint) external view returns (address pair); 22 | 23 | function allPairsLength() external view returns (uint); 24 | 25 | function createPair(address tokenA, address tokenB) 26 | external 27 | returns (address pair); 28 | 29 | function setFeeTo(address) external; 30 | 31 | function setFeeToSetter(address) external; 32 | } 33 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | const DAI = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; 4 | const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; 5 | const USDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; 6 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 7 | const WBTC = "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"; 8 | const CRV = "0xD533a949740bb3306d119CC777fa900bA034cd52"; 9 | 10 | const WETH_10 = "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F"; 11 | 12 | const DAI_WHALE = process.env.DAI_WHALE; 13 | const USDC_WHALE = process.env.USDC_WHALE; 14 | const USDT_WHALE = process.env.USDT_WHALE; 15 | const WETH_WHALE = process.env.WETH_WHALE; 16 | const WETH_WHALE1 = process.env.WETH_WHALE1; 17 | const WBTC_WHALE = process.env.WBTC_WHALE; 18 | const WETHDAI_WHALE = process.env.WETHDAI_WHALE; 19 | 20 | module.exports = { 21 | DAI, 22 | USDC, 23 | USDT, 24 | WETH, 25 | WBTC, 26 | CRV, 27 | WETH_10, 28 | DAI_WHALE, 29 | USDC_WHALE, 30 | USDT_WHALE, 31 | WETH_WHALE, 32 | WETH_WHALE1, 33 | WBTC_WHALE, 34 | WETHDAI_WHALE, 35 | }; 36 | -------------------------------------------------------------------------------- /contracts/IUniswapV2Router.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IUniswapV2Router { 5 | function swapExactTokensForTokens( 6 | uint amountIn, 7 | uint amountOutMin, 8 | address[] calldata path, 9 | address to, 10 | uint deadline 11 | ) external returns (uint[] memory amounts); 12 | 13 | function swapTokensForExactTokens( 14 | uint amountOut, 15 | uint amountInMax, 16 | address[] calldata path, 17 | address to, 18 | uint deadline 19 | ) external returns (uint[] memory amounts); 20 | 21 | function addLiquidity( 22 | address tokenA, 23 | address tokenB, 24 | uint amountADesired, 25 | uint amountBDesired, 26 | uint amountAMin, 27 | uint amountBMin, 28 | address to, 29 | uint deadline 30 | ) 31 | external 32 | returns ( 33 | uint amountA, 34 | uint amountB, 35 | uint liquidity 36 | ); 37 | 38 | function removeLiquidity( 39 | address tokenA, 40 | address tokenB, 41 | uint liquidity, 42 | uint amountAMin, 43 | uint amountBMin, 44 | address to, 45 | uint deadline 46 | ) external returns (uint amountA, uint amountB); 47 | } 48 | -------------------------------------------------------------------------------- /contracts/UniswapV2SingleHopSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "./IERC20.sol"; 5 | import "./IUniswapV2Router.sol"; 6 | 7 | contract UniswapV2SingleHopSwap { 8 | address private constant UNISWAP_V2_ROUTER = 9 | 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; 10 | 11 | address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 12 | address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 13 | 14 | IUniswapV2Router private constant router = 15 | IUniswapV2Router(UNISWAP_V2_ROUTER); 16 | IERC20 private constant weth = IERC20(WETH); 17 | IERC20 private constant dai = IERC20(DAI); 18 | 19 | function swapSingleHopExactAmountIn(uint amountIn, uint amountOutMin) 20 | external 21 | { 22 | weth.transferFrom(msg.sender, address(this), amountIn); 23 | weth.approve(UNISWAP_V2_ROUTER, amountIn); 24 | address[] memory path = new address[](2); 25 | path[0] = WETH; 26 | path[1] = DAI; 27 | router.swapExactTokensForTokens( 28 | amountIn, 29 | amountOutMin, 30 | path, 31 | msg.sender, 32 | block.timestamp 33 | ); 34 | } 35 | 36 | function swapSingleHopExactAmountOut( 37 | uint amountOutDesired, 38 | uint amountInMax 39 | ) external { 40 | weth.transferFrom(msg.sender, address(this), amountInMax); 41 | weth.approve(UNISWAP_V2_ROUTER, amountInMax); 42 | address[] memory path = new address[](2); 43 | path[0] = WETH; 44 | path[1] = DAI; 45 | router.swapTokensForExactTokens( 46 | amountOutDesired, 47 | amountInMax, 48 | path, 49 | msg.sender, 50 | block.timestamp 51 | ); 52 | weth.transfer(msg.sender, weth.balanceOf(address(this))); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/UniswapV2FlashSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "./IERC20.sol"; 5 | import "./IUniswapV2Factory.sol"; 6 | import "./IUniswapV2Callee.sol"; 7 | import "./IUniswapV2Pair.sol"; 8 | 9 | contract UniswapV2FlashSwap is IUniswapV2Callee { 10 | event Log(string message, uint val); 11 | 12 | address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 13 | address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 14 | 15 | address private constant UNISWAP_V2_FACTORY = 16 | 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; 17 | 18 | IERC20 private constant weth = IERC20(WETH); 19 | IUniswapV2Factory private constant factory = 20 | IUniswapV2Factory(UNISWAP_V2_FACTORY); 21 | IUniswapV2Pair private immutable pair; 22 | 23 | constructor() { 24 | pair = IUniswapV2Pair(factory.getPair(DAI, WETH)); 25 | } 26 | 27 | function flashSwap(uint wethAmount) external { 28 | bytes memory data = abi.encode(WETH, msg.sender); 29 | pair.swap(0, wethAmount, address(this), data); 30 | } 31 | 32 | function uniswapV2Call( 33 | address sender, 34 | uint amount0, 35 | uint amount1, 36 | bytes calldata data 37 | ) external { 38 | require(msg.sender == address(pair), "not pair"); 39 | require(sender == address(this), "not sender"); 40 | 41 | (address tokenBorrow, address caller) = abi.decode( 42 | data, 43 | (address, address) 44 | ); 45 | uint fee = ((amount1 * 3) / 997) + 1; 46 | 47 | // Arbitrage... 48 | emit Log("amount", amount1); 49 | emit Log("fee", fee); 50 | emit Log("amount to repay", fee + amount1); 51 | 52 | weth.transferFrom(caller, address(this), amount1 + fee); 53 | weth.transfer(address(pair), amount1 + fee); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contracts/UniswapV2MultiHopSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "./IERC20.sol"; 5 | import "./IUniswapV2Router.sol"; 6 | import "./IUniswapV2Factory.sol"; 7 | 8 | contract UniswapV2MultiHopSwap { 9 | address private constant UNISWAP_V2_ROUTER = 10 | 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; 11 | 12 | address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 13 | address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 14 | address private constant CRV = 0xD533a949740bb3306d119CC777fa900bA034cd52; 15 | 16 | IUniswapV2Router private constant router = 17 | IUniswapV2Router(UNISWAP_V2_ROUTER); 18 | IERC20 private constant weth = IERC20(WETH); 19 | IERC20 private constant dai = IERC20(DAI); 20 | IERC20 private constant crv = IERC20(CRV); 21 | 22 | function swapMultiHopExactAmountIn(uint amountIn, uint amountOutMin) 23 | external 24 | { 25 | dai.transferFrom(msg.sender, address(this), amountIn); 26 | dai.approve(UNISWAP_V2_ROUTER, amountIn); 27 | 28 | address[] memory path = new address[](3); 29 | path[0] = DAI; 30 | path[1] = WETH; 31 | path[2] = CRV; 32 | 33 | router.swapExactTokensForTokens( 34 | amountIn, 35 | amountOutMin, 36 | path, 37 | msg.sender, 38 | block.timestamp 39 | ); 40 | } 41 | 42 | function swapMultiHopExactAmountOut(uint amountOutDesired, uint amountInMax) 43 | external 44 | { 45 | dai.transferFrom(msg.sender, address(this), amountInMax); 46 | dai.approve(UNISWAP_V2_ROUTER, amountInMax); 47 | 48 | address[] memory path = new address[](3); 49 | path[0] = DAI; 50 | path[1] = WETH; 51 | path[2] = CRV; 52 | 53 | uint[] memory amounts = router.swapTokensForExactTokens( 54 | amountOutDesired, 55 | amountInMax, 56 | path, 57 | msg.sender, 58 | block.timestamp 59 | ); 60 | 61 | if (amounts[0] < amountInMax) { 62 | dai.transfer(msg.sender, amountInMax - amounts[0]); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IUniswapV2Pair { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external pure returns (string memory); 8 | 9 | function symbol() external pure returns (string memory); 10 | 11 | function decimals() external pure returns (uint8); 12 | 13 | function totalSupply() external view returns (uint); 14 | 15 | function balanceOf(address owner) external view returns (uint); 16 | 17 | function allowance(address owner, address spender) 18 | external 19 | view 20 | returns (uint); 21 | 22 | function approve(address spender, uint value) external returns (bool); 23 | 24 | function transfer(address to, uint value) external returns (bool); 25 | 26 | function transferFrom( 27 | address from, 28 | address to, 29 | uint value 30 | ) external returns (bool); 31 | 32 | function DOMAIN_SEPARATOR() external view returns (bytes32); 33 | 34 | function PERMIT_TYPEHASH() external pure returns (bytes32); 35 | 36 | function nonces(address owner) external view returns (uint); 37 | 38 | function permit( 39 | address owner, 40 | address spender, 41 | uint value, 42 | uint deadline, 43 | uint8 v, 44 | bytes32 r, 45 | bytes32 s 46 | ) external; 47 | 48 | event Mint(address indexed sender, uint amount0, uint amount1); 49 | event Burn( 50 | address indexed sender, 51 | uint amount0, 52 | uint amount1, 53 | address indexed to 54 | ); 55 | event Swap( 56 | address indexed sender, 57 | uint amount0In, 58 | uint amount1In, 59 | uint amount0Out, 60 | uint amount1Out, 61 | address indexed to 62 | ); 63 | event Sync(uint112 reserve0, uint112 reserve1); 64 | 65 | function MINIMUM_LIQUIDITY() external pure returns (uint); 66 | 67 | function factory() external view returns (address); 68 | 69 | function token0() external view returns (address); 70 | 71 | function token1() external view returns (address); 72 | 73 | function getReserves() 74 | external 75 | view 76 | returns ( 77 | uint112 reserve0, 78 | uint112 reserve1, 79 | uint32 blockTimestampLast 80 | ); 81 | 82 | function price0CumulativeLast() external view returns (uint); 83 | 84 | function price1CumulativeLast() external view returns (uint); 85 | 86 | function kLast() external view returns (uint); 87 | 88 | function mint(address to) external returns (uint liquidity); 89 | 90 | function burn(address to) external returns (uint amount0, uint amount1); 91 | 92 | function swap( 93 | uint amount0Out, 94 | uint amount1Out, 95 | address to, 96 | bytes calldata data 97 | ) external; 98 | 99 | function skim(address to) external; 100 | 101 | function sync() external; 102 | 103 | function initialize(address, address) external; 104 | } 105 | -------------------------------------------------------------------------------- /contracts/UniswapV2Liquidity.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "./IUniswapV2Factory.sol"; 5 | import "./IUniswapV2Router.sol"; 6 | import "./IERC20.sol"; 7 | 8 | contract UniswapV2Liquidity { 9 | event Log(string message, uint val); 10 | 11 | address private constant UNISWAP_V2_ROUTER = 12 | 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; 13 | 14 | address private constant UNISWAP_V2_FACTORY = 15 | 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; 16 | 17 | address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 18 | address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 19 | 20 | IUniswapV2Router private constant router = 21 | IUniswapV2Router(UNISWAP_V2_ROUTER); 22 | IUniswapV2Factory private constant factory = 23 | IUniswapV2Factory(UNISWAP_V2_FACTORY); 24 | 25 | IERC20 private constant weth = IERC20(WETH); 26 | IERC20 private constant dai = IERC20(DAI); 27 | 28 | IERC20 private immutable pair; 29 | 30 | constructor() { 31 | pair = IERC20(factory.getPair(WETH, DAI)); 32 | } 33 | 34 | function addLiquidity(uint wethAmountDesired, uint daiAmountDesired) 35 | external 36 | { 37 | weth.transferFrom(msg.sender, address(this), wethAmountDesired); 38 | dai.transferFrom(msg.sender, address(this), daiAmountDesired); 39 | weth.approve(address(router), wethAmountDesired); 40 | dai.approve(address(router), daiAmountDesired); 41 | (uint wethAmount, uint daiAmount, uint liquidity) = router.addLiquidity( 42 | WETH, 43 | DAI, 44 | wethAmountDesired, 45 | daiAmountDesired, 46 | 1, 47 | 1, 48 | msg.sender, 49 | block.timestamp 50 | ); 51 | // Trying to get liquidity amount returned from the function call to fix issues met when testing 52 | // emit Log("wethAmount", wethAmount); 53 | // emit Log("daiAmount", daiAmount); 54 | emit Log("liquidity", liquidity); 55 | 56 | if (wethAmount < wethAmountDesired) { 57 | weth.transfer(msg.sender, wethAmountDesired - wethAmount); 58 | } 59 | 60 | if (daiAmount < daiAmountDesired) { 61 | dai.transfer(msg.sender, daiAmountDesired - daiAmount); 62 | } 63 | } 64 | 65 | // Trying to get liquidity 66 | function removeLiquidity() external { 67 | uint liquidity = IERC20(pair).balanceOf(msg.sender); 68 | require(liquidity > 0, "liquidity = 0"); 69 | pair.transferFrom(msg.sender, address(this), liquidity); 70 | pair.approve(address(router), liquidity); 71 | 72 | (uint wethAmount, uint daiAmount) = router.removeLiquidity( 73 | WETH, 74 | DAI, 75 | liquidity, 76 | 1, 77 | 1, 78 | msg.sender, 79 | block.timestamp 80 | ); 81 | 82 | emit Log("wethAmount", wethAmount); 83 | emit Log("daiAmount", daiAmount); 84 | } 85 | 86 | // function removeLiquidity(uint liquidity) external { 87 | // pair.transferFrom(msg.sender, address(this), liquidity); 88 | // pair.approve(address(router), liquidity); 89 | 90 | // router.removeLiquidity( 91 | // WETH, 92 | // DAI, 93 | // liquidity, 94 | // 1, 95 | // 1, 96 | // msg.sender, 97 | // block.timestamp 98 | // ); 99 | // } 100 | } 101 | -------------------------------------------------------------------------------- /test/uniswap.test.js: -------------------------------------------------------------------------------- 1 | // const { BigNumber } = require("@ethersproject/bignumber"); 2 | const { assert, expect } = require("chai"); 3 | const { ethers } = require("hardhat"); 4 | const { 5 | WETH_WHALE, 6 | DAI_WHALE, 7 | WETHDAI_WHALE, 8 | WETH_WHALE1, 9 | WETH, 10 | DAI, 11 | CRV, 12 | } = require("./config.js"); 13 | 14 | // Single Hop Swap ✅ 15 | describe("Uniswap v2 Single Hop Swap", function () { 16 | let TestSwapContract; 17 | 18 | beforeEach(async () => { 19 | const TestSwapFactory = await ethers.getContractFactory( 20 | "UniswapV2SingleHopSwap" 21 | ); 22 | TestSwapContract = await TestSwapFactory.deploy(); 23 | await TestSwapContract.deployed(); 24 | }); 25 | 26 | it("should swap", async () => { 27 | await hre.network.provider.request({ 28 | method: "hardhat_impersonateAccount", 29 | params: [WETH_WHALE], 30 | }); 31 | const impersonateSigner = await ethers.getSigner(WETH_WHALE); 32 | 33 | const WETHContract = await ethers.getContractAt("IERC20", WETH); 34 | 35 | const WETHHolderBalance = await WETHContract.balanceOf( 36 | impersonateSigner.address 37 | ); 38 | await WETHContract.connect(impersonateSigner).approve( 39 | TestSwapContract.address, 40 | WETHHolderBalance 41 | ); 42 | 43 | const DAIContract = await ethers.getContractAt("IERC20", DAI); 44 | 45 | const DAIHolderBalance = await DAIContract.balanceOf( 46 | impersonateSigner.address 47 | ); 48 | console.log( 49 | "Initial DAI Balance:", 50 | ethers.utils.formatUnits(DAIHolderBalance.toString()) 51 | ); 52 | 53 | console.log( 54 | "Initial WETH Balance:", 55 | ethers.utils.formatUnits(WETHHolderBalance.toString()) 56 | ); 57 | 58 | console.log( 59 | "-----------------------------SINGLE SWAP-----------------------------" 60 | ); 61 | 62 | await TestSwapContract.connect( 63 | impersonateSigner 64 | ).swapSingleHopExactAmountIn(WETHHolderBalance, 1); 65 | 66 | const daiBalance_updated = await DAIContract.balanceOf( 67 | impersonateSigner.address 68 | ); 69 | console.log( 70 | "DAI Balance after Swap:", 71 | ethers.utils.formatUnits(daiBalance_updated.toString()) 72 | ); 73 | const WETHHolderBalance_updated = await WETHContract.balanceOf( 74 | impersonateSigner.address 75 | ); 76 | console.log( 77 | `WETH Balance after Swap: ${ethers.utils.formatUnits( 78 | WETHHolderBalance_updated.toString() 79 | )}` 80 | ); 81 | 82 | assert.equal(WETHHolderBalance_updated.toString(), 0); 83 | }); 84 | }); 85 | 86 | // Multi Hop Swap ✅ 87 | describe("Uniswap v2 Multi Hop Swap", function () { 88 | let TestMultiSwapContract; 89 | 90 | beforeEach(async () => { 91 | const TestMultiSwapFactory = await ethers.getContractFactory( 92 | "UniswapV2MultiHopSwap" 93 | ); 94 | TestMultiSwapContract = await TestMultiSwapFactory.deploy(); 95 | await TestMultiSwapContract.deployed(); 96 | }); 97 | 98 | it("should Multiswap", async () => { 99 | await hre.network.provider.request({ 100 | method: "hardhat_impersonateAccount", 101 | params: [DAI_WHALE], 102 | }); 103 | const impersonateSigner = await ethers.getSigner(DAI_WHALE); 104 | 105 | const DAIContract = await ethers.getContractAt("IERC20", DAI); 106 | 107 | const DAIHolderBalance = await DAIContract.balanceOf( 108 | impersonateSigner.address 109 | ); 110 | 111 | await DAIContract.connect(impersonateSigner).approve( 112 | TestMultiSwapContract.address, 113 | DAIHolderBalance 114 | ); 115 | 116 | const CRVContract = await ethers.getContractAt("IERC20", CRV); 117 | 118 | const CRVHolderBalance = await CRVContract.balanceOf( 119 | impersonateSigner.address 120 | ); 121 | 122 | console.log( 123 | "Initial CRV Balance:", 124 | ethers.utils.formatUnits(CRVHolderBalance.toString()) 125 | ); 126 | 127 | console.log( 128 | "Initial DAI Balance:", 129 | ethers.utils.formatUnits(DAIHolderBalance.toString()) 130 | ); 131 | 132 | console.log( 133 | "------------------------------MULTI SWAP------------------------------" 134 | ); 135 | 136 | await TestMultiSwapContract.connect( 137 | impersonateSigner 138 | ).swapMultiHopExactAmountIn(DAIHolderBalance, 1); 139 | 140 | const crvBalance_updated = await CRVContract.balanceOf( 141 | impersonateSigner.address 142 | ); 143 | console.log( 144 | "CRV Balance after Swap:", 145 | ethers.utils.formatUnits(crvBalance_updated.toString()) 146 | ); 147 | const DAIHolderBalance_updated = await DAIContract.balanceOf( 148 | impersonateSigner.address 149 | ); 150 | 151 | console.log( 152 | "DAI Balance after Swap:", 153 | ethers.utils.formatUnits(DAIHolderBalance_updated.toString()) 154 | ); 155 | 156 | assert.equal(DAIHolderBalance_updated.toString(), 0); 157 | }); 158 | }); 159 | 160 | // Add Liquidity ✅ 161 | describe("Uniswap V2 Liquidity", function () { 162 | let TestLiquidityContract; 163 | 164 | beforeEach(async () => { 165 | const TestLiquidityFactory = await ethers.getContractFactory( 166 | "UniswapV2Liquidity" 167 | ); 168 | 169 | TestLiquidityContract = await TestLiquidityFactory.deploy(); 170 | await TestLiquidityContract.deployed(); 171 | }); 172 | it("should add liquidity", async () => { 173 | await hre.network.provider.request({ 174 | method: "hardhat_impersonateAccount", 175 | params: [WETHDAI_WHALE], 176 | // WETHDAI_WHALE holds WETH, DAI and ETH 177 | }); 178 | const impersonateSigner = await ethers.getSigner(WETHDAI_WHALE); 179 | 180 | const WETHContract = await ethers.getContractAt("IERC20", WETH); 181 | const DAIContract = await ethers.getContractAt("IERC20", DAI); 182 | // const UNIContract = await ethers.getContractAt("IERC20", UNI); 183 | const DAIBalanceBefore = await DAIContract.balanceOf( 184 | impersonateSigner.address 185 | ); 186 | const WETHBalanceBefore = await WETHContract.balanceOf( 187 | impersonateSigner.address 188 | ); 189 | 190 | await DAIContract.connect(impersonateSigner).approve( 191 | TestLiquidityContract.address, 192 | DAIBalanceBefore 193 | ); 194 | 195 | await WETHContract.connect(impersonateSigner).approve( 196 | TestLiquidityContract.address, 197 | WETHBalanceBefore 198 | ); 199 | 200 | console.log( 201 | `WETH Balannce before adding liquidity: ${ethers.utils.formatUnits( 202 | WETHBalanceBefore.toString() 203 | )}` 204 | ); 205 | console.log( 206 | `DAI Balance before adding liquidity: ${ethers.utils.formatUnits( 207 | DAIBalanceBefore.toString() 208 | )}` 209 | ); 210 | 211 | console.log( 212 | "----------------------------ADD LIQUIDITY----------------------------" 213 | ); 214 | 215 | let tx = await TestLiquidityContract.connect( 216 | impersonateSigner 217 | ).addLiquidity(WETHBalanceBefore, DAIBalanceBefore); 218 | 219 | let receipt = await tx.wait(); 220 | // console.log(receipt.logs); 221 | // console.log(receipt.events[0].topics.toString()); 222 | // console.log(receipt.events[0].args.message.toString()); 223 | // console.log(receipt.events[0].args.val.toString()); 224 | 225 | const WETHBalanceAfter = await WETHContract.balanceOf( 226 | impersonateSigner.address 227 | ); 228 | const DAIBalanceAfter = await DAIContract.balanceOf( 229 | impersonateSigner.address 230 | ); 231 | 232 | console.log( 233 | `WETH Balance after adding Liquidity: ${ethers.utils.formatUnits( 234 | WETHBalanceAfter.toString() 235 | )}` 236 | ); 237 | 238 | console.log( 239 | `DAI Balance after adding Liquidity: ${ethers.utils.formatUnits( 240 | DAIBalanceAfter.toString() 241 | )}` 242 | ); 243 | 244 | assert.isBelow( 245 | WETHBalanceAfter, 246 | WETHBalanceBefore, 247 | "WETH not added to liquidity" 248 | ); 249 | assert.isBelow( 250 | DAIBalanceAfter, 251 | DAIBalanceBefore, 252 | "DAI not added to liquidity" 253 | ); 254 | 255 | expect(tx).to.emit(TestLiquidityContract, "Log"); 256 | }); 257 | 258 | it("should remove liquidity", async () => { 259 | await hre.network.provider.request({ 260 | method: "hardhat_impersonateAccount", 261 | params: [WETHDAI_WHALE], 262 | // WETHDAI_WHALE holds WETH, DAI and ETH 263 | }); 264 | const impersonateSigner = await ethers.getSigner(WETHDAI_WHALE); 265 | 266 | const WETHContract = await ethers.getContractAt("IERC20", WETH); 267 | const DAIContract = await ethers.getContractAt("IERC20", DAI); 268 | const DAIBalanceBefore = await DAIContract.balanceOf( 269 | impersonateSigner.address 270 | ); 271 | const WETHBalanceBefore = await WETHContract.balanceOf( 272 | impersonateSigner.address 273 | ); 274 | console.log( 275 | `WETH Balance before removing Liquidity: ${ethers.utils.formatUnits( 276 | WETHBalanceBefore.toString() 277 | )}` 278 | ); 279 | 280 | console.log( 281 | `DAI Balance before removing Liquidity: ${ethers.utils.formatUnits( 282 | DAIBalanceBefore.toString() 283 | )}` 284 | ); 285 | 286 | //❌ 287 | //////////////////////////////////////////////////// 288 | ////////////////Stuck at this point//////////////// 289 | //////////////////////////////////////////////////// 290 | //can't find events emited on transaction receipt, 291 | // and also the transaction was reverted 292 | // (after calling remove liquidity) 293 | // with this error: 'ds-math-sub-underflow' 294 | //////////////////////////////////////////////////// 295 | //////////////////////////////////////////////////// 296 | 297 | console.log( 298 | "----------------------------REMOVE LIQUIDITY----------------------------" 299 | ); 300 | 301 | // let tx = await TestLiquidityContract.connect( 302 | // impersonateSigner 303 | // ).removeLiquidity(); 304 | 305 | // let receipt = await tx.wait(1); 306 | 307 | // console.log(receipt); 308 | 309 | // const WETHBalanceAfter = await WETHContract.balanceOf( 310 | // impersonateSigner.address 311 | // ); 312 | // const DAIBalanceAfter = await DAIContract.balanceOf( 313 | // impersonateSigner.address 314 | // ); 315 | 316 | // console.log( 317 | // `WETH Balance after removing Liquidity: ${ethers.utils.formatUnits( 318 | // WETHBalanceAfter.toString() 319 | // )}` 320 | // ); 321 | 322 | // console.log( 323 | // `DAI Balance after removing Liquidity: ${ethers.utils.formatUnits( 324 | // DAIBalanceAfter.toString() 325 | // )}` 326 | // ); 327 | }); 328 | }); 329 | 330 | // Flash Swap ✅ 331 | describe("Uniswap V2 Flash Swap", function () { 332 | let TestFlashContract; 333 | 334 | beforeEach(async () => { 335 | const TestFlashFactory = await ethers.getContractFactory( 336 | "UniswapV2FlashSwap" 337 | ); 338 | 339 | TestFlashContract = await TestFlashFactory.deploy(); 340 | await TestFlashContract.deployed(); 341 | }); 342 | it("flash swap", async () => { 343 | await hre.network.provider.request({ 344 | method: "hardhat_impersonateAccount", 345 | params: [WETH_WHALE1], 346 | // WETHDAI_WHALE holds WETH, DAI and ETH 347 | }); 348 | 349 | const impersonateSigner = await ethers.getSigner(WETH_WHALE1); 350 | 351 | const WETHContract = await ethers.getContractAt("IERC20", WETH); 352 | 353 | const WETHBalanceBefore = await WETHContract.balanceOf( 354 | impersonateSigner.address 355 | ); 356 | 357 | console.log( 358 | `Balance of WETH before the Flash Swap: ${ethers.utils.formatUnits( 359 | WETHBalanceBefore.toString() 360 | )}` 361 | ); 362 | 363 | await WETHContract.connect(impersonateSigner).approve( 364 | TestFlashContract.address, 365 | WETHBalanceBefore 366 | ); 367 | 368 | console.log( 369 | "----------------------------FLASH SWAP----------------------------" 370 | ); 371 | const amountFlash = ethers.utils.parseUnits("100"); 372 | await TestFlashContract.connect(impersonateSigner).flashSwap(amountFlash); 373 | 374 | const WETHBalanceAfter = await WETHContract.balanceOf( 375 | impersonateSigner.address 376 | ); 377 | 378 | console.log( 379 | `Balance of WETH AFTER the Flash Swap: ${ethers.utils.formatUnits( 380 | WETHBalanceAfter.toString() 381 | )}` 382 | ); 383 | assert(WETHBalanceAfter < WETHBalanceBefore); 384 | }); 385 | }); 386 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Contributors][contributors-shield]][contributors-url] 4 | [![Forks][forks-shield]][forks-url] 5 | [![Stargazers][stars-shield]][stars-url] 6 | [![Issues][issues-shield]][issues-url] 7 | [![MIT License][license-shield]][license-url] 8 | [![LinkedIn][linkedin-shield]][linkedin-url] 9 | 10 | 11 |
12 |
13 | 14 | Logo 15 | 16 | 17 |

Uniswap V2

18 | 19 |

20 | Uniswap V2 21 |
22 | Explore the docs » 23 |
24 |
25 | View Demo 26 | · 27 | Report Bug 28 | · 29 | Request Feature 30 |

31 |
32 | 33 | 34 |
35 | Table of Contents 36 |
    37 |
  1. 38 | About The Project 39 | 42 |
  2. 43 |
  3. 44 | Getting Started 45 | 49 |
  4. 50 |
  5. Usage
  6. 51 |
  7. Constant Product AMM
  8. 52 |
  9. Impermanent Loss on Uniswap v2
  10. 53 |
90 |
91 | 92 | 93 | 94 | ## About The Project 95 | 96 | This project shows how to interact with the main functions of Uniswap V2 97 | 98 |

(back to top)

99 | 100 | ### Built With 101 | 102 | - [![Hardhat][Hardhat]][Hardhat-url] 103 | - [![Ethers][Ethers.js]][Ethers-url] 104 | 105 |

(back to top)

106 | 107 | 108 | 109 | ## Getting Started 110 | 111 | To get a local copy up and running follow these simple example steps. 112 | 113 | ### Prerequisites 114 | 115 | - npm 116 | 117 | ```sh 118 | npm install npm@latest -g 119 | ``` 120 | 121 | - hardhat 122 | 123 | ```sh 124 | npm install --save-dev hardhat 125 | ``` 126 | 127 | ```sh 128 | npm install @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle 129 | ``` 130 | 131 | run: 132 | 133 | ```sh 134 | npx hardhat 135 | ``` 136 | 137 | verify: 138 | 139 | ```sh 140 | npx hardhat verify --network goerli "contract address" "pair address" 141 | ``` 142 | 143 | ### Installation 144 | 145 | 1. Clone the repo 146 | ```sh 147 | git clone https://github.com/Aboudoc/Uniswap-v2.git 148 | ``` 149 | 2. Install NPM packages 150 | ```sh 151 | npm install 152 | ``` 153 | 3. Dependencies 154 | 155 | ```sh 156 | npm i @uniswap/v2-core @uniswap/v2-periphery 157 | ``` 158 | 159 |

(back to top)

160 | 161 | 162 | 163 | ## Usage 164 | 165 | If you need testnet funds, use the [Alchemy testnet faucet](https://goerlifaucet.com/). 166 | 167 | **This project shows how to swap, add and remove liquidity** 168 | 169 | ## Constant Product AMM 170 | 171 | Uniswap V2 is a Constant product AMM (automated market maker) <=> a decentralized exchange where 2 tokens are traded. 172 | You can find a deep overview of CPAMM in [this repo](https://github.com/Aboudoc/Constant-Product-AMM) 173 | 174 | ## Impermanent Loss on Uniswap v2 175 | 176 | How to calculate Impermanent Loss in a constant product AMM? 177 | 178 | If the price does not change at all, the d = 1 and there is no loss in providing liquidity to CPAMM 179 | 180 | if the price changes either down or up, then the LP will experience some loss 181 | 182 | ### How to calculate impermanent loss 183 | 184 |
185 | Test 186 |
187 | 188 | Let's see how to derive this equation. Find `x` and `y` 189 | 190 |
191 | Test 192 |
193 | 194 | We solved `y` and `x` in terms of `L` and `P`. We are now ready to solve `IL(d)` 195 | 196 |
197 | Test 198 |
199 | 200 | Le's find out what are `V1` and `Vhold` in terms of token `y`. 201 | 202 | Below we multiplied `x1` by `P1` to get the price of `x1` in term of `y` 203 | 204 |
205 | Test 206 |
207 | 208 | We are now ready to solve the equation of `IL(d)` 209 | 210 |
211 | Test 212 |
213 | 214 |

(back to top)

215 | 216 | ## Test 217 | 218 |
219 | Test 220 |
221 | 222 | ## Uniswap V2 Single Hop Swap 223 | 224 | This contract introduces 2 functions to swap tokens on Uniswap V2 225 | 226 | `swapExactTokensForTokens` - Sell all of input token. 227 | `swapTokensForExactTokens` - Buy specific amount of output token. 228 | 229 | ### State variables 230 | 231 | 1. Address of tokens (2 or 3) and the address of the router 232 | 2. Set interfaces for tokens and router 233 | 234 | ### Function swapSingleHopExactAmountIn 235 | 236 | 1. Transfer `amountIn` from `msg.sender` 237 | 2. Approve `amountIn` to `router` 238 | 3. Set the `path` 239 | 4. Call `swapExactTokensForTokens` on IUniswapV2Router 240 | 241 | ### Function swapSingleHopExactAmountOut 242 | 243 | 1. Transfer `amountInMax`from `msg.sender` 244 | 2. Approve `amountInMax` to `router` 245 | 3. Set the `path` 246 | 4. Call `swapTokensForExactTokens` on IUniswapV2Router and store amount of WETH spent by Uniswap in amounts (uint[]) 247 | 5. Refund excess WETH to `msg.sender`. Amount of WETH spent by Uniswap is stored in amounts[0] 248 | 249 | ## Uniswap V2 Multi Hop Swap 250 | 251 | Sell DAI and buy CRV. 252 | 253 | However there is no DAI - CRV pool, so we will execute multi hop swaps, DAI to WETH and then WETH to CRV. 254 | 255 | ### State variables 256 | 257 | 1. Address of tokens and the address of the router 258 | 2. Set interfaces for tokens and router 259 | 260 | ### Function swapMultiHopExactAmountIn 261 | 262 | This function will swap `all` of DAI for `maximum amount` of CRV. It will execute multi hop swaps from DAI to WETH and then WETH to CRV. 263 | 264 | 1. Transfer `amountIn` from `msg.sender` 265 | 2. Approve `amountIn` to `router` 266 | 3. Setup the swapping `path` 267 | 4. Send CRV to msg.sender 268 | 269 | ### Function swapMultiHopExactAmountOut 270 | 271 | This function will swap `minimum` DAI to obtain a `specific amount` of CRV. It will execute multi hop swaps from DAI to WETH and then WETH to CRV. 272 | 273 | 1. Transfer `amountInMax`from `msg.sender` 274 | 2. Approve `amountInMax` to `router` 275 | 3. Setup the swapping `path` 276 | 4. Call `swapTokensForExactTokens` on IUniswapV2Router and store amount of DAI spent by Uniswap in amounts (uint[]) 277 | 5. Refund DAI to `msg.sender` if not all of DAI was spent. Amount of DAI spent by Uniswap is stored in amounts[0] 278 | 279 |

(back to top)

280 | 281 | ## Uniswap V2 Liquidity Delta 282 | 283 | When we add or remove tokens from a Uniswap V2 pool, the liquidity changes. 284 | 285 | Let's see how to derive the liquidity delta 286 | 287 | As always, we start with the definition: 288 | 289 |
290 | Test 291 |
292 | 293 | Preliminary math to derive these equations (green square) 294 | 295 |
296 | Test 297 |
298 | 299 | Now we have the math needed, let's now derive the equation for the liquidity delta 300 | 301 |
302 | Test 303 |
304 | 305 |

(back to top)

306 | 307 | ## Uniswap V2 Add and Remove Liquidity 308 | 309 | Deposit your tokens into an Uniswap V2 pool to earn trading fees. 310 | 311 | This is called adding liquidity. 312 | 313 | Remove liquidity to withdraw your tokens and claim your trading fees. 314 | 315 | ### State variables 316 | 317 | 1. Address of tokens and the addresses of the router and the factory. Declare pair variable 318 | 2. Set interfaces for tokens, router and factory 319 | 320 | ### Constructor 321 | 322 | 1. Setup pair (IERC20) by calling getPair() on factory 323 | 324 | ### Function addLiquidity 325 | 326 | This function adds liquidity to the Uniswap WETH - DAI pool. 327 | 328 | 1. Transfer `wethAmountDesired` and `daiAmountDesired` from `msg.sender` 329 | 2. Approve `amountwethAmountDesired` and `daiAmountDesired` to `router` 330 | 3. Call `addLiqiuidity()` on `router` and store `wethAmount`, `daiAmount` and `liquidity` returned from the function call 331 | 4. Refund to msg.sender, excess WETH and DAI that were not added to liquidity 332 | 333 | ### Function removeLiquidity 334 | 335 | This function removes liquidity from the Uniswap WETH - DAI pool. 336 | 337 | 1. Transfer `liquidity` from `msg.sender` 338 | 2. Approve `liquidity` to `router` 339 | 3. Call `removeLiqiuidity()` on `router` 340 | 341 |

(back to top)

342 | 343 | ## Uniswap V2 Flash Swap 344 | 345 | Tokens in the pool can be borrowed as long as they are repaid in the same transaction plus fee on borrow. 346 | 347 | This is called **flash swap**. 348 | 349 | The contract inherit from `IUniswapV2Callee` 350 | 351 | ### State Variables 352 | 353 | 1. Address of tokens and the address of the factory 354 | 2. Set WETH and factory interface then declare pair (IUniswapV2Pair) 355 | 356 | ### Constructor 357 | 358 | 1. Call getPair() on factory and store the result inside pair variable (which is a IUniswapV2Pair interface) 359 | 360 | ### Funuction flashSwap 361 | 362 | 1. Prepare data of bytes to send. This can be any data, as long as it is not empty Uniswap will trigger a flash swap. For this example, we encode WETH and msg.sender. 363 | 2. Call `swap()`on pair. Find below `swap()` from `IUniswapV2Pair` 364 | 365 | ```js 366 | function swap( 367 | uint amount0Out, 368 | uint amount1Out, 369 | address to, 370 | bytes calldata data 371 | ) external; 372 | 373 | ``` 374 | 375 | `amount0Out`: Amount of token0 to withdraw from the pool => 0 376 | 377 | `amount1Out`:Amount of token1 to withdraw from the pool => wethAmount 378 | 379 | `to`: Recipient of tokens in the pool => address(this) 380 | 381 | `data`: Data to send to uniswapV2Call => data 382 | 383 | ### Function uniswapV2Call 384 | 385 | This function is called by the DAI/WETH pair contract after we called pair.swap. 386 | 387 | Immediately before the pool calls this function, the amount of tokens that we requested to borrow is sent. Inside this function, we write our custom code and then repay the borrowed amount plus some fees. 388 | 389 | 1. Require that `msg.sender` is pair. Only pair contract should be able to call this function. 390 | 2. Require `sender` is this contract. Initiator of the flash swap should be this contract. 391 | 3. Decode `data`. Inside flashSwap we've encoded WETH and msg.sender. 392 | 4. Once the data is decoded, we would write our custom code here (arbitrage). We only emitted events for this example 393 | 5. Calculate total amount to repay 394 | 6. Transfer fee amount of WETH from caller (about 0.3% fee, +1 to round up) 395 | 7. Repay WETH to pair, amount borrowed plus fee 396 | 397 |

(back to top)

398 | 399 | ## Forking mainnet 400 | 401 | When we fork 🍴 the mainnet, we have the current state of the blockchain running locally on our system, including all contracts deployed on it and all transactions performed on it. 402 | 403 | 1. Setup hardhat.config 404 | 2. Find a whale on etherscan 405 | 406 | `hardhat.config.js` 407 | 408 | ```sh 409 | networks: { 410 | hardhat: { 411 | forking: { 412 | url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`, 413 | }, 414 | }, 415 | } 416 | ``` 417 | 418 | Note: Replace the `${}` component of the URL with your personal [Alchemy](https://www.alchemy.com/) API key. 419 | 420 | `.config` 421 | 422 | ```js 423 | const DAI = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; 424 | const DAI_WHALE = process.env.DAI_WHALE; 425 | 426 | module.exports = { 427 | DAI, 428 | DAI_WHALE, 429 | }; 430 | ``` 431 | 432 | `.env` 433 | 434 | ```sh 435 | ALCHEMY_API_KEY=... 436 | ``` 437 | 438 | `Terminal 1` 439 | 440 | ```sh 441 | npx hardhat test --network localhost 442 | ``` 443 | 444 | `Terminal 2` 445 | 446 | ```sh 447 | ALCHEMY_API_KEY=... 448 | npx hardhat node --fork https://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_API_KEY 449 | ``` 450 | 451 |

(back to top)

452 | 453 | ## Note 454 | 455 | This contract assumes that token0 and token1 both have same decimals 456 | 457 | Consider Uniswap trading fee = 0.3% 458 | 459 | ### Further reading 460 | 461 | (...soon) 462 | 463 | ### Sources 464 | 465 |

(back to top)

466 | 467 | 468 | 469 | ## Roadmap 470 | 471 | - [ ] Uniswap V3 TWAP 472 | - [ ] Further reading 473 | - [ ] Deploy script 474 | - [ ] Unit test 475 | 476 | See the [open issues](https://github.com/Aboudoc/Uniswap-v2.git/issues) for a full list of proposed features (and known issues). 477 | 478 |

(back to top)

479 | 480 | 481 | 482 | ## Contributing 483 | 484 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 485 | 486 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". 487 | Don't forget to give the project a star! Thanks again! 488 | 489 | 1. Fork the Project 490 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 491 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 492 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 493 | 5. Open a Pull Request 494 | 495 |

(back to top)

496 | 497 | 498 | 499 | ## License 500 | 501 | Distributed under the MIT License. See `LICENSE.txt` for more information. 502 | 503 |

(back to top)

504 | 505 | 506 | 507 | ## Contact 508 | 509 | Reda Aboutika - [@twitter](https://twitter.com/AboutikaR) - reda.aboutika@gmail.com 510 | 511 | Project Link: [https://github.com/Aboudoc/Uniswap-v2.git](https://github.com/Aboudoc/Uniswap-v2.git) 512 | 513 |

(back to top)

514 | 515 | 516 | 517 | ## Acknowledgments 518 | 519 | - [Smart Contract Engineer](https://www.smartcontract.engineer/) 520 | 521 |

(back to top)

522 | 523 | 524 | 525 | 526 | [contributors-shield]: https://img.shields.io/github/contributors/Aboudoc/Uniswap-v2.svg?style=for-the-badge 527 | [contributors-url]: https://github.com/Aboudoc/Uniswap-v2/graphs/contributors 528 | [forks-shield]: https://img.shields.io/github/forks/Aboudoc/Uniswap-v2.svg?style=for-the-badge 529 | [forks-url]: https://github.com/Aboudoc/Uniswap-v2/network/members 530 | [stars-shield]: https://img.shields.io/github/stars/Aboudoc/Uniswap-v2.svg?style=for-the-badge 531 | [stars-url]: https://github.com/Aboudoc/Uniswap-v2/stargazers 532 | [issues-shield]: https://img.shields.io/github/issues/Aboudoc/Uniswap-v2.svg?style=for-the-badge 533 | [issues-url]: https://github.com/Aboudoc/Uniswap-v2/issues 534 | [license-shield]: https://img.shields.io/github/license/Aboudoc/Uniswap-v2.svg?style=for-the-badge 535 | [license-url]: https://github.com/Aboudoc/Uniswap-v2/blob/master/LICENSE.txt 536 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 537 | [linkedin-url]: https://www.linkedin.com/in/r%C3%A9da-aboutika-34305453/?originalSubdomain=fr 538 | [product-screenshot]: https://ethereum.org/static/28214bb68eb5445dcb063a72535bc90c/9019e/hero.webp 539 | [Hardhat]: https://img.shields.io/badge/Hardhat-20232A?style=for-the-badge&logo=hardhat&logoColor=61DAFB 540 | [Hardhat-url]: https://hardhat.org/ 541 | [Ethers.js]: https://img.shields.io/badge/ethers.js-000000?style=for-the-badge&logo=ethersdotjs&logoColor=white 542 | [Ethers-url]: https://docs.ethers.org/v5/ 543 | [Vue.js]: https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D 544 | [Vue-url]: https://vuejs.org/ 545 | [Angular.io]: https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white 546 | [Angular-url]: https://angular.io/ 547 | [Svelte.dev]: https://img.shields.io/badge/Svelte-4A4A55?style=for-the-badge&logo=svelte&logoColor=FF3E00 548 | [Svelte-url]: https://svelte.dev/ 549 | [Laravel.com]: https://img.shields.io/badge/Laravel-FF2D20?style=for-the-badge&logo=laravel&logoColor=white 550 | [Laravel-url]: https://laravel.com 551 | [Bootstrap.com]: https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white 552 | [Bootstrap-url]: https://getbootstrap.com 553 | [JQuery.com]: https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge&logo=jquery&logoColor=white 554 | [JQuery-url]: https://jquery.com 555 | --------------------------------------------------------------------------------