Decentralized Exchange (DEX) on the ICE Blockchain
Decentralized Exchanges, also known as DEX, are an innovative new approach to trading cryptocurrencies. They enable users to exchange cryptocurrencies without requiring the assistance of a third party.
Decentralized exchanges are safe and transparent because they are built on smart contracts and blockchain technology. DEXs, unlike centralized exchanges, do not require the personal information to trade with. There are many successful DEX projects, such as Uniswap, 1inch Exchange, SushiSwap, Balanced, etc.
In this article, we will cover how to create a minimalistic version of a DEX.
What will we be doing?
- Write and deploy Solidity Smart Contracts:
- Token Contract: A Contract for building a custom ERC20 token.
- Exchange Contract: A Contract that allows token swaps between an ERC20 token and ICZ (the native token of ICE testnet).
2. Interact with Smart Contracts using the Remix IDE and a user interface for the DEX.
Prerequisite
- Metamask configured with Arctic testnet
- Fund one of your account with ICZ testnet tokens from ICE Faucet
Smart contract for Token
Let us look at the Token smart contract.
It is a simple ERC20 token contract that takes in the token name, token symbol, and the initial supply of tokens to be minted as parameters to the constructor which is invoked during the contract creation.
You can find detailed information on property and its usage regarding the ERC20 token on the API reference page of the ERC20 token provided by openZeppelin contracts
The functions balanceOf
, totalSupply
, transfer
, transferFrom
, approve
, and allowance
are defined in ERC20. Apart from these, there are other getters functions like name
and symbol.
Below is a brief description of a standard ERC20 contract
- totalSupply: It returns the total amount of tokens supplied
- balanceOf: It returns the balance of the account with the address
_owner
- transfer: It transfers
_value
amount from contract owner address to address_to.
- transferFrom: It enables a smart contract to automate the transfer procedure and transmit a specified quantity of the token on the owner’s behalf. It returns a failed transaction unless the
_from
account has deliberately authorized the sender of the message. - approve: It allows
_spender
to withdraw from your account, multiple times, up to the_value
amount. If this function is called again it overwrites the current allowance with_value
. - allowance: It returns the amount which
_spender
is still allowed to withdraw from_owner.
- event Transfer: It is triggered when tokens are transferred.
- event Approval: It is triggered whenever
approve(address _spender, uint256 _value)
is called.
Creating Token Contract
Let us head over to Remix IDE and deploy this Token contract.
- In a workspace create a file in the contracts folder named Token.sol, and paste the above token contract in it.
- Compile the contract by pressing ctrl + s or cmd + s.
Deploying the Token Contract
- Navigate to Deploy & Run Transaction tab from Left Side Menu.
- From the Environment drop-down menu, Select Environment as “Injected Web3”; make sure that it’s connected to the “Custom 552” network. If it is connected to some other network, open your Metamask and change the network to “Arctic” by following the instruction here.
- Select the contract to deploy: Token - contracts/Token.sol from the Contract dropdown menu. Extend the Deploy tab and Enter the parameters for your custom ERC20 Token: NAME, SYMBOL, and INITIALSUPPLY in their respective fields.
- Hit Transact to deploy the contract; Metamask will pop up for confirmation, confirm it, and wait for some time for the transaction to complete.
- After the contract is deployed, a success message will be logged to the console and the deployed Token contract instance will be fetched from the Arctic to the Deployed Contracts section at the bottom of the Deploy and Run Transactions window.
Note: For deploying a smart contract using Remix IDE , you can follow the instructions from here.
Interacting with the Token contract
Expand the Deployed Token Instance from the Deployed Contracts
- Query the name, symbol and totalSupply of your Token by clicking the respective buttons.
- Query the Deployer account balance (which should be equal to the “initialSupply” we provided the constructor during deployment). Copy-paste the connected account from the Addresses tab in the Deploy window to the balanceOf function in the Deployed Token Instance.
Similarly, you can interact with other methods of the Token contract.
Smart Contract for Exchange
Let us now look at the code for Exchange contract
The exchange contract will allow us to swap between the ERC20 ‘MTKN’ Token and the native ICZ token and vice-versa.
The constructor takes the tokenAddress (address of the ERC20 token deployed above) as a parameter, which is invoked at the time of the creation of the contract.
The exchange contract will contain a liquidity pool, which is a crowdsourced pool of cryptocurrencies or tokens that are locked in a smart contract and are used to enable trades between assets on a decentralized exchange (DEX). So an exchange needs to have tokens in its liquidity pool.
The addLiquidity() method can be used to supply liquidity. Both Token and ICZ tokens can be supplied to the contract’s liquidity pool using this method.
Eg: 200 MTKN and 100 ICZ
In the addLiquidity function, the Exchange contract initiates a token transfer from the function caller’s address to itself (the address of the Exchange contract).
NOTE: Before calling the addLiquidity function, we should allow the Exchange contract to spend MTKN tokens on our behalf. For this, we can use the approve(spender, amount) function in the Token contract. Calling it will allow the Exchange contract (spender) to transfer the amount to other accounts on behalf of the user (account) that called the approve function.
Let us now look at some more methods of Exchange contract:
- getReserveToken() returns the number of MTKN Token present in the contract.
- getIczReserve() returns the number of ICZ present in the contract.
- getAmount() calculates and returns the total amount to be received while swapping the tokens. Here, we determine the price of our asset w.r.t the other asset using the following formula (developed by Uniswap V1):
CONSTANT PRODUCT FORMULA:
X * Y = k, where
x = input reserve
y = output reserve
k = constant
----------------------------------------------
So from the Constant product formula:
where,
△x = input amount
△y = output amount
x = input reserve
y = output reserve
so our outputAmount formula becomes:
Which is returned as result of query when getAmount is invoked.
Creating the Exchange Contract
Now let us head over to Remix IDE and deploy the Exchange.sol contract written above.
- Create a file named Exchange.sol in contracts folder, and paste above Exchange contract in it.
- Compile the contract by pressing ctrl + s or cmd + s (Mac)
Deploying Exchange Contract
- Navigate to Deploy & Run Transaction tab from Left Side Menu.
- From the Environment drop-down-menu, Select Environment as “Injected Web3”; make sure that it’s connected to the “Custom 552” network. If it’s connected to some other network, Open up your Metamask and change the network to “Arctic” by following the instruction from here.
- Select the contract to deploy: Exchange - contracts/Exchange.sol from the Contract dropdown menu. Extend the Deploy tab and in “_TOKENADDRESS” field, paste the Token address from the Deployed Contracts.
- Hit Transact to deploy it; Metamask will pop-up for confirmation, confirm it and wait for some time for the transaction to pass successfully.
- After the contract is deployed, a success message will be logged to the console and the Exchange contract instance will be fetched from Arctic to the Deployed Contracts section at the bottom of the Deploy and Run Transactions window.
Interacting with Exchange contract
- Open the Deployed Exchange Instance from the Deployed Contracts section in Remix.
- Query the the tokenAddress the exchange is linked to, ensure that it is the deployed address of your token contract in Arctic testnet.
Querying the Reserve balance of Tokens in the Exchange:
- getIczReserve: to query the ICZ Reserve balance of the Exchange
- getTokenReserve: to query the Token Reserve balance of the Exchange
Ensure they return Zero/0 during the first query.
Adding Liquidity to the Exchange:
- First navigate to the deployed instance of the Token Contract and approve the Exchange contract address as a spender with a suitable token allowance amount as follows:
NOTE: If transaction fails, increase the gas limit value in metamask wallet and try again.
For increasing the gas limit value:
- When making the transaction, click the Edit button in MetaMask.
2. Then a window named ‘Edit priority’ will pop up. Click on Edit suggested gas fee button.
3. Finally increase the gas limit value and hit save.
- To add liquidity, call the addLiquidity() function and pass token amount in the liquidity function.
Note: The function button for it is colored in red, which denotes that this is a payable function. You’ll need to provide the value of ICZ that you want to add to the liquidity pool in the Value field in the Deploy and Run Transactions Window
If you now query the getTokenReserve() and getIczReserve() functions in the Exchange contract instance, you should get back the respective amounts of the two tokens you added to the Liquidity pool using this function.
Querying for available MTKN tokens for a swap:
To query the amount of MTKN Token available for a swap, query both the token reserves in the exchange. For this, expand the getAmount() function and enter the following params:
- inputAmount [uint256]: Enter the token amount you want to sell
- inputReserve [uint256]:
if you want to sell ICZ and buy MTKN, inputReserve = value of getTokenReserve()
if you want to sell MTKN Token and buy ICZ, inputReserve = value of getTokenReserve()
- outputReserve [uint256]:
if you want to sell ICZ and buy MTKN Token, outputReserve = value of getTokenReserve()
if you want to sell MTKN Token and buy ICZ, outputReserve = value of getIczReserve()
Swapping ICZ for MTKN token:
For this swap, we will call swapIczForToken() function button to swap ICZ for Token.
NOTE: The function button for it is colored in red, which denotes that this is a payable function.
We’ll need to provide the value of ICZ that we want to sell for Token in the Value field in the Deploy and Run Transactions Window.
Swap Token for ICZ:
Inverse to the swapIczForToken() function, enter the amount of MTKN tokens you would like to swap to obtain ICZ from the exchange in the _tokenAmount field.
Until now, we have deployed and interacted with MTKN Token contract as well as Exchange contract using the Remix IDE. Then we also swapped the MTKN Tokens to ICZ and vise-versa.
You can find the code for smart contract in contracts folder of this github repo.
Now that we have deployed the smart contracts to the Arctic testnet, will we be using a web User Interface to interact with the smart contracts we built and also make some exchanges.
Running the DEX frontend
Please make sure you have Node.js and npm installed in your device to run the frontend. The compatible Node.js version is
Node.js >= v 14.7.3 and the npm version is >= v 6.4.3.
You can check their versions by respectively running the commands:
$ node -v
$ npm -v
- Clone the github repository with the frontend source code into your local machine with the following command:
git clone <github_repository_link>
- Navigate into the cloned repository in your terminal and once inside its root, install all the necessary dependencies with the command
npm install
- In the .env file, replace the values in these two environment variables with your contract addresses in the Arctic testnet from Remix:
- Start the frontend with the command
npm start
The DEX frontend will launch and will be accessible via any web browser at http://localhost:3000.
Note: Make sure you have configured your metamask with the Arctic testnet before building the UI since we have deployed our contract there. You can make the configuration following the instructions here.
Taking a look at the UI
Since UI/UX is not the main focus of this article, we have kept the UI simple.
Interact with smart contract from UI
UI is divided into four section:
- Wallet Balance: It provides information about the wallet balance of a user and amount of token (MTKN) that the same user holds.
- Add Liquidity: From here user can provide liquidity to the exchange contract.
- Reserve in Exchange Contract: It provides the amount of ICZ and Token in the Exchange contract’s liquidity Pool.
- Swap: It allows user to swap their token with ICZ and vice-versa
We can interact with the functions in a sequential manner as we did in Remix:
- Add Liquidity
- Enter the ICZ amount and MTKN Token you want to approve the DEX to spend, then click on Add Liquidity.
Here we are invoking two methods from both contracts (Token contract and Exchange contract) that are Token#approve() and Exchange#addLiquidity(). First we are approving Exchange contract to spend MTKN Token amount on the caller’s behalf. Then we are adding the ICZ amount & MTKN amount from the UI to the Liquidity Pool. Since we are making two state changing function calls, Metamask will pop-up twice, one after another, asking for confirmation.
Once the transaction is complete, we can see our liquidity pool balances in Reserve in Exchange Contract section in UI
NOTE: If transaction fails, increase the gas limit value in metamask wallet and try again
2. Swap Tokens
Now, we will swap MYTOKEN for ICZ.
- Select MYTOKEN in From select field
- Select ICZ in To select field
- Enter the amount you want to swap in Amount input field and Click Swap button at last.
Again, we are making two contract calls. First we are transferring the MTKN Token amount to Exchange contract from user’s balance and then sending the ICZ amount from Exchange contract to user’s wallet. Now, Metamask will pop-up twice, one after another, and we need to confirm both the transactions.
After the transaction is successful, your Token amount will decrease and ICZ amount will increase which can seen in Wallet Balance section of UI.
NOTE: If transaction fails, increase the gas limit value in metamask wallet and try again
Conclusion
In this article, we went through the concepts of how DEX works, taking a reference from Uniswap V1. We wrote and deployed two smart contracts to the Arctic network: the Token and Exchange Smart Contract. Then, using the Remix IDE and we interacted with the functions of smart contracts. We also deployed an existing open source simple DEX UI to interact with the smart contracts, which helped visualize the functionality the Smart Contracts provided.