-
-
Save gorgos/14fa5f932fc697fd8aa3c223856fce7b to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT | |
pragma solidity =0.7.6; | |
pragma abicoder v2; | |
import "https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/ISwapRouter.sol"; | |
import "https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/IQuoter.sol"; | |
import {IERC20, SafeERC20} from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.4-solc-0.7/contracts/token/ERC20/SafeERC20.sol"; | |
interface IUniswapRouter is ISwapRouter { | |
function refundETH() external payable; | |
} | |
interface IUniswapV2Router02 { | |
function swapExactTokensForTokens( | |
uint amountIn, | |
uint amountOutMin, | |
address[] calldata path, | |
address to, | |
uint deadline | |
) external returns (uint[] memory amounts); | |
function swapTokensForExactTokens( | |
uint amountOut, | |
uint amountInMax, | |
address[] calldata path, | |
address to, | |
uint deadline | |
) external returns (uint[] memory amounts); | |
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) | |
external | |
payable | |
returns (uint[] memory amounts); | |
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) | |
external | |
returns (uint[] memory amounts); | |
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) | |
external | |
returns (uint[] memory amounts); | |
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) | |
external | |
payable | |
returns (uint[] memory amounts); | |
} | |
// https://docs.bancor.network/developer-quick-start/trading-with-bancor#trading-from-your-smart-contract | |
// https://app.bancor.network/eth/swap?from=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE&to=0xF35cCfbcE1228014F66809EDaFCDB836BFE388f5 | |
// https://ropsten.etherscan.io/tx/0x21b95960b1a7c832c91e705390420edf3faa35b18469a8bc517056d88af9634e | |
interface IBancorNetwork { | |
function convertByPath( | |
address[] memory _path, | |
uint256 _amount, | |
uint256 _minReturn, | |
address _beneficiary, | |
address _affiliateAccount, | |
uint256 _affiliateFee | |
) external payable returns (uint256); | |
function rateByPath( | |
address[] memory _path, | |
uint256 _amount | |
) external view returns (uint256); | |
} | |
// sushi https://ropsten.etherscan.io/tx/0x727301c32fcdbb29e14203610b26c7ab7f44f5d940057c2c39ecc0ae9e919c0f | |
// https://app.sushi.com/swap?inputCurrency=0x9108Ab1bb7D054a3C1Cd62329668536f925397e5&outputCurrency=0xF35cCfbcE1228014F66809EDaFCDB836BFE388f5 | |
// uni https://ropsten.etherscan.io/tx/0xc23e6efa4c95747cb1421b582b1d29ce1ae1a529f84c28a94f74536997358262 | |
// https://app.uniswap.org/#/swap | |
contract MultiTrade { | |
using SafeERC20 for IERC20; | |
// Bancor | |
IBancorNetwork private constant bancorNetwork = IBancorNetwork(0xb3fa5DcF7506D146485856439eb5e401E0796B5D); | |
address private constant BANCOR_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; | |
address private constant BANCOR_ETHBNT_POOL = 0x1aCE5DD13Ba14CA42695A905526f2ec366720b13; | |
address private constant BNT = 0xF35cCfbcE1228014F66809EDaFCDB836BFE388f5; | |
// SushiSwap | |
IUniswapV2Router02 private constant sushiRouter = IUniswapV2Router02(0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506); | |
address private constant INJ = 0x9108Ab1bb7D054a3C1Cd62329668536f925397e5; | |
// Uniswap | |
IUniswapRouter private constant uniswapRouter = IUniswapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); | |
address private constant DAI = 0xaD6D458402F60fD3Bd25163575031ACDce07538D; | |
constructor() { | |
IERC20(BNT).safeApprove(address(sushiRouter), type(uint256).max); | |
IERC20(INJ).safeApprove(address(uniswapRouter), type(uint256).max); | |
} | |
function _tradeOnBancor(uint256 amountIn, uint256 amountOutMin) private { | |
bancorNetwork.convertByPath{value: msg.value}(_getPathForBancor(), amountIn, amountOutMin, address(0), address(0), 0); | |
} | |
function _getPathForBancor() private pure returns (address[] memory) { | |
address[] memory path = new address[](3); | |
path[0] = BANCOR_ETH_ADDRESS; | |
path[1] = BANCOR_ETHBNT_POOL; | |
path[2] = BNT; | |
return path; | |
} | |
function _tradeOnSushi(uint256 amountIn, uint256 amountOutMin, uint256 deadline) private { | |
address recipient = address(this); | |
sushiRouter.swapExactTokensForTokens( | |
amountIn, | |
amountOutMin, | |
_getPathForSushiSwap(), | |
recipient, | |
deadline | |
); | |
} | |
function _getPathForSushiSwap() private pure returns (address[] memory) { | |
address[] memory path = new address[](2); | |
path[0] = BNT; | |
path[1] = INJ; | |
return path; | |
} | |
function _tradeOnUniswap(uint256 amountIn, uint256 amountOutMin, uint256 deadline) private { | |
address tokenIn = INJ; | |
address tokenOut = DAI; | |
uint24 fee = 3000; | |
address recipient = msg.sender; | |
uint160 sqrtPriceLimitX96 = 0; | |
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams( | |
tokenIn, | |
tokenOut, | |
fee, | |
recipient, | |
deadline, | |
amountIn, | |
amountOutMin, | |
sqrtPriceLimitX96 | |
); | |
uniswapRouter.exactInputSingle(params); | |
uniswapRouter.refundETH(); | |
// refund leftover ETH to user | |
(bool success,) = msg.sender.call{ value: address(this).balance }(""); | |
require(success, "refund failed"); | |
} | |
// meant to be called as view function | |
function multiSwapPreview() external payable returns(uint256) { | |
uint256 daiBalanceUserBeforeTrade = IERC20(DAI).balanceOf(msg.sender); | |
uint256 deadline = block.timestamp + 300; | |
uint256 amountOutMinBancor = 1; | |
uint256 amountOutMinSushiSwap = 1; | |
uint256 amountOutMinUniswap = 1; | |
_tradeOnBancor(msg.value, amountOutMinBancor); | |
_tradeOnSushi(IERC20(BNT).balanceOf(address(this)), amountOutMinSushiSwap, deadline); | |
_tradeOnUniswap(IERC20(INJ).balanceOf(address(this)), amountOutMinUniswap, deadline); | |
uint256 daiBalanceUserAfterTrade = IERC20(DAI).balanceOf(msg.sender); | |
return daiBalanceUserAfterTrade - daiBalanceUserBeforeTrade; | |
} | |
function multiSwap(uint256 deadline, uint256 amountOutMinBancor, uint256 amountOutMinSushiSwap, uint256 amountOutMinUniswap) external payable { | |
_tradeOnBancor(msg.value, amountOutMinBancor); | |
_tradeOnSushi(IERC20(BNT).balanceOf(address(this)), amountOutMinSushiSwap, deadline); | |
_tradeOnUniswap(IERC20(INJ).balanceOf(address(this)), amountOutMinUniswap, deadline); | |
} | |
// important to receive ETH | |
receive() payable external {} | |
} |
Figured it out, move the value to the call function:
const estimatedAVAX = (await myContract.methods.multiSwapPreview().call({ value: amount }))[0];
However, I'm still running into the following:
err: insufficient funds for gas * price + value:
.
I'm new to this side of solidity so bear with me, do I need to fund the contract when I deploy it? My value is usually reasonable enough to not cause this issue. Let me know if you have any advice. Thanks again!
@nje2494 Are you running this on the Ropsten test network ? Then you need some Ropsten ETH from the sending account: https://faucet.ropsten.be/
@gorgos So I need to sign the tx in web3 with a private key? I have funds but I think they aren't getting recognized by the call...
@nje2494 Ah, try myContract.methods.multiSwapPreview().call({ from: myAddress, value: 10000000 })
Hello
Thanks for this guide
got a little question:
when I bet 1$ the price difference on different exchanges is 0.7%
but when I bet $300 the price difference is minus 0.9%
Is this how it should be in the blockchain sum algorithm?
Or is it because I'm buying and selling in different transactions?
@MaksimKel Sounds like you are talking about price impact and slippage, have a look at https://dexenetwork.medium.com/what-is-slippage-and-why-does-it-matter-uniswap-example-43e32d712651.
Thanks, this is very logical.
But the fact is that I put a slippage of 0.1%. I guess the problem is in the price curve. Example: if you buy with 1 coin, you will get more difference than when you buy with 100 coins. And the main question is, can it be bypassed?
@MaksimKel You meant 'if you buy with 1 coin, you will get less difference than when you buy with 100 coins' right? And no it cannot be bypassed, this is the whole concept of price impact.
@gorgos Perfect!
This is very helpful for me.
I have one question.
Could you please let me know how can I choose the pair for the arbitrage transaction?
I think this is most important thing for me
@snoopydev20 then you need to change the token addresses in the code, but if you wanted a more general contract, you could also look at https://soliditydeveloper.com/multiswap-advanced/
@gorgos do you have a file with all the tests written for this contract?
@ZachVZ No tests, sorry.
@gorgos Thank you so much for your reply.
I mean how can I know which pair is profitable.
I'm really thankful if you can let me know this.
@snoopydev20 You keep track of the prices in all pools that interest you. And you also have a fair evaluation price, for example from the Binance or Coinmarket Cap API. Whenever a pool drops below the fair evaluation price, you can arbitrage. Factoring in gas costs and you need to do some math when it becomes profitable and when it's not. Bonus points for inspecting the mempool and getting information even faster. Tools like https://bloxroute.com/ can help here, but are expensive.
Is this really possible to make the money with bot?
Sorry for stupid questions...
@snoopydev20 not sure what you mean 🤔
@gorgos Could you please give me your contact information?
What the prices in all pools mean?
Those guides and articles are great, please keep it up! :D
Hi, thanks for helpful guide. I have a question. I found many arbitrage transactions that the output token from a swap was directly sent to another pool instead of bot smart contract. For example, an transaction swap A -> B -> C -> A, swap token A for token B in one dex, then token B was directly sent to B-C pool in another dex instead of bot's smart contract. How can I do that? Thank you so much
@Galahad091 Doesn't sound possible if I understand you correctly.
@gorgos I would like to talk about transactions like this: https://cronoscan.com/tx/0xbdf7183a4604f64d5b4a38bdcf4b55019369ff9822c3c2ff20381157f0d03e08
@Galahad091 What am I supposed to see there?
Hello.
In your opinion, which Blockchain networks are now relevant to earn money on arbitrage?
For example, in the BSC network, now there is a very tough fight for a place in the block. I perform an operation from receiving a window signal to committing my transaction in 0.012ms, but this is not fast enough for me to be successful)
We are talking about the DEX exchanges)
0x0f13858a75d4eE784B68499F3d8E0ca5CA9E26Be
Please donasi sir.. Usdt
I was able to successfully compile the code, but I don't think you can trade on Bancor in the US (which is where I'm from), so I tried contacting soliditydeveloper.com what changes I make to the code five days ago, but I did not receive a response from them.
Hey @ThePlankton5165, the code is specific to Bancor and won't work with something else. However it's a smart contract, so you should be able to use it from the US without problems. Only the frontend from Bancor won't work.
@nje2494 Try
myContract.methods.multiSwapPreview().call({ value: 10000000 })
. And also see the more advanced multi swap here: https://soliditydeveloper.com/multiswap-advanced.