├── .gitignore ├── images ├── Execution.PNG ├── TXResult.PNG ├── summary.jpg └── Deployment.PNG ├── LICENSE ├── FlashArbitragerV2.sol ├── README.md └── FlashArbitrageBot.sol /.gitignore: -------------------------------------------------------------------------------- 1 | .gitattributes -------------------------------------------------------------------------------- /images/Execution.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelinfosec/flash-arb-bot/HEAD/images/Execution.PNG -------------------------------------------------------------------------------- /images/TXResult.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelinfosec/flash-arb-bot/HEAD/images/TXResult.PNG -------------------------------------------------------------------------------- /images/summary.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelinfosec/flash-arb-bot/HEAD/images/summary.jpg -------------------------------------------------------------------------------- /images/Deployment.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelinfosec/flash-arb-bot/HEAD/images/Deployment.PNG -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Fiona Kobayashi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /FlashArbitragerV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | /** 5 | Ropsten instances: 6 | - Uniswap V2 Router: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D 7 | - Sushiswap V1 Router: No official sushi routers on testnet 8 | - DAI: 0xf80A32A835F79D7787E8a8ee5721D0fEaFd78108 9 | - ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 10 | - Aave LendingPoolAddressesProvider: 0x1c8756FD2B28e9426CDBDcC7E3c4d64fa9A54728 11 | 12 | Mainnet instances: 13 | - Uniswap V2 Router: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D 14 | - Sushiswap V1 Router: 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F 15 | - DAI: 0x6B175474E89094C44Da98b954EedeAC495271d0F 16 | - ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 17 | - Aave LendingPoolAddressesProvider: 0x24a42fD28C976A61Df5D00D0599C34c4f90748c8 18 | */ 19 | 20 | import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; 21 | import '@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol'; 22 | 23 | import './interfaces/IERC3156FlashBorrower.sol'; 24 | import './interfaces/IERC3156FlashLender.sol'; 25 | 26 | contract ArbitragerV2 is IERC3156FlashBorrower { 27 | 28 | enum Direction { 29 | UNISWAP_SUSHISWAP, 30 | SUSHISWAP_UNISWAP 31 | } 32 | 33 | struct ExtraData { 34 | address swapToken; 35 | Direction direction; 36 | uint256 deadline; 37 | uint256 amountRequired; 38 | address profitReceiver; 39 | uint256 minAmountSwapToken; 40 | uint256 minAmountBorrowedToken; 41 | } 42 | 43 | IERC3156FlashLender public immutable lender; 44 | IUniswapV2Router02 public immutable uniswapRouter; 45 | IUniswapV2Router02 public immutable sushiswapRouter; 46 | 47 | constructor( 48 | IERC3156FlashLender _lender, 49 | IUniswapV2Router02 _uniswapRouter, 50 | IUniswapV2Router02 _sushiswapRouter 51 | ) { 52 | lender = _lender; 53 | uniswapRouter = _uniswapRouter; 54 | sushiswapRouter = _sushiswapRouter; 55 | } 56 | 57 | function onFlashLoan( 58 | address initiator, 59 | address borrowedToken, 60 | uint256 amount, 61 | uint256 fee, 62 | bytes calldata data 63 | ) external override returns (bytes32) { 64 | require(msg.sender == address(lender), 'FLASH_BORROWER_UNTRUSTED_LENDER'); 65 | require(initiator == address(this), 'FLASH_BORROWER_LOAN_INITIATOR'); 66 | 67 | IERC20 borrowedTokenContract = IERC20(borrowedToken); 68 | ExtraData memory extraData = abi.decode(data, (ExtraData)); 69 | (IUniswapV2Router02 router1, IUniswapV2Router02 router2) = _getRouters(extraData.direction); 70 | 71 | // Call protocol 1 72 | uint256 amountReceivedSwapToken = _protocolCall( 73 | router1, 74 | borrowedToken, 75 | extraData.swapToken, 76 | amount, 77 | extraData.minAmountSwapToken, 78 | extraData.deadline 79 | ); 80 | 81 | // Call protocol 2 82 | uint256 amountReceivedBorrowedToken = _protocolCall( 83 | router2, 84 | extraData.swapToken, 85 | borrowedToken, 86 | amountReceivedSwapToken, 87 | extraData.minAmountBorrowedToken, 88 | extraData.deadline 89 | ); 90 | 91 | uint256 repay = amount + fee; 92 | 93 | // Transfer profits 94 | borrowedTokenContract.transfer( 95 | extraData.profitReceiver, 96 | amountReceivedBorrowedToken - repay - extraData.amountRequired 97 | ); 98 | 99 | // Approve lender 100 | borrowedTokenContract.approve(address(lender), repay); 101 | 102 | return keccak256('ERC3156FlashBorrower.onFlashLoan'); 103 | } 104 | 105 | function arbitrage( 106 | address borrowedToken, 107 | uint256 amount, 108 | ExtraData memory extraData 109 | ) public { 110 | _checkAmountOut(borrowedToken, amount, extraData); 111 | bytes memory _data = abi.encode(extraData); 112 | lender.flashLoan(this, borrowedToken, amount, _data); 113 | } 114 | 115 | function _checkAmountOut( 116 | address borrowedToken, 117 | uint256 amount, 118 | ExtraData memory extraData 119 | ) internal view { 120 | (IUniswapV2Router02 router1, IUniswapV2Router02 router2) = _getRouters(extraData.direction); 121 | 122 | uint256 amountOutSwapToken = _getAmountOut(router1, borrowedToken, extraData.swapToken, amount); 123 | uint256 amountOutBorrowedToken = _getAmountOut(router2, extraData.swapToken, borrowedToken, amountOutSwapToken); 124 | require(amountOutBorrowedToken >= extraData.minAmountBorrowedToken, 'ARBITRAGER_AMOUNT_OUT_TOO_LOW'); 125 | } 126 | 127 | function _getRouters(Direction direction) internal view returns (IUniswapV2Router02, IUniswapV2Router02) { 128 | if (direction == Direction.SUSHISWAP_UNISWAP) { 129 | return (sushiswapRouter, uniswapRouter); 130 | } 131 | 132 | return (uniswapRouter, sushiswapRouter); 133 | } 134 | 135 | function _getAmountOut( 136 | IUniswapV2Router02 router, 137 | address token1, 138 | address token2, 139 | uint256 amount 140 | ) internal view returns (uint256) { 141 | address[] memory path = new address[](2); 142 | path[0] = token1; 143 | path[1] = token2; 144 | 145 | return router.getAmountsOut(amount, path)[1]; 146 | } 147 | 148 | function _protocolCall( 149 | IUniswapV2Router02 router, 150 | address token1, 151 | address token2, 152 | uint256 amount, 153 | uint256 minAmount, 154 | uint256 deadline 155 | ) internal returns (uint256) { 156 | address[] memory path = new address[](2); 157 | path[0] = token1; 158 | path[1] = token2; 159 | IERC20(token1).approve(address(router), amount); 160 | 161 | return router.swapExactTokensForTokens(amount, minAmount, path, address(this), deadline)[1]; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flash Loan Arbitrage Bot 2 | This repo holds a functional implementation of flash loan and arbitrage swap on Ethereum developed and executed last year. 3 | 4 | ## Why are you releasing it? 5 | It created lower-than-expected returns, so I moved on. I think it might be useful for others as learning material. 6 |

7 | Even though it made some [profits live](https://ethtx.info/mainnet/0x0f3a3dbc5d6887c08b4f1bf039b3676aa4ba0256e7e4f6d6d94ceb0e50fff9ea/), it wasn't up to my expectations. Although there were some little miscalculations, here is a summary of one of my profitable transactions: 8 | 9 | ![](https://raw.githubusercontent.com/manuelinfosec/flash-arb-bot/main/images/summary.jpg) 10 | 11 | I made two other iterations of this arbitrage which will be released shortly, with similar commentary. 12 | 13 | ## Overview 14 | 15 | This is a simple working example of a flash arbitrage smart contract. Within a single transaction it: 16 | 1. Instantly flash borrows a certain asset (ETH in this example) from Aave lending pools with zero collateral 17 | 2. Calls UniswapV2 Router02 to wrap the flash liquidity of ETH into WETH and exchange it for DAI tokens 18 | 3. Checks the exchange rate of DAI back into ETH on Sushiswap V1 19 | 4. Calls SushiswapV1 Router02 to swap the DAI back into WETH and then ETH 20 | 5. There's also an independent function to withdraw all ETH and ERC20 tokens at the contract owner's discretion 21 | 22 | Before you start playing with this I highly recommend to have a read of the [Aave Flash Loan mechanism](https://aave.com/flash-loans) and get an indepth conceptual understanding, as it's equally important as understanding the code. 23 | 24 | Since Sushiswap is a fork of UniswapV2, I also suggest familiarising yourself with the Uniswap V2 guide on [trading via smart contracts](https://uniswap.org/docs/v2/smart-contract-integration/trading-from-a-smart-contract/), particularly if you plan on adding more swaps to your arbitrage strategy. 25 | 26 | 27 | ## Deployment 28 | 29 | The contract can be deployed unto Remix, using solidity compiler 0.6.12, and Metamask using Injected Web3. 30 | 31 | On deployment, set the following parameters: 32 | 33 | ![](https://raw.githubusercontent.com/manuelinfosec/flash-arb-bot/main/images/Deployment.PNG) 34 | 35 | - ***_AaveLendingPool:*** the LendingPoolAddressesProvider address corresponding to the deployment environment. see [Deployed Contract Instances](https://docs.aave.com/developers/deployed-contracts/deployed-contract-instances). 36 | - ***_UniswapV2Router:*** the Router02 address for UniswapV2 see [here](https://uniswap.org/docs/v2/smart-contracts/router02/). 37 | - ***_SushiswapV1Router:*** the Router02 address for SushiswapV1. There isn't an official testnet router02 so for demo purposes you can just use the uniswapV2 address when playing on the testnet since their codebase is identical (for now - which may not be the case in the future). Alternatively see [Sushiswap repo](https://github.com/sushiswap/sushiswap) for the mainnet router02 address to test in prod or deploy your own version of Router02 onto testnet. 38 | - Click 'transact' and approve the Metamask pop up. 39 | - Once the flash arb contract is deployed, send some ETH or ERC20 token to this contract depending on what asset you're planning to flash borrow from Aave in case you need extra funds to cover the flash fee. 40 | 41 | 42 | ## Execution 43 | 44 | On execution, set the following parameters: 45 | 46 | ![](https://raw.githubusercontent.com/manuelinfosec/flash-arb-bot/main/images/Execution.PNG) 47 | 48 | - ***_flashAsset:*** address of the asset you want to flash loan. e.g. ETH is 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE. If you want to flash anything else see [Reserved Assets](https://docs.aave.com/developers/deployed-contracts/deployed-contract-instances#reserves-assets) but you will need to adjust the executeArbitrage() function accordingly. 49 | - ***_flashAmount:*** how much of _flashAsset you want to borrow, demoniated in wei (e.g. 1000000000000000000 for 1 ether). 50 | - ***_daiTokenAddress:*** for this demo we're swapping with the DAI token, so lookup the reserved address of the DAI token. See [Reserved Assets](https://docs.aave.com/developers/deployed-contracts/deployed-contract-instances#reserves-assets). 51 | - ***_amountToTrade:*** how much of the newly acquired _flashAsset you'd like to use as part of this arbitrage. 52 | - ***_tokensOut:*** how much of the ERC20 tokens from the first swap would you like to swap back to complete the arb. Denominated in actual tokens, i.e. 1 = 1 DAI token. 53 | - Click 'transact' and approve in Metamask. 54 | 55 | 56 | 57 | ## Result 58 | ![](https://raw.githubusercontent.com/manuelinfosec/flash-arb-bot/main/images/TXResult.PNG) 59 | 60 | If all goes well, a successful execution of this contract looks like [this (Ropsten testnet)](https://ropsten.etherscan.io/tx/0xc1da19c7a5e189b372ec3b310453d7ee267da5df661ee61833230470e5b97fd8). 61 | 62 | ## Updates 63 | - Added V2 contract for a more recent deployment. 64 | - Expected to provide more promising results for arbitraging with flash loans. 65 | 66 | ## Tips for further customization 67 | - This contract would typically be executed by a Web3py bot (beyond this scope) via a web3.eth.Contract() call, referencing the deployed address of this contract and its corresponding ABI. You would usually get the bot to interact with price aggregators such as [1inch](https://1inch.exchange) to assess arb opportunities and execute this contract if the right opportunity is found. 68 | - To have any chance of getting in front of other arbitrage bots on significant arb opportunities the Web3py bot needs to be hosted on your own fast Ethereum node. You will most likely come off second best going through the Infura API to interact with the Ethereum blockchain. 69 | - Some people like to get an unfair advantage by building Transaction-Ordering Dependence (front running) capabilities into the Web3py. However this smart contract would then need to be significantly more complex and flexible enough to cater for a wide range of arbitrage permutations across multiple protocols. 70 | - User specified parameters (as opposed to hardcoded variables) should be passed via the flashloan() function in the first instance. You can subsequently set these parameters to contract variables with higher visibility across the contract. 71 | - There are no direct ETH pairs in UniswapV2 therefore the need for a WETH wrapper. Since Sushiswap is forked from UniswapV2 you'll need to wrap in WETH as well. 72 | 73 | ## Connect with me 74 | If you appreciate this, leave the repo a star and feel free to follow me on: 75 | 76 | [![Manuel Twitter](https://img.shields.io/badge/Twitter-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white)](https://twitter.com/manuelinfoec) 77 | [![Chiemezie Njoku Linkedin](https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/manuelinfosec/) 78 | [![Manuel Medium](https://img.shields.io/badge/Medium-000000?style=for-the-badge&logo=medium&logoColor=white)](https://manuelinfosec.medium.com/) 79 | 80 | ## Appreciation 81 | You could also donate: 82 | 83 | Ethereum/Binance Smart Chain/Polygon/Avalanche/etc address: 0xE882D838eF07e796bf6b19636931F143e3eC4Dc3 84 |

-------------------------------------------------------------------------------- /FlashArbitrageBot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | /** 5 | Ropsten instances: 6 | - Uniswap V2 Router: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D 7 | - Sushiswap V1 Router: No official sushi routers on testnet 8 | - DAI: 0xf80A32A835F79D7787E8a8ee5721D0fEaFd78108 9 | - ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 10 | - Aave LendingPoolAddressesProvider: 0x1c8756FD2B28e9426CDBDcC7E3c4d64fa9A54728 11 | 12 | Mainnet instances: 13 | - Uniswap V2 Router: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D 14 | - Sushiswap V1 Router: 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F 15 | - DAI: 0x6B175474E89094C44Da98b954EedeAC495271d0F 16 | - ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 17 | - Aave LendingPoolAddressesProvider: 0x24a42fD28C976A61Df5D00D0599C34c4f90748c8 18 | */ 19 | 20 | // importing flash loan dependencies as per https://docs.aave.com/developers/tutorials/performing-a-flash-loan/...-with-remix 21 | import "https://github.com/aave/flashloan-box/blob/Remix/contracts/aave/FlashLoanReceiverBase.sol"; 22 | import "https://github.com/aave/flashloan-box/blob/Remix/contracts/aave/ILendingPoolAddressesProvider.sol"; 23 | import "https://github.com/aave/flashloan-box/blob/Remix/contracts/aave/ILendingPool.sol"; 24 | 25 | // importing both Sushiswap V1 and Uniswap V2 Router02 dependencies 26 | import "https://github.com/sushiswap/sushiswap/blob/master/contracts/uniswapv2/interfaces/IUniswapV2Router02.sol"; 27 | import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol"; 28 | 29 | contract FlashArbBot is FlashLoanReceiverBase { 30 | 31 | using SafeMath for uint256; 32 | IUniswapV2Router02 uniswapV2Router; 33 | IUniswapV2Router02 sushiswapV1Router; 34 | uint deadline; 35 | IERC20 dai; 36 | address daiTokenAddress; 37 | uint256 amountToTrade; 38 | uint256 tokensOut; 39 | 40 | /** 41 | Initialize deployment parameters 42 | */ 43 | constructor( 44 | address _aaveLendingPool, 45 | IUniswapV2Router02 _uniswapV2Router, 46 | IUniswapV2Router02 _sushiswapV1Router 47 | ) FlashLoanReceiverBase(_aaveLendingPool) public { 48 | 49 | // instantiate SushiswapV1 and UniswapV2 Router02 50 | sushiswapV1Router = IUniswapV2Router02(address(_sushiswapV1Router)); 51 | uniswapV2Router = IUniswapV2Router02(address(_uniswapV2Router)); 52 | 53 | // setting deadline to avoid mining wait 54 | deadline = block.timestamp + 300; // 5 minutes 55 | } 56 | 57 | /** 58 | Mid-flashloan logic i.e. what you do with the temporarily acquired flash liquidity 59 | */ 60 | function executeOperation( 61 | address _reserve, 62 | uint256 _amount, 63 | uint256 _fee, 64 | bytes calldata _params 65 | ) 66 | external 67 | override 68 | { 69 | require(_amount <= getBalanceInternal(address(this), _reserve), "Invalid balance"); 70 | 71 | // execute arbitrage strategy 72 | try this.executeArbitrage() { 73 | } catch Error(string memory) { 74 | // Reverted with a reason string provided 75 | } catch (bytes memory) { 76 | // failing assertion 77 | } 78 | 79 | // return the flash loan plus Aave's flash loan fee back to the lending pool 80 | uint totalDebt = _amount.add(_fee); 81 | transferFundsBackToPoolInternal(_reserve, totalDebt); 82 | } 83 | 84 | /** 85 | The specific cross protocol swaps that makes up your arb strategy 86 | UniswapV2 -> SushiswapV1 example below 87 | */ 88 | function executeArbitrage() public { 89 | 90 | // Trade 1: Execute swap of Ether into designated ERC20 token on UniswapV2 91 | try uniswapV2Router.swapETHForExactTokens{ 92 | value: amountToTrade 93 | }( 94 | amountToTrade, 95 | getPathForETHToToken(daiTokenAddress), 96 | address(this), 97 | deadline 98 | ){ 99 | } catch { 100 | // error handling when arb failed due to trade 1 101 | } 102 | 103 | // Re-checking prior to execution since the NodeJS bot that instantiated this contract would have checked already 104 | uint256 tokenAmountInWEI = tokensOut.mul(1000000000000000000); //convert into Wei 105 | uint256 estimatedETH = getEstimatedETHForToken(tokensOut, daiTokenAddress)[0]; // check how much ETH you'll get for x number of ERC20 token 106 | 107 | // grant uniswap / sushiswap access to your token, DAI used since we're swapping DAI back into ETH 108 | dai.approve(address(uniswapV2Router), tokenAmountInWEI); 109 | dai.approve(address(sushiswapV1Router), tokenAmountInWEI); 110 | 111 | // Trade 2: Execute swap of the ERC20 token back into ETH on Sushiswap to complete the arb 112 | try sushiswapV1Router.swapExactTokensForETH ( 113 | tokenAmountInWEI, 114 | estimatedETH, 115 | getPathForTokenToETH(daiTokenAddress), 116 | address(this), 117 | deadline 118 | ){ 119 | } catch { 120 | // error handling when arb failed due to trade 2 121 | } 122 | } 123 | 124 | /** 125 | sweep entire balance on the arb contract back to contract owner 126 | */ 127 | function WithdrawBalance() private payable onlyOwner { 128 | 129 | // withdraw all ETH 130 | msg.sender.call{ value: address(this).balance }(""); 131 | 132 | // withdraw all x ERC20 tokens 133 | dai.transfer(msg.sender, dai.balanceOf(address(this))); 134 | } 135 | 136 | /** 137 | Flash loan x amount of wei's worth of `_flashAsset` 138 | e.g. 1 ether = 1000000000000000000 wei 139 | */ 140 | function flashloan ( 141 | address _flashAsset, 142 | uint _flashAmount, 143 | address _daiTokenAddress, 144 | uint _amountToTrade, 145 | uint256 _tokensOut 146 | ) public onlyOwner { 147 | 148 | bytes memory data = ""; 149 | 150 | daiTokenAddress = address(_daiTokenAddress); 151 | dai = IERC20(daiTokenAddress); 152 | 153 | amountToTrade = _amountToTrade; // how much wei you want to trade 154 | tokensOut = _tokensOut; // how many tokens you want converted on the return trade 155 | 156 | // call lending pool to commence flash loan 157 | ILendingPool lendingPool = ILendingPool(addressesProvider.getLendingPool()); 158 | lendingPool.flashLoan(address(this), _flashAsset, uint(_flashAmount), data); 159 | } 160 | 161 | /** 162 | Using a WETH wrapper here since there are no direct ETH pairs in Uniswap v2 163 | and sushiswap v1 is based on uniswap v2 164 | */ 165 | function getPathForETHToToken(address ERC20Token) private view returns (address[] memory) { 166 | address[] memory path = new address[](2); 167 | path[0] = uniswapV2Router.WETH(); 168 | path[1] = ERC20Token; 169 | 170 | return path; 171 | } 172 | 173 | /** 174 | Using a WETH wrapper to convert ERC20 token back into ETH 175 | */ 176 | function getPathForTokenToETH(address ERC20Token) private view returns (address[] memory) { 177 | address[] memory path = new address[](2); 178 | path[0] = ERC20Token; 179 | path[1] = sushiswapV1Router.WETH(); 180 | 181 | return path; 182 | } 183 | 184 | /** 185 | helper function to check ERC20 to ETH conversion rate 186 | */ 187 | function getEstimatedETHForToken(uint _tokenAmount, address ERC20Token) public view returns (uint[] memory) { 188 | return uniswapV2Router.getAmountsOut(_tokenAmount, getPathForTokenToETH(ERC20Token)); 189 | } 190 | } 191 | --------------------------------------------------------------------------------