Build an Ethereum dapp using ethers.js
If you have built dapps on Ethereum, you most likely have used Web3.js to build your javascript frontend. Ethers.js is a lightweight javascript library which can be used as an alternative to Web3.js to build your javascript frontend and interact with the Ethereum blockchain. I had a chance to tinker with Ethers.js recently and was impressed with it. In this post/tutorial, I show how you can use Ethers.js to build a simple dapp. I hope this helps you evaluate and get started using Ethers.js for your next project.
In this tutorial, we will create a simple Voting dapp (very similar to our popular ‘Hello world voting dapp’ many of you might know!). The Solidity contract will be the same but we will use Ethers.js instead of Web3.js for the frontend. The application is extremely simple, all it does is initialize a set of candidates, let anyone vote for those candidates and display the total votes received by each candidate.
If you are new to Ethereum, here are some courses that can help you get started.
The goal of this tutorial is to:
1. Set up the development environment
2. Learn the process of writing a contract, compiling it and deploying it in your development environment
3. Interact with the contract using ethers.js through a nodejs console
4. Interact with the contract using ethers.js in a simple web page to display the vote counts and vote for candidates through the page
If you have already worked through the ‘Hello world voting dapp’, you can skip to Step 3.
This is how I would visualize this application we are going to build.
1. Setting up the development environment
Instead of developing the app against the live blockchain, we will use an in- memory blockchain (think of it as a blockchain simulator) called ganache. Below are the steps to install ganache, ethersjs, solc (to compile our contract) and start the test blockchain on a macos. The exact same instructions work on linux as well.
zastrin@macbook$ brew update
zastrin@macbook$ brew install nodejs
zastrin@macbook$ mkdir -p ethereum_voting_dapp/chapter1-ethersjs
zastrin@macbook$ cd ethereum_voting_dapp/chapter1-ethersjs
zastrin@macbook$ npm install ganache-cli ethers solc@0.5.3
zastrin@macbook$ node_modules/.bin/ganache-cliGanache CLI v6.0.3 (ganache-core: 2.0.2)Available Accounts==================(0) 0x5c252a0c0475f9711b56ab160a1999729eccce97
(1) 0x353d310bed379b2d1df3b727645e200997016ba3
(2) 0xa3ddc09b5e49d654a43e161cae3f865261cabd23
(3) 0xa8a188c6d97ec8cf905cc1dd1cd318e887249ec5
(4) 0xc0aa5f8b79db71335dacc7cd116f357d7ecd2798
(5) 0xda695959ff85f0581ca924e549567390a0034058
(6) 0xd4ee63452555a87048dcfe2a039208d113323790
(7) 0xc60c8a7b752d38e35e0359e25a2e0f6692b10d14
(8) 0xba7ec95286334e8634e89760fab8d2ec1226bf42
(9) 0x208e02303fe29be3698732e92ca32b88d80a2d36Private Keys==================(0) a6de9563d3db157ed9926a993559dc177be74a23fd88ff5776ff0505d21fed2b
(1) 17f71d31360fbafbc90cad906723430e9694daed3c24e1e9e186b4e3ccf4d603
(2) ad2b90ce116945c11eaf081f60976d5d1d52f721e659887fcebce5c81ee6ce99
(3) 68e2288df55cbc3a13a2953508c8e0457e1e71cd8ae62f0c78c3a5c929f35430
(4) 9753b05bd606e2ffc65a190420524f2efc8b16edb8489e734a607f589f0b67a8
(5) 6e8e8c468cf75fd4de0406a1a32819036b9fa64163e8be5bb6f7914ac71251cc
(6) c287c82e2040d271b9a4e071190715d40c0b861eb248d5a671874f3ca6d978a9
(7) cec41ef9ccf6cb3007c759bf3fce8ca485239af1092065aa52b703fd04803c9d
(8) c890580206f0bbea67542246d09ab4bef7eeaa22c3448dcb7253ac2414a5362a
(9) eb8841a5ae34ff3f4248586e73fcb274a7f5dd2dc07b352d2c4b71132b3c73fHD Wallet==================
Mnemonic: cancel better shock lady capable main crunch alcohol derive alarm duck umbrella
Base HD Path: m/44'/60'/0'/0/{account_index}
Listening on localhost:8545
Notice that the ganache-cli creates 10 test accounts to play with automatically. These accounts come preloaded with 100 (fake) ethers.
2. Simple voting contract
We are going to use the solidity programming language to write our contract. If you are familiar with object oriented programming, learning to write solidity contracts should be a breeze. We will write a contract (think of contract as a class in your favorite OOP language) called Voting with a constructor which initializes an array of candidates. We will write 2 methods, one to return the total votes a candidate has received and another method to increment vote count for a candidate.
Note: The constructor is invoked once and only once when you deploy the contract to the blockchain. Unlike in the web world where every deploy of your code overwrites the old code, deployed code in the blockchain is immutable. i.e, If you update your contract and deploy again, the old contract will still be in the blockchain untouched along with all the data stored in it, the new deployment will create a new instance of the contract.
Below is the voting contract code with inline comment explanation:
Copy the above code to a file named Voting.sol in the ethereum_voting_dapp/chapter1-ethersjs directory. Now let’s compile the code and deploy it to ganache blockchain. Follow the commands below to compile the contract.
zastrin@macbook$ node_modules/.bin/solcjs — bin — abi Voting.sol
zastrin@macbook$ ls
Voting.sol Voting_sol_Voting.abi Voting_sol_Voting.bin
When you compile the code successfully using the command above, the compiler outputs 2 files that are important to understand:
1. Voting_sol_Voting.bin: This is the bytecode you get when the source code in Voting.sol is compiled. This is the code which will be deployed to the blockchain.
2. Voting_sol_Voting.abi: This is an interface or template of the contract (called abi) which tells the contract user what methods are available in the contract. Whenever you have to interact with the contract in the future, you will need this abi definition. You can read more details about ABI here
We will now use ethersjs library to deploy our application and interact with it.
First, run the ‘node’ command in your terminal to get in to the node console and initialize the ethers object. All the code snippets below need to be typed in the node console. To compile the contract, load the bytecode and abi from the file system in to a string like shown below
zastrin@macbook$ node
> ethers = require(‘ethers’)
> bytecode = fs.readFileSync(‘Voting_sol_Voting.bin’).toString()
> abi = JSON.parse(fs.readFileSync(‘Voting_sol_Voting.abi’).toString())
Next is to initialize the provider which is a generic interface to connect to the blockchain. Since we are running the blockchain locally, we will use the JsonRPCProvider to connect to it. If you wanted to connect to a real blockchain, you have many other provider options. Once connected, you can test the connection by querying ganache and listing all the accounts. Running the below commands in your nodejs console should list 10 accounts.
> provider = new ethers.providers.JsonRpcProvider()
> provider.listAccounts().then(result => console.log(result))
The next step is to initialize the signer which is used to sign the transactions. We can select which account we want to use (we have 10 accounts ganache in setup) by passing the index to getSigner as shown below. Once the wallet is intialized, create a contract factory and deploy the contract as shown. The deploy function takes an array of arguments that is passed to the constructor of the contract. In our case, we pass in the names of the candidates. We have to explicitly convert string to bytes32 because our contract takes bytes32 as argument.
> signer = provider.getSigner(0)
> factory = new ethers.ContractFactory(abi, bytecode, signer)
> contract = null
> factory.deploy([ethers.utils.formatBytes32String(‘Rama’), ethers.utils.formatBytes32String(‘Nick’), ethers.utils.formatBytes32String(‘Jose’)]).then(© => { contract = c})
If you successfully deployed the contract, your contract object should have all the details of the deployed contract. There are hundreds of thousands of contracts deployed on the blockchain. So, how do you identify your contract in that blockchain? Answer: contract.address. When you have to interact with your contract, you need this deployed address and abi definition we talked about earlier.
3. Interact with the contract in the nodejs console
> contract.totalVotesFor(ethers.utils.formatBytes32String(‘Rama’)).then((f) => console.log(f.toNumber()))
> contract.voteForCandidate(ethers.utils.formatBytes32String(‘Rama’)).then((f) => console.log(f))
> contract.totalVotesFor(ethers.utils.formatBytes32String(‘Rama’)).then((f) => console.log(f.toNumber()))
Try the above commands in your node console and you should see the vote count increment. Every time you vote for a candidate, you get back a transaction id: Example: ‘0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53’ above). This transaction id is the proof that this transaction occurred and you can refer back to this at any time in the future. This transaction is immutable. This immutability is one of the big advantages of blockchains such as Ethereum.
4. Webpage to connect to the blockchain and vote
Now that most of the work is done, all we have to do now is create a simple html file with candidate names and invoke the voting commands (which we already tried and tested in the nodejs console) in a js file. Below you can find the html code and the js file. Drop both of them in the ethereum_voting_dapp/chapter1-ethersjs directory and open the index.html in your browser.
You should now see the vote counts and you should also be able to vote for candidates.
If you are using a wallet like Metamask, you will need to use a Web3 provider instead of JsonRPCProvider we used earlier. To do that, simply change the provider in your index.js to:
provider = new ethers.providers.Web3Provider(web3.currentProvider);
If you want to interact through Metamask, you can’t just open index.html and interact in your browser anymore. You have to server the file through a server. So, install a simple webserver and start like below
zastrin@macbook$ npm install http-server
zastrin@macbook$ http-server
You can now go to localhost:8080 and interact with your application. Below is a quick demo which includes loading test ether account into metamask.
I hope you were able to follow along and get the application working.
If you run into issues getting the application working, feel free to DM me on twitter @zastrinlab or email mahesh@zastrin.com.
Thanks to Richard for the feedback.
If you liked this tutorial, check out more interesting real-world project based courses