This article will discuss how to get started using the Web3.js library, widely regarded as the most popular JavaScript framework for dealing with the Ethereum Blockchain.
2004 marked the rise in popularity of the term “Web 2.0” – which describes the emerging focus of websites on user-generated content, responsive interfaces, and interoperability.
DApp helped take the World Wide Web to its next stage, by introducing decentralized applications with a peer-to-peer protocol and putting them to use in every aspect of web applications. The term used to describe this evolution is Web3 – which was proposed by Gavin Wood. Web3 represents a new vision and focus for web applications, moving away from centralized applications management centers to applications built on decentralized protocols.
Contents
What is blockchain?
Blockchain refers to a type of distributed database shared across different nodes of a network. Within it, information is kept in blocks – connected together with a “chain”.
Blockchains are widely known for their connection with the cryptocurrency system (e.g: Bitcoin) – specifically, it plays an important role in maintaining a secure and decentralized record of transactions. The use of blockchain guarantees the fidelity and security of data without the need for a third party.
Cryptocurrency is based on the decentralized blockchain, in which information is spread among multiple devices – so that no single party owns it. All transactions are ensured to be transparent and immutable – they can’t be changed once recorded in the systems.
What is Web3.js?
When it comes to designing blockchain apps with Ethereum, there are a few key elements to consider:
- Smart contract development – Writing code that is deployed to the blockchain using the Solidity programming language.
- Creating websites that interact with the blockchain – writing code that uses smart contracts to read and publish data from the blockchain.
The term “web3.js” refers to a set of libraries that utilizes HTTP, IPC, or WebSocket to communicate with a local or distant Ethereum node. With it, users are able to effectively design websites/clients that interact with the Ethereum Blockchain.
Web3.js makes it possible to transmit Ether from one account to another, read and write data from smart contracts, build smart contracts, and so on. For developers who have used jQuery to conduct Ajax calls to the server before, Web3.js presents a nice alternative solution to developing the Ethereum Blockchain.
Below is an illustration of how a client communicates to Ethereum:
(Source: iotbl)
Web3.js communicates with the Ethereum Blockchain via the JSON RPC (i.e: “Remote Procedure Call”) protocol. Ethereum acts as a peer-to-peer network of nodes that retains a copy of the blockchain’s data. Developers can use Web3.js to send JSON RPC queries to specific Ethereum nodes – so that they can read and write data to the network. This is pretty similar to reading and writing data with a web server utilizing jQuery and a JSON API.
Web3.js tutorial – Learn the basics
When it comes to Web3.js, there are some basics that you need to know beforehand.
Node Package Manager (NPM)
Node Package Manager (NPM), which comes with Node.js, is a dependency of Web3.js. To check if Node is installed or not, go to your terminal and type:
$ node -v
Web3.js Library
The Web3.js library can be installed with NPM in your terminal by using the following command:
$ npm install web3
Infura RPC URL
Access to an Ethereum node on the Main Net is required to connect to it with JSON RPC. This can be accomplished in a number of ways. You may, for example, use Geth or Parity to run your own Ethereum node. However, this necessitates downloading and maintaining a large amount of data from the blockchain.
To make it simpler, Infura – a service that offers a free remote Ethereum node – can be used to access an Ethereum node without the need to operate one yourself. Simply sign up to acquire an API key and the RPC URL for the network you wish to connect to.
After registration, you should land on an Infura RPC URL:
https://mainnet.infura.io/YOUR_INFURA_API_KEY
Account Balances
Once all essential dependencies have been installed, it’s time to develop with Web3.js. Initially, you may open the Node console by typing like this:
$ node
After that, use the following command to request Web3.js:
const Web3 = require('web3')
Before creating a Web3 connection, you need to assign the Infura URL to a variable like this:
const rpcURL = "https://mainnet.infura.io/YOUR_INFURA_API_KEY"
Remember to replace YOUR_INFURA_API_KEY with the Infura API key that you received before. Then, you can build a live Web3 connection like this:
const web3 = new Web3(rpcURL)
With this, you now are able to communicate to the Ethereum main net – as well as check the account balance with web3.eth.getBalance().
First, you need to assign the address to a variable:
const account = "0x90e63c3d53E0Ea496845b7a03ec7548B70014A91"
Then, use the following command to check the balance:
web3.eth.getBalance(address, (err, wei) => { balance = web3.utils.fromWei(wei, 'ether') })
Full documentation for Web3.js can be found here: https://web3js.readthedocs.io/en/1.0/
Github repository: https://github.com/ethereum/web3.js/
How to Read Smart Contract Data with Web3.js
The web3.eth.Contract() function can be used to return a JavaScript representation of an Ethereum smart contract. This function requires two arguments: the smart contract ABI (Abstract Binary Interface) and the smart contract address.
ABI refers to a JSON array that explains how a given smart contract operates. Below is an example:
const abi = [{"constant":true,"inputs":[],"name":"mintingFinished","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"},{"name":"_releaseTime","type":"uint256"}],"name":"mintTimelocked","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[],"name":"MintFinished","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]
Now, we will store the address to the OMG token from the Ethereum main net:
const address = "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"
After that, we can develop a full JavaScript representation of the OMG token smart contract – which may look like this:
const contract = new web3.eth.Contract(abi, address)
Now, we’re ready to read data from the smart contract by calling its functions. All smart contract functions are listed under the contract.methods namespace within the assigned Web3 contract. For instance, we can call contract.methods.myFunction() if the contract implements myFunction().
Using the above-mentioned method, it’s possible to call virtually any function of the smart contract. However, how can we figure out the specific functions that are implemented?
We can read each of those values individually, like this:
First, the total supply of all OMG tokens in existence:
contract.methods.totalSupply().call((err, result) => { console.log(result) }) // > 140245398
Second, the name of the OMG token:
contract.methods.name().call((err, result) => { console.log(result) }) // > OMG Token
Third, the symbol of the OMG token:
contract.methods.symbol().call((err, result) => { console.log(result) }) // > OMG
Last but not least, it’s time to check the account balance:
contract.methods.balanceOf('0xd26114cd6EE289AccF82350c8d8487fedB8A0C07').call((err, result) => { console.log(result) }) // > A very large number...
Reference: Web3 on Github
How to Conduct Transactions with Web3.js
It’s necessary to sign transactions first before broadcasting them to the network. To do this, we can use ethereumjs-tx, which can be installed via the following command:
$ npm install ethereumjs-tx
Within the app.js file, we can then require the newly installed library like this:
var Tx = require('ethereumjs-tx')slacks
After that, let’s set up a Web3 connection:
const Web3 = require('web3') const web3 = new Web3('https://ropsten.infura.io/YOUR_INFURA_API_KEY')
Because all transactions cost gas in the form of Ether, you may want to use a test network beforehand. The Ropsten test net provides a great platform to use fake Ether without spending any money.
In order to initiate a transaction that sends fake Ether from one account to another, two accounts – along with their private keys – are needed. To create a new account with Web3.js, use the following command:
web3.eth.accounts.create() // > { // address: "0xb8CE9ab6943e0eCED004cDe8e3bBed6568B2Fa01", // privateKey: "0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709", // signTransaction: function(tx){...}, // sign: function(data){...}, // encrypt: function(password){...} // }
Once it is done, make sure to load the accounts up with fake Ether from a faucet. Then, we’ll assign them to variables in the script like this:
const account1 = '0xb8CE9ab6943e0eCED004cDe8e3bBed6568B2Fa01' const account2 = '0xb8CE9ab6943e0eCED004cDe8e3bBed6568B2Fa02'
After that, save the private keys to the environment like this:
export PRIVATE_KEY_1='your private key 1 here' export PRIVATE_KEY_1='your private key 2 here'
To sign transactions with the private keys, it’s necessary to convert them to a string of binary data with a Buffer, one common module in NodeJS:
const privateKey1 = Buffer.from(process.env.PRIVATE_KEY_1) const privateKey1 = Buffer.from(process.env.PRIVATE_KEY_2)
From this point, we can build the transaction like this:
const txObject = { nonce: web3.utils.toHex(txCount), to: account2, value: web3.utils.toHex(web3.utils.toWei('0.1', 'ether')), gasLimit: web3.utils.toHex(21000), gasPrice: web3.utils.toHex(web3.utils.toWei('10', 'gwei')) }
(in which:
nonce – the previous transaction count, which must be converted to hexidecimal with the Web3.js utilitly web3.utils.toHex()
to – the account we’re sending Ether to.
value – the amount of Ether we want to send.
gasLimit – the maximum amount of gas consumed by the transaction.
gasPrice – the amount we want to pay for each unit of gas.)
Now, let’s wrap all codes inside a callback function like this:
web3.eth.getTransactionCount(account1, (err, txCount) => { const txObject = { nonce: web3.utils.toHex(txCount), to: account2, value: web3.utils.toHex(web3.utils.toWei('0.1', 'ether')), gasLimit: web3.utils.toHex(21000), gasPrice: web3.utils.toHex(web3.utils.toWei('10', 'gwei')) } })
Step is completed – it’s time to sign the transaction, by inputting this command:
const tx = new Tx(txObject) tx.sign(privateKey1) const serializedTx = tx.serialize() const raw = '0x' + serializedTx.toString('hex')
In this example, we use the etheremjs-tx package to build a new Tx object. This library is also used to sign the transaction with privateKey1. The transaction is then serialized and converted to a hexadecimal string, which can be provided to Web3.
In the last step, we use the web3.eth.sendSignedTransaction() function to send this signed serialized transaction to the test network, as follows:
web3.eth.sendSignedTransaction(raw, (err, txHash) => { console.log('txHash:', txHash) })
Calling Smart Contract Functions with Web3.js
We’ll use the same basic setup with an app.js file that looks similar to this:
const Web3 = require('web3') const web3 = new Web3('https://ropsten.infura.io/YOUR_INFURA_API_KEY') const account1 = '' // Your account address 1 const account2 = '' // Your account address 2 const privateKey1 = Buffer.from('YOUR_PRIVATE_KEY_1', 'hex') const privateKey2 = Buffer.from('YOUR_PRIVATE_KEY_2', 'hex')
We’ll also build out a transaction object like this:
const txObject = { nonce: web3.utils.toHex(txCount), gasLimit: web3.utils.toHex(800000), gasPrice: web3.utils.toHex(web3.utils.toWei('10', 'gwei')), to: contractAddress, data: data }
In order to fill these values out, we’ll need to get the smart contract ABI for this ERC-20 token.
Now, we can create a JavaScript representation of the smart contract with Web3.js like this:
const contractAddress = '0xd03696B53924972b9903eB17Ac5033928Be7D3Bc' const contractABI = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"standard","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event"}] const contract = new web3.eth.Contract(abi, contractAddress)
Now, let’s fill out the data field of the transaction by converting the contract’s transfer() function to bytecode. This is achievable with the Web3.js function encodeABI() available on the contract object – which looks like this:
const data = contract.methods.transfer(account2, 1000).encodeABI()
Once the transaction has been signed and sent, we can log the values of the account balances to see that the smart contract function was called, and that the token transfers were complete. The tutorial code should look like this:
const Web3 = require('web3') const web3 = new Web3('https://ropsten.infura.io/YOUR_INFURA_API_KEY') const account1 = '' // Your account address 1 const account2 = '' // Your account address 2 const privateKey1 = Buffer.from('YOUR_PRIVATE_KEY_1', 'hex') const privateKey2 = Buffer.from('YOUR_PRIVATE_KEY_2', 'hex') // Read the deployed contract - get the addresss from Etherscan const contractAddress = '0xd03696B53924972b9903eB17Ac5033928Be7D3Bc' const contractABI = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"standard","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event"}]
const contract = new web3.eth.Contract(abi, contractAddress) // Transfer some tokens web3.eth.getTransactionCount(account1, (err, txCount) => { const txObject = { nonce: web3.utils.toHex(txCount), gasLimit: web3.utils.toHex(800000), // Raise the gas limit to a much higher amount gasPrice: web3.utils.toHex(web3.utils.toWei('10', 'gwei')), to: contractAddress, data: contract.methods.transfer(account2, 1000).encodeABI() } const tx = new Tx(txObject) tx.sign(privateKey1) const serializedTx = tx.serialize() const raw = '0x' + serializedTx.toString('hex') web3.eth.sendSignedTransaction(raw, (err, txHash) => { console.log('err:', err, 'txHash:', txHash) // Use this txHash to find the contract on Etherscan! }) }) // Check Token balance for account1 contract.methods.balanceOf(account1).call((err, balance) => { console.log({ err, balance }) }) // Check Token balance for account2 contract.methods.balanceOf(account2).call((err, balance) => { console.log({ err, balance }) })
Now you can run the app.js file from your terminal with NodeJS like this:
$ node app.js
Or simply:
$ node app
Inspecting Blocks with Web3.js
Inspecting blocks is often useful when analyzing history on The Ethereum Blockchain. Web3.js has lots of functionality that helps us to do just that.
For instance, we could build something that looks like this block history feature on Etherscan:
Let’s set up an app.js file to start using some of this functionality provided by Web3.js. We’ll connect to the main net to inspect blocks there:
const Web3 = require('web3') const web3 = new Web3('https://mainnet.infura.io/YOUR_INFURA_API_KEY')
First, we can get the latest block number like this:
web3.eth.getBlockNumber().then(console.log)
We can also get all the data for the latest block like this:
web3.eth.getBlock('latest').then(console.log)
If we were going to build a block history feature like the one on Etherscan pictured above, we would need to get a list of the most recent blocks in the chain. We can do this by fetching the most recent block and counting backward until we have the last 10 blocks in the chain. We can do that with a for loop like this:
web3.eth.getBlockNumber().then((latest) => { for (let i = 0; i < 10; i++) { web3.eth.getBlock(latest - i).then(console.log) } })
Web3.js has another nice feature that allows you to inspect transactions contained within a specific block. We can do that like this:
const hash = '0x66b3fd79a49dafe44507763e9b6739aa0810de2c15590ac22b5e2f0a3f502073' web3.eth.getTransactionFromBlock(hash, 2).then(console.log)
Conclusion
And that concludes our web3.js tutorial. If you are interested and would like to receive more updates, subscribe to JSLancer’s newsletter and follow us on social media now.
In addition, for those who are thinking about starting their own blockchain project, we are more than happy to help. Reach out to JSLancer for a free call on how we can work together now!
Before you read another post…