Categories
Arbitrage Bot DeFi DEX erc20 javascript web3js

Swap tokens with 1inch Exchange in JavaScript: DEX and Arbitrage part 2

For this tutorial will see how to execute a trade using 1inch DEX aggregator in Javascript with the web3.js library. At the end of the tutorial, you’ll know how to exchange ERC20 tokens and Ether directly on the Ethereum blockchain.

This article is part 2 (part 1 here) of tutorials we started while using the 1inch Exchange dex aggregator to get a quote on a specific trade.
Part 1 got you through how to get a quote on a trade: getting the amount of token you’ll get in exchange of the one you’re selling. Part 2, we will see ho to Execute the trade with Javascript.

To complete a swap on the 1inch’s DEX aggregator, we need 3 things:

  • Get the current exchange rate (as seen in part1).
  • Approve the amount of ERC20 token we want to swap so the 1inch smart contract can access your funds.
  • Make the trade by calling the swap method with the parameters we got at step 1.

But before starting, we are going to define all the constant variables and some helper functions we’ll need to make the code more simple:

Setup

For this tutorial we’ll use ganache-cli to fork the existing blockchain and use it to unlock an account that already owns a lot of DAI token. In our example 0x78bc49be7bae5e0eec08780c86f0e8278b8b035b. We also set the gas limit really high so we don’t have problems while testing without estimating gas cost before every transaction. The command line for launching the forked blockchain is:

Join the newsletter

Get a weekly summary of what is happening in the Ethereum developer space for free

ganache-cli  -f https://mainnet.infura.io/v3/[YOUR INFURA KEY] -d -i 66 --unlock 0x78bc49be7bae5e0eec08780c86f0e8278b8b035b -l 8000000

In this tutorial we’ll try to swap 1000 DAI to ETH using the 1inch dex aggregator, at first and for convenience, let’s declare all the variables we need like the addresses of the contracts, the ABIs..

var Web3 = require('web3');
const BigNumber = require('bignumber.js');

const oneSplitABI = require('./abis/onesplit.json');
const onesplitAddress = "0xC586BeF4a0992C495Cf22e1aeEE4E446CECDee0E"; // 1plit contract address on Main net

const erc20ABI = require('./abis/erc20.json');
const daiAddress = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI ERC20 contract address on Main net

const fromAddress = "0x4d10ae710Bd8D1C31bd7465c8CBC3add6F279E81";

const fromToken = daiAddress;
const fromTokenDecimals = 18;

const toToken = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; // ETH
const toTokenDecimals = 18;

const amountToExchange = new BigNumber(1000);

const web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545'));

const onesplitContract = new web3.eth.Contract(oneSplitABI, onesplitAddress);
const daiToken = new web3.eth.Contract(erc20ABI, fromToken);

We also define some helper function that just wait for a transaction to be mined on the blockchain:

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function waitTransaction(txHash) {
    let tx = null;
    while (tx == null) {
        tx = await web3.eth.getTransactionReceipt(txHash);
        await sleep(2000);
    }
    console.log("Transaction " + txHash + " was mined.");
    return (tx.status);
}

Get the rates

Now that we have everything ready to work, we use the code from the first part of the series of tutorials to get the expected rate for the trade. We just transform the code to be used as a function for ease of reading. The function getQuote returns an object containing all the parameters to use the swap function as detailed in the first part.

async function getQuote(fromToken, toToken, amount, callback) {
    let quote = null;
    try {
        quote = await onesplitContract.methods.getExpectedReturn(fromToken, toToken, amount, 100, 0).call();
    } catch (error) {
        console.log('Impossible to get the quote', error)
    }
    console.log("Trade From: " + fromToken)
    console.log("Trade To: " + toToken);
    console.log("Trade Amount: " + amountToExchange);
    console.log(new BigNumber(quote.returnAmount).shiftedBy(-fromTokenDecimals).toString());
    console.log("Using Dexes:");
    for (let index = 0; index < quote.distribution.length; index++) {
        console.log(oneSplitDexes[index] + ": " + quote.distribution[index] + "%");
    }
    callback(quote);
}

Approve the spending of the token

Once we got the rate for exchanging a token as seen in part 1, we first need to approve the 1inch dex aggregator smart contract to spend our token. As you may know, the ERC20 token standard does not allow to send tokens to a smart contract and trigger an action from it in one transaction We wrote a simple function that calls an ERC20 contract instance approve function and wait for the transaction to be mined using our previous waitTransaction helper:

function approveToken(tokenInstance, receiver, amount, callback) {
    tokenInstance.methods.approve(receiver, amount).send({ from: fromAddress }, async function(error, txHash) {
        if (error) {
            console.log("ERC20 could not be approved", error);
            return;
        }
        console.log("ERC20 token approved to " + receiver);
        const status = await waitTransaction(txHash);
        if (!status) {
            console.log("Approval transaction failed.");
            return;
        }
        callback();
    })
}

Note that you can approve a bigger amount than the token you plan to trade in order, for example, to execute multiple trades without having to do several approval transactions.

Make the swap

Now that we have everything to get the parameters, we need to call the 1inch aggregator swap function and let the smart contract access our funds. We’ll now emit a transaction that will trigger the swap and wait for it to be mined by the blockchain.

In order to make sure that our swap was successful, we’ll also add some calls to the balanceOf function of the DAI token and fetch the balance of our Ether address to make sure we really retrieve our Ether in exchange of the DAI tokens.

let amountWithDecimals = new BigNumber(amountToExchange).shiftedBy(fromTokenDecimals).toFixed()

getQuote(fromToken, toToken, amountWithDecimals, function(quote) {
    approveToken(daiToken, onesplitAddress, amountWithDecimals, async function() {
        // We get the balance before the swap just for logging purpose
        let ethBalanceBefore = await web3.eth.getBalance(fromAddress);
        let daiBalanceBefore = await daiToken.methods.balanceOf(fromAddress).call();
        onesplitContract.methods.swap(fromToken, toToken, amountWithDecimals, quote.returnAmount, quote.distribution, 0).send({ from: fromAddress, gas: 8000000 }, async function(error, txHash) {
            if (error) {
                console.log("Could not complete the swap", error);
                return;
            }
            const status = await waitTransaction(txHash);
            // We check the final balances after the swap for logging purpose
            let ethBalanceAfter = await web3.eth.getBalance(fromAddress);
            let daiBalanceAfter = await daiToken.methods.balanceOf(fromAddress).call();
            console.log("Final balances:")
            console.log("Change in ETH balance", new BigNumber(ethBalanceAfter).minus(ethBalanceBefore).shiftedBy(-fromTokenDecimals).toFixed(2));
            console.log("Change in DAI balance", new BigNumber(daiBalanceAfter).minus(daiBalanceBefore).shiftedBy(-fromTokenDecimals).toFixed(2));
        });
    });
});

Which at the time of writing with a price of ETH at around 170$ is showing something like this:

As you can see, the swap gave us 5.85 while we sold 1000 DAI tokens.

To make it easy for you, we published the source code on Github so you can easily get the code on your side:

git clone git@github.com:jdourlens/ethereumdevio-dex-tutorial.git && cd ethereumdevio-dex-tutorial/part2

And install the required dependencies web3.js and bignumber.js:

npm install

To execute the code:

node index.js

A problem you might encounter is having this message: “VM Exception while processing transaction: revert OneSplit: actual return amount is less than minReturn“. it simply means that the quote has changed since you got it and the block was mined on the blockchain. If you want to avoid it happening, you can introduce in your code a slippage by decreasing the minReturn parameter of the swap by 1 or 3 percent depending on the amount of your trade.


That’s all you need to execute on-chain ERC20 and Eth swaps using 1inch DEX aggregator. Don’t forget that you don’t have to always swap against Ether and can swap 2 ERC20 tokens together or even with Wrapped Ether. Keep an eye on our tutorials or subscribe to our newsletter. You can continue to learn on building on top of money legos with our next tutorial about flash loans using Aave, Dy/Dx, or Kollateral.

13 replies on “Swap tokens with 1inch Exchange in JavaScript: DEX and Arbitrage part 2”

If you are using Mainnet. This tutorial is explaining how to do it on a local test network. Once you are ready you can move to mainnet.

I understand we are on a testnet, but the code only ever uses a public-key in the fromAddress. Im guessing this is because Ganashe doesn’t care and accepts all TXs as if they were legitimate. Even staying on a testnet, I would love to be able to test with valid priv/pub pairs.

You can use any address you own. In this example we use this address (that we unlock through ganache) as it has already funds in it so makes the process easier. If you need help you can join our Telegram group https://t.me/ethereumdevio

Hi thankyou for this post. How can you approve Ether and buy Dai? I know how to do it with ERC20 tokens but I cant figure out how to approve ether.

I am running the script on a ganache infura fork and am failing on SWAP with this error “Could not complete the swap Error: Returned error: VM Exception while processing transaction: revert SafeERC20: low-level call failed”. Do you have thoughts on why?

I am getting the below error would you please advise ?

Trade From: 0x6b175474e89094c44da98b954eedeac495271d0f
Trade To: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
Trade Amount: 1000
2.655920724314997294
Using Dexes:
Uniswap: 23%
Kyber: 0%
Bancor: 0%
Oasis: 0%
Curve Compound: 0%
Curve USDT: 0%
Curve Y: 0%
Curve Binance: 0%
Curve Synthetix: 0%
Uniswap Compound: 0%
Uniswap CHAI: 0%
Uniswap Aave: 0%
Mooniswap: 0%
Uniswap V2: 77%
Uniswap V2 ETH: 0%
Uniswap V2 DAI: 0%
Uniswap V2 USDC: 0%
Curve Pax: 0%
Curve renBTC: 0%
Curve tBTC: 0%
Dforce XSwap: 0%
Shell: 0%
ERC20 token approved to 0xC586BeF4a0992C495Cf22e1aeEE4E446CECDee0E
Transaction 0x9869f58345b836bb4b98d067c16bb96c33c25f3fe6be2c48031fcfcaacbd2d56 was mined.
Could not complete the swap Error: Returned error: VM Exception while processing transaction: revert SafeERC20: low-level call failed
at Object.ErrorResponse (/xxx/ethereumdevio-dex-tutorial/part2/node_modules/web3-core-helpers/src/errors.js:29:16)
at /xxxx/ethereumdevio-dex-tutorial/part2/node_modules/web3-core-requestmanager/src/index.js:140:36

The address used for approving in the example does not hold enough DAI anymore you can use this one in the unlock parameter of Ganache and from address: 0x6dcb8492b5de636fd9e0a32413514647d00ef8d0

Hi Peter,
getting this error :

~/Documents/work2/BTCsubToken/swap-wth-1inch/swap$ node index.js
Trade From: 0x6b175474e89094c44da98b954eedeac495271d0f
Trade To: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
Trade Amount: 10DAI
FOR 0.021082622248708205 ETH
————–RETURN QUOTE—————- 21082622248708205
Using Dexes:
Uniswap: 100%
Kyber: 0%
Bancor: 0%
Oasis: 0%
Curve Compound: 0%
Curve USDT: 0%
Curve Y: 0%
Curve Binance: 0%
Curve Synthetix: 0%
Uniswap Compound: 0%
Uniswap CHAI: 0%
Uniswap Aave: 0%
Mooniswap: 0%
Uniswap V2: 0%
Uniswap V2 ETH: 0%
Uniswap V2 DAI: 0%
Uniswap V2 USDC: 0%
Curve Pax: 0%
Curve renBTC: 0%
Curve tBTC: 0%
Dforce XSwap: 0%
Shell: 0%
(node:54739) UnhandledPromiseRejectionWarning: TypeError: tokenInstance.methods.approve is not a function
at approveToken (/home/ramesh/Documents/work2/BTCsubToken/swap-wth-1inch/swap/index.js:66:27)
at /home/ramesh/Documents/work2/BTCsubToken/swap-wth-1inch/swap/index.js:107:5
at getQuote (/home/ramesh/Documents/work2/BTCsubToken/swap-wth-1inch/swap/index.js:99:5)
at process._tickCallback (internal/process/next_tick.js:68:7)
(node:54739) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:54739) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

hi using the new address you provided im getting:
ERC20 could not be approved { Error: Returned error: sender account not recognized
at Object.ErrorResponse (/home/dextut/ethereumdevio-dex-tutorial/part2/node_modules/web3-core-helpers/src/errors.js:30:19)
at onJsonrpcResult (/home/dextut/ethereumdevio-dex-tutorial/part2/node_modules/web3-core-requestmanager/src/index.js:172:36)
at XMLHttpRequest.request.onreadystatechange (/home/dextut/ethereumdevio-dex-tutorial/part2/node_modules/web3-providers-http/src/index.js:111:13)
at XMLHttpRequestEventTarget.dispatchEvent (/home/dextut/ethereumdevio-dex-tutorial/part2/node_modules/xhr2-cookies/dist/xml-http-request-event-target.js:34:22)
at XMLHttpRequest._setReadyState (/home/dextut/ethereumdevio-dex-tutorial/part2/node_modules/xhr2-cookies/dist/xml-http-request.js:208:14)
at XMLHttpRequest._onHttpResponseEnd (/home/dextut/ethereumdevio-dex-tutorial/part2/node_modules/xhr2-cookies/dist/xml-http-request.js:318:14)
at IncomingMessage. (/home/dextut/ethereumdevio-dex-tutorial/part2/node_modules/xhr2-cookies/dist/xml-http-request.js:289:61)
at emitNone (events.js:111:20)
at IncomingMessage.emit (events.js:208:7)
at endReadableNT (_stream_readable.js:1064:12)
at _combinedTickCallback (internal/process/next_tick.js:138:11)
at process._tickCallback (internal/process/next_tick.js:180:9)
data:
{ stack: ‘a: sender account not recognized\n at S.queueTransaction (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:215053)\n at p.eth_sendTransaction (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:204296)\n at p.handleRequest (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:201387)\n at t (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:52741)\n at n.handleRequest (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:199670)\n at t (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:52741)\n at f.s.handleRequest (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:165287)\n at f.handleRequest (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:163099)\n at t (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:52741)\n at o.handleRequest (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:171351)\n at t (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:52741)\n at n.handleRequest (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:170796)\n at t (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:52741)\n at u._handleAsync (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:52777)\n at Timeout._onTimeout (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:39:52205)\n at ontimeout (timers.js:482:11)’,
name: ‘a’ } }

Hello, trying to exchange from ETH to DAI, and getting this error: Could not complete the swap: Returned error: VM Exception while processing transaction: revert OneSplit: msg.value shoule be used only for ETH swap.

Found this in the contract:
require((msg.value != 0) == fromToken.isETH(), “OneSplit: msg.value shoule be used only for ETH swap”);
My tokens:
const fromToken = ‘0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE’;
const toTokenDAI = ‘0x6b175474e89094c44da98b954eedeac495271d0f’;

Please, do you have any lights on this.
Best regards.

Hi Peter,
Keep getting these error messages when I try to swap an ERC20 token like USDC.

UnhandledPromiseRejectionWarning: TypeError: tokenInstance.methods.approve is not a function at approveToken

Leave a Reply