A-Z of building dApps on ICE — Part 1: Introduction and environment setup
What you will learn
- What ICE Network is and how it’s related to ICON and Polkadot
- Tools and environment setup for building dApps on ICE Network
- Create and fund wallet on Arctic Network
- Make a simple transaction on Arctic Network
- Ganache, your personal EVM blockchain
Introduction
ICE is an extension network and application hub of the ICON ecosystem.
Built with Substrate, ICE is the first network to use the Substrate SDK to extend the feature-set of an existing layer one blockchain protocol. In addition, ICE provides the much-needed addition of EVM (Ethereum Virtual Machine) compatibility to the ICON ecosystem.
ICE will also be the only EVM-compatible parachain on Polkadot ecosystem that is optimized for ICON’s BTP. This will position ICE as a potential flagship chain within the Polkadot ecosystem that showcases industry-leading cross-chain applications — all powered by ICON’s BTP.
For this tutorial series, we will be working on the Arctic Network(Since Frost testnet is deprecated), - current live testnet of Snow blockchain (Snow is the canary network of ICE, just like Kusama is the canary network of Polkadot).
Getting Started — Node.js and Ethers
Getting started on ICE is really easy because of the vast amount of community already built to support the EVM. This allows you to interact with the blockchain in just about any programming language including: Java, Python, JavaScript, Go, Rust, .NET, Delphi and Dart.
Throughout this course we will be focusing on JavaScript because of its massive popularity and ease of use.
Installing Node.js
Node.js is a runtime environment which includes everything needed to execute a program written in JavaScript.
$ sudo apt install nodejs$ node --version
v14.17.0
Installing npm
Node Package Manager (npm) is an open source package manager for the Node JavaScript platform.
$ sudo apt install npm$ npm --version
6.14.13
Ethers
The ethers.js is a library that aims to be a complete and compact library for interacting with EVM-compatible blockchains and their ecosystem. It was originally designed for use with ethers.io and has since expanded into a more general-purpose library.
Installing Ethers
On a fresh working folder, run:
$ npm install --save ethers
Now that we have our setup out of the way, we can now start to interacting with the blockchain. We will be utilizing the node runtime environment for this purpose. Start the node by simply typing node
to the terminal. We will greeted with the node environment.
$ node
Welcome to Node.js v14.17.0.
Type ".help" for more information.
>
Importing Ethers
> const { ethers } = require(“ethers”);
Before making any queries, we have to set up a Provider — a JavaScript class which provides an abstraction for a connection to the Blockchain Network. It provides read-only access to the Blockchain and its status. We will be using Arctic Testnet as Provider.
Querying the Blockchain
> const provider = new ethers.providers.JsonRpcProvider("https://arctic-rpc.icenetwork.io:9933");//Get the current block number
> provider.getBlockNumber().then(res => console.log(res))
108981
NOTE: Press Enter key if JS promise blocks your terminal
As an example, you can get the details of a specific block number
//Get details of the block
> provider.getBlock(108981).then(res => console.log(res))
which gives output that looks something like
{
hash: '0xd6bbcb783700de1e3aa5e7e92b8c7d9f46f59593e2eb504b6b56f9e9e0e558fb',
parentHash: '0xfd6c4cbf33ae9fe425fa2ab71d5a633adf00d9152ca977871ab858561a2a99f5',
number: 108981,
timestamp: 1645193964,
difficulty: 0,
gasLimit: BigNumber { _hex: '0xffffffff', _isBigNumber: true },
gasUsed: BigNumber { _hex: '0x00', _isBigNumber: true },
miner: '0xBfF94C8C44Af38b8a92C1d5992D061D41f700C76',
extraData: '0x',
transactions: [],
_difficulty: BigNumber { _hex: '0x00', _isBigNumber: true }
}
At this point, you are already able to make some queries to the blockchain. You can explore about the queries that you can make using this link.
Creating a Wallet
//Creating a new wallet randomly
> const wallet = ethers.Wallet.createRandom()//Create a new Wallet instance with existing privateKey
> const wallet = ethers.Wallet(privateKey)//Print the wallet address
> wallet.address//Encrypting the wallet in JSON with password
> encrypted_json = wallet.encrypt('password_here')//Saving the encrypted wallet
> const fs = require('fs')
> encrypted_json.then(json => fs.writeFileSync('wallet', json))//Recovering the encrypted wallet
> let encrypted_wallet = fs.readFileSync('wallet', {encoding: 'utf8'})//Replace password_here with the password you set for the wallet
> let wallet_ = ethers.Wallet.fromEncryptedJson(encrypted_wallet, 'password_here')//Verify the recovered wallet is same as the one we created
> wallet_.then(w => w.address == wallet.address).then(res => console.log(res))
true// NOTE: Press Enter key if JS promise blocks your terminal
The creation of a wallet can also be achieved using Metamask, the most popular wallet for EVM based blockchains. Metamask uses of BIP-39 mnemonic phrases by default to recover your wallet instead of making direct use of private key or encrypted wallet.
Please go through this easy to follow tutorial from Lim on creating a new wallet with Metamask.
// Create a wallet instance from a mnemonic (your wallet backup phrase)...> backup_phrase = "<your_backup_phrase_here>"> walletMnemonic = ethers.Wallet.fromMnemonic(backup_phrase)
Funding wallet with testnet tokens
Now that we have our wallet setup, we can start making some transactions on Arctic Network. Before making a transaction let us first check the balance.
> provider.getBalance(wallet.address).then(bal => console.log(bal))
BigNumber { _hex: ‘0x00’, _isBigNumber: true }// NOTE: Press Enter key if JS promise blocks your terminal
Oops! you need to have some ICZ balance on your wallet before you can make any transactions on Arctic network.
However, we’ve got you covered. You can get test ICZ tokens into your account by joining the discord channel and pasting following command in the message box:
!send <evm_address>
Eg: !send 0x7c0f5F59A22B657C8D9E21b44D2Dc0118fD2BE7B
Find more about the Arctic Network faucet here.
Creating your first transaction
You can check your balance again after receiving test ICZ tokens from the faucet.
> provider.getBalance(wallet.address).then(bal => console.log(bal))
BigNumber { _hex: ‘0x8ac7230489e7fe0c’, _isBigNumber: true }// NOTE: Press Enter key if JS promise blocks your terminal
With enough tokens to cover our gas fees we can now start making transactions.
Let’s create a transaction object to send 1 ICZ to the wallet we imported from metamask.
tx = {
to: walletMnemonic.address,
value: ethers.utils.parseEther(“1.0”)
}
In the construction of our transaction, we have made use of the utility library provided by ethers to convert between user-friendly strings (usually displaying ether) and the machine-readable values that contracts and math depend on (usually in wei).
Now let’s actually send transaction to the Arctic Network.
> connected_wallet = wallet.connect(provider)
> connected_wallet.sendTransaction(tx)//Output
{
nonce: 1,
gasPrice: BigNumber { _hex: '0x01', _isBigNumber: true },
gasLimit: BigNumber { _hex: '0x5208', _isBigNumber: true },
to: '0x71CB05EE1b1F506fF321Da3dac38f25c0c9ce6E1',
value: BigNumber { _hex: '0x0de0b6b3a7640000', _isBigNumber: true },
data: '0x',
chainId: 553,
v: 1141,
r: '0x49c14440b7a8b4493f2d3112c68944b27ab7b536de3b4e0a87f06e88b54122a1',
s: '0x10bcd3ab81dade5583ea478e68835730eeff2d8e6f1125692a92745bda8831d4',
from: '0x4821C4aa31DEdFE01C36ed9F9ebab8Ba98509516',
hash: '0x06455b1991ddfba24bb070f8d704bc778d223fee80b19e714fcba471b4cfe379',
type: null,
confirmations: 0,
wait: [Function (anonymous)]
}
We can verify that the transaction actually went through and the txHash exists on Arctic network by following the instructions here.
Ganache
Ganache is a personal blockchain for rapid Ethereum and Corda distributed application development. You can use Ganache across the entire development cycle; enabling you to develop, deploy, and test your dApps in a safe and deterministic environment.
Since ICE is a fully EVM compatible blockchain, we can use Ganache to deploy smart contracts locally and test them out with a number of pre-funded wallets provided by Ganache.
Installing Ganache
You can download and install Ganache from this link.
Or, simply install it with npm.
$ npm install -g ganache
Starting ganache
$ ganacheganache v7.0.2 (@ganache/cli: 0.1.3, @ganache/core: 0.1.3)
Starting RPC serverAvailable Accounts
==================
(0) 0xb5A1FB0BA92D41783558f1BcC26d4B683A527898 (1000 ETH)
(1) 0x3773DF2125B277D9A68AfFe6D8aE64a0E1613CAB (1000 ETH)
(2) 0x77eF3259fb23945050826D71C94c6BaC4BC2f63d (1000 ETH)
(3) 0x2Cbd0BF569A17a41F3297e57ED83aebC8e2147D1 (1000 ETH)
(4) 0xD8306Cf8fcDcdA72F46f83E17d0119c468FE88AB (1000 ETH)
(5) 0x5438834965690b03547Dd8FC5EbF599d0E43E63C (1000 ETH)
(6) 0xf5Ae084abEFa5aFf3D851905710985Adb2a66518 (1000 ETH)
(7) 0xaE26fD20bd3BF1DC979Bc9F021FcCE35f500eab5 (1000 ETH)
(8) 0x6C1fb76696bc9b80c48D01359d171C98de743364 (1000 ETH)
(9) 0x548B4823b24c544D5B130EbD1Df611eAA03c7292 (1000 ETH)Private Keys
==================
(0) 0x656ed25385551d2eb12eecefbd2caa4660862b344f3ef17d6b9cc1c6201f4f6f
(1) 0x29cd6d979c2334a090525c53b8c4ccb16b3148d5b7ca7fdedaaea8dd81d14f2b
(2) 0x9541df40c5af0ed39c3484ddf33614f2894b30fb0087f14c85faded025437042
(3) 0x6bf650f05f371f5cc6b924f0a4d02fd9ec3ffa9de8a830bd14a1d416bc92b95b
(4) 0xaa7dfc0423df51580d7dfd897e5186374d2f4256877312a4a35728e9ef9425e2
(5) 0x6ae5680df64775b64c1d826fc5ac026d4ac8d569db8e4b123097458dbd6ea952
(6) 0x7b6c88535839f0cc84c272f5c3a47c37de289539acd0560d9b05f57ced755e15
(7) 0x1376bdc04032cde0a1f6cab10abe13bdc3c7e07c3cf681b94176372369313bce
(8) 0x4c89b9af175d57b321945d7c9a67a33a5b27fb328a22023e66c483373bcfdbd4
(9) 0xa4d2b757328f5d82ce036b5ee2d5501b59ced8a979bb9e66579aa1fa97240c59HD Wallet
==================
Mnemonic: knife because tribe ride confirm squeeze certain lava letter piano legend library
Base HD Path: m/44'/60'/0'/0/{account_index}Default Gas Price
==================
2000000000BlockGas Limit
==================
30000000Call Gas Limit
==================
50000000Chain Id
==================1337
RPC Listening on 127.0.0.1:8545
With our new local provider, we can setup a new provider and try running on the same queries we ran before.
> const local_provider = new ethers.providers.JsonRpcProvider();
> local_provider.getBlockNumber().then(blk => console.log(blk))
BigNumber { _hex: ‘0x00’, _isBigNumber: true }
Since there has been no transaction in our local blockchain, we are still at block 0. Let’s load up wallets using some private keys provided by Ganache when you run the ganache command, and make some transactions to populate blocks.
> wallet1 = new ethers.Wallet(‘0x656ed25385551d2eb12eecefbd2caa4660862b344f3ef17d6b9cc1c6201f4f6f’)
> wallet2 = new ethers.Wallet(‘0x29cd6d979c2334a090525c53b8c4ccb16b3148d5b7ca7fdedaaea8dd81d14f2b’)//Verify that we have balance in thee wallet1
> local_provider.getBalance(wallet1.address).then(bal => console.log(bal))
BigNumber { _hex: ‘0x3635c9adc5dea00000’, _isBigNumber: true }
Let’s create a transaction to send 10 eth from wallet1 to wallet2.
> tx = {
to: wallet2.address,
value: ethers.utils.parseEther(“10.0”)
}
Let’s now connect with our local provider and execute the transaction.
> wallet1 = wallet1.connect(local_provider)
> wallet1.sendTransaction(tx).then(res => console.log(res))//Output
{
type: 2,
chainId: 1337,
nonce: 0,
maxPriorityFeePerGas: BigNumber { _hex: ‘0x9502f900’, _isBigNumber: true },
maxFeePerGas: BigNumber { _hex: ‘0x010c388d00’, _isBigNumber: true },
gasPrice: null,
gasLimit: BigNumber { _hex: ‘0x5208’, _isBigNumber: true },
to: ‘0x3773DF2125B277D9A68AfFe6D8aE64a0E1613CAB’,
value: BigNumber { _hex: ‘0x8ac7230489e80000’, _isBigNumber: true },
data: ‘0x’,
accessList: [],
hash: ‘0x8570ad95847a5c3743eea40db95e97757ebd094594a7a6a70a28e1017baf6754’,
v: 0,
r: ‘0x65d0acd6f38cf10fc8b9f6f6b9698780d0fad4790e6e754e8c61a0d3ee1ed8f2’,
s: ‘0x794d663e793c3450bde6ddccb47b6ba08e6ff9ee01067105ad01bd767aac7273’,
from: ‘0xb5A1FB0BA92D41783558f1BcC26d4B683A527898’,
confirmations: 0,
wait: [Function (anonymous)]
}
If we look at the latest block number, it is 1 now.
> local_provider.getBlockNumber().then(blk => console.log(blk))
1
We can further explore the new block to see the transactions included in the block.
> local_provider.getBlock(1).then(blk => console.log(blk))
{
hash: ‘0xe04bbe4c9727d711b0fc208258d333690f875568f960622ef9c7900810cd37a1’,
parentHash: ‘0x09ac6d90a50d8919b14321eecc6ee1eedcbee1c959fa456fa783600d1566f208’,
number: 1,
timestamp: 1644569590,
nonce: ‘0x0000000000000000’,
difficulty: 1,
gasLimit: BigNumber { _hex: ‘0x01c9c380’, _isBigNumber: true },
gasUsed: BigNumber { _hex: ‘0x5208’, _isBigNumber: true },
miner: ‘0x0000000000000000000000000000000000000000’,
extraData: ‘0x’,
transactions: [
‘0x8570ad95847a5c3743eea40db95e97757ebd094594a7a6a70a28e1017baf6754’
],
baseFeePerGas: BigNumber { _hex: ‘0x342770c0’, _isBigNumber: true },
_difficulty: BigNumber { _hex: ‘0x01’, _isBigNumber: true }
}
We can clearly verify that the block includes just the recent transaction.
Summary
In this first part of the development on ICE series, we got acquiainted with the ICE Network and setup tools like Node.js and Ethers to interact with the Arctic network. We also went through creating and funding wallets, and made our first transaction to transfer balance from one account to another on the Arctic testnet. Finally, we deployed our own local private blockchain using Ganache, to make our development process easier.
In the next part, we will learn about writing smart contracts (business logic of dApps) using Solidity.