How to create a crypto exchange on the Ethereum blockchain using Node.js/React.js/Solidity - Part 5
Hello, how are you doing? This write-up is post number four in a series of posts for the tutorial series on How to create a crypto exchange on the Ethereum blockchain using Node.js/React.js/Solidity
.
I'll leave the link to previous posts in the series at the end of the post.
This series was inspired by a similar series by Dapp University.
The crypto exchange will contain a buy/sell
feature and will have Ethereum and ERC20 token pairs.
After adding the tests for our token and exchange smart contract in the last post, in this post we are going to continue with developing the smart contract for the exchange.
Mainly the smart contract for the exchange will have a functionality that allows us to buy the ERC20 token using Ethereum and also to sell the token for Ethereum.
Before we start with the actual objective of this tutorial, there are some little functions that we need to include in our project which will be setting the stage for our actual objective.
If you check the tests we wrote in our last post, you will find that there are some values that were used in more than one place out of the array of tests we wrote.
What we want to do now is to declare those values in a public scope so that they can be called by any test or function in our test module.
In our Exchange.test.js
file, right above the first describe()
method used for testing, add the following code
let token, exchange
before(async () => {
token = await Token.new()
exchange = await Exchange.new()
await token.transfer(exchange.address, '1000000000000000000000000')
})
In all the describe()
methods in the file, remove any instance of the following declarations so that the only place they are declared is in the method I added above
token = await Token.new()
exchange = await Exchange.new()
await token.transfer(exchange.address, '1000000000000000000000000')
After doing that, save your file and in the command line for the project, run the command
truffle test
You should get a result like the image below in your console.
Now that we have that out of the way, we can move forward to the next item on the menu.
Adding helper module to format module balances
On the line that says await token.transfer(exchange.address, '1000000000000000000000000')
, '1000000000000000000000000'
is equivalent to 1 million tokens even though the number of zeros after 1 is more than 6.
Let me explain why this is, if you check the smart contract code for the token in the file src/contracts/Token.sol
, you will find a line that says
uint8 public decimals = 18;
The line above automatically configures the balance to be exactly 18 decimal points.
But the rule when it comes to Ethereum smart contracts is that decimal points are not expected to be explicitly declared, even though under the hood they will be considered.
So what we do in the case of Ethereum is that Ethereum balances are to be represented in their basic unit Wei, just like the Dollar value is can be represented in Cents and Bitcoin in Satoshi, this principle also applies to our token.
If 1
dollar can be represented by 100
cents, 1
of our token can be represented by 1000000000000000000
. 1
million of our token can be represented by 1000000000000000000000000
which is the same the amount as what we have on the line await token.transfer(exchange.address, '1000000000000000000000000')
.
We can now say that 1 million of our token is in circulation presently.
What we want to do now is to create a helper function that helps us format the balances and make it more human readable.
So in the file Exchange.test.js
, add the following code below the line require('chai').use(require('chai-as-promised')).should()
function tokens(n) {
return web3.utils.toWei(n, 'ether');
}
Inside the before()
method, replace the line await token.transfer(exchange.address, '1000000000000000000000000')
with await token.transfer(exchange.address, tokens('1000000'))
.
Also, inside the test contract has token
, replace the line assert.equal(balance.toString(), '1000000000000000000000000')
with the following code
assert.equal(balance.toString(), tokens('1000000'))
Save the file again and run the command truffle test
in the command line, if all goes well you should still get an image like the one below.
Exchange smart contract
I believe we are done with all the preliminary stuff and we can now start writing the smart contract for our exchange.
In our earlier tutorials, we created a file at the path src/contracts/Exchange.sol
which will contain our exchange smart contract code.
The first thing we want to do in the Exchange.sol
file is to import our token so that we can use it in the file.
Add the following code below the line pragma solidity ^0.5.0;
import './Token.sol';
Now that we have imported the token smart contract we need to create a variable to represent the token, we do that by adding the following code in the smart contract object
Token public token;
Now, we need to create a new function that will help us identify where the token smart contract is located on the network.
To do that, we firstly need to update the migrations/2_deploy_contracts.js
file in our project.
In 2_deploy_contracts.js
, replace the line that says await deployer.deploy(Exchange);
with the code
await deployer.deploy(Exchange, token.address);
In the project command line run the following code
truffle migrate --reset
In Exchange.test.js
, replace the line exchange = await Exchange.new()
with the following code
exchange = await Exchange.new(token.address)
Let's save the file and move on.
Buying Tokens
Now, we are going to create a function for buying the token using Ethereum.
What the function will do is to facilitate an exchange of funds between the buyer's wallet and the exchange wallet.
In order to make this happen, we will rely on the transfer()
function in the token smart contract, we will have to call it in the Exchange.sol
file and it will result in the requested amount of tokens being sent from the exchange wallet to the buyer's wallet.
In the Exchange.sol
file, add the following code
function buyTokens() public payable {
// Calculate amount of tokens to buy
uint tokenAmount = msg.value * rate;
token.transfer(msg.sender, tokenAmount);
}
The contents of the function above calls the token.transfer()
method in the token smart contract, remember that we initialized our token smart contract earlier storing it as a variable token
.
The transfer()
function takes two arguments, the first one msg.sender
is the wallet address the token will be sent to, the second one tokenAmount
is the variable that stores the amount of the token to be sent to the buyer.
Directly below the line that says Token public token;
, add the following code
uint public rate = 100;
Then save the file and in your command line, run the following command
truffle compile
If you get a result like in the image below, then we can move on and write a test module for our buy tokens feature.
Test module for buy tokens feature
In Exchange.test.js
, add a new describe()
method for the buyTokens()
function.
Add the following code in the file below the other describe()
functions
describe('buyTokens()', async () => {
it('Allows user to instantly purchase tokens from the exchange', async () => {
await exchange.buyTokens({ from: accounts[1], value: web3.utils.toWei('1', 'ether') })
})
})
If you run truffle test
in your command line now, you should get something similar to this
We are not quite done yet, on the line that says contract('Exchange', (accounts)
, replace accounts
with the following array,
[deployer, investor]
On the line that says await exchange.buyTokens({ from: accounts[1]
, replace accounts[1]
with the variable investor
and rerun truffle test
in your command line.
If you get an identical result to the image we got in our last running of truffle test
, it means everything is running correctly.
Also, we need to refactor our buyTokens()
test code and add all the variables in a before()
hook, just like we did to the previous tests.
Replace the entire describe()
method for buyTokens()
with the following code
describe('buyTokens()', async () => {
let result;
before(async () => {
result = await exchange.buyTokens({ from: investor, value: web3.utils.toWei('1', 'ether') })
})
it('Allows user to instantly purchase tokens from the exchange', async () => {
let investorBalance = await token.balanceOf(investor);
assert.equal(investorBalance.toString(), tokens('100'));
let exchangeBalance;
exchangeBalance = await token.balanceOf(exchange.address);
assert.equal(exchangeBalance.toString(), tokens('999900'))
exchangeBalance = await web3.eth.getBalance(exchange.address)
assert.equal(exchangeBalance.toString(), web3.utils.toWei('1', 'Ether'))
})
})
Save the file and run truffle test
in the command line, if you get a response like in the image below, then you are on the right path.
Finally, in Exchange.sol
, we wan to be able to emit an event whenever a token is purchased. The event emitted upon token buy can be used to create stuff like transaction histories and likes.
Under the line that says uint public rate = 100;
, add the following code
event TokenPurchased(
address account,
address token,
uint amount,
uint rate
);
Now, below the line that says token.transfer(msg.sender, tokenAmount);
, add the following code
emit TokenPurchased(msg.sender, address(token), tokenAmount, rate);
We want the test for buyTokens()
to also inspect this event for every transaction and help us confirm whether the values being returned are correct.
In the Exchange.test.js
file, below the line assert.equal(exchangeBalance.toString(), web3.utils.toWei('1', 'Ether'))
, add the following code
const event = result.logs[0].args;
assert.equal(event.account, investor);
assert.equal(event.token, token.address)
assert.equal(event.amount.toString(), tokens('100').toString());
assert.equal(event.rate.toString(), '100')
Run truffle test
and if you get a response like this image you are good to go
Lastly, we want to add a checker module that helps to check whether the exchange has enough tokens to be able to fill a buy order on the exchange, in a case where the exchange doesn't have enough tokens, the transaction has to fail and the funds returned to the buyer of the token.
So, in Exchnage.sol
, above the line that says in buyTokens()
function, add the following code
require(token.balanceOf(address(this)) >= tokenAmount);
Save the file and run the test again using truffle test
in the command line, if it executes successfully like the other tests then, great we have successfully created a functionality for buying our tokens with Ether on the chain.
We have come to the end of this session for this series, we will continue with building the needed functionalities in the next post.
@tipu curate
Congratulations @gotgame! You have completed the following achievement on the Hive blockchain and have been rewarded with new badge(s) :
You can view your badges on your board And compare to others on the Ranking
If you no longer want to receive notifications, reply to this comment with the word
STOP
Do not miss the last post from @hivebuzz:
Congratulations @gotgame! You received a personal badge!
You can view your badges on your board And compare to others on the Ranking
Do not miss the last post from @hivebuzz: