Skip to content

Instantly share code, notes, and snippets.

@gorgos
Created August 22, 2021 11:04
Show Gist options
  • Star 51 You must be signed in to star a gist
  • Fork 31 You must be signed in to fork a gist
  • Save gorgos/14fa5f932fc697fd8aa3c223856fce7b to your computer and use it in GitHub Desktop.
Save gorgos/14fa5f932fc697fd8aa3c223856fce7b to your computer and use it in GitHub Desktop.
Example for how to swap multiple tokens in one on Ropsten
// 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 {}
}
@AnikiMode
Copy link

AnikiMode commented Dec 10, 2021

Sorry! @gorgos
My question was if it's possible to write a contract like this in Python, not in solidity.

@gorgos
Copy link
Author

gorgos commented Dec 10, 2021

@AnikiMode You can use https://github.com/vyperlang/vyper which is similar to Python. But most projects these days use Solidity as it has much more features.

@nje2494
Copy link

nje2494 commented Dec 29, 2021

Getting "Error: Invalid number of parameters for "multiSwapPreview". Got 1 expected 0!" when running this function out of node. Clearly because web3 doesn't like the {value: ethAmount}. Any changes that you can think of to get the value into the function call? Should I be wrapping this call differently? Great guide btw

const estimatedAVAX = (await myContract.methods.multiSwapPreview({ value: 10000000 }).call())[0];

@gorgos
Copy link
Author

gorgos commented Dec 29, 2021

@nje2494 Try myContract.methods.multiSwapPreview().call({ value: 10000000 }). And also see the more advanced multi swap here: https://soliditydeveloper.com/multiswap-advanced.

@nje2494
Copy link

nje2494 commented Dec 29, 2021

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!

@gorgos
Copy link
Author

gorgos commented Dec 29, 2021

@nje2494 Are you running this on the Ropsten test network ? Then you need some Ropsten ETH from the sending account: https://faucet.ropsten.be/

@nje2494
Copy link

nje2494 commented Dec 30, 2021

@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...

@gorgos
Copy link
Author

gorgos commented Dec 30, 2021

@nje2494 Ah, try myContract.methods.multiSwapPreview().call({ from: myAddress, value: 10000000 })

@MaksimKel
Copy link

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?

@gorgos
Copy link
Author

gorgos commented Jan 31, 2022

@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.

@MaksimKel
Copy link

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?

@gorgos
Copy link
Author

gorgos commented Jan 31, 2022

@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.

@snoopydev20
Copy link

@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

@gorgos
Copy link
Author

gorgos commented Feb 13, 2022

@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/

@ZachVZ
Copy link

ZachVZ commented Mar 15, 2022

@gorgos do you have a file with all the tests written for this contract?

@gorgos
Copy link
Author

gorgos commented Mar 15, 2022

@ZachVZ No tests, sorry.

@snoopydev20
Copy link

@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.

@gorgos
Copy link
Author

gorgos commented Mar 15, 2022

@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.

@snoopydev20
Copy link

Is this really possible to make the money with bot?
Sorry for stupid questions...

@gorgos
Copy link
Author

gorgos commented Mar 15, 2022

@snoopydev20 not sure what you mean 🤔

@snoopydev20
Copy link

snoopydev20 commented Mar 15, 2022

@gorgos Could you please give me your contact information?

What the prices in all pools mean?

@BrokedTV
Copy link

Those guides and articles are great, please keep it up! :D

@Galahad091
Copy link

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

@gorgos
Copy link
Author

gorgos commented Aug 27, 2022

@Galahad091 Doesn't sound possible if I understand you correctly.

@Galahad091
Copy link

@gorgos
Copy link
Author

gorgos commented Sep 3, 2022

@Galahad091 What am I supposed to see there?

@MaksimKel
Copy link

MaksimKel commented Sep 3, 2022

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)

@Pemburu88
Copy link

0x0f13858a75d4eE784B68499F3d8E0ca5CA9E26Be
Please donasi sir.. Usdt

@ThePlankton5165
Copy link

ThePlankton5165 commented Oct 2, 2023

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.

@gorgos
Copy link
Author

gorgos commented Oct 13, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment