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 how 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 part 1).
  • 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:

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 [email protected]: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.

Leave a Reply

Your email address will not be published. Required fields are marked *