Home

Use Hardhat with Dijets Utility Chain

Introduction#

The Utility Chain is one of Dijets Primary Blockchains. Being an instance of Ethereum Virtual Machine, its API is almost identical to an Ethereum node's API. this allows the Utility Chain to offer the same interface as Ethereum but with much higher speed, higher throughput, lower fees and lower transaction confirmation times. These properties considerably improve the performance of DApps and the user experience of smart contracts.

The goal of this guide is to lay out best practices regarding writing, testing and deployment of smart contracts on Dijets's Utility Chain. We will build the smart contracts using Hardhat development environment.

Prerequisites#

NodeJS and Yarn#

First, install the LTS (long-term support) version of NodeJS. This is 18.x at the time of writing. NodeJS bundles npm.

Next, install yarn:


_10
npm install -g yarn

DijetsNodeGo and Dijets-Up#

DijetsNodeGo is a Dijets node implementation written in Go. Dijets-up is a tool to quickly deploy local test networks. Together, you can deploy local test networks and run tests on them.

Solidity and Dijets#

It is also helpful to have a basic understanding of Solidity and Dijets.

Dependencies#

Clone the Dijets smart contracts repo and install the necessary packages via yarn.


_10
git clone https://github.com/Dijets-Inc/dijets-smart-contracts-guide.git
_10
cd dijets-smart-contracts-guide
_10
yarn

Write Contracts#

Edit the ExampleERC20.sol contract in contracts/ExampleERC20.sol is an Open Zeppelin ERC20 contract. ERC20 is a popular smart contract interface. You can also add your own contracts.

Hardhat Config#

Hardhat uses hardhat.config.js as the configuration file. You can define tasks, networks, compilers and more in that file. For more information see here.

Here is an example of a pre-configured hardhat.config.ts file.


_56
import { task } from "hardhat/config"
_56
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"
_56
import { BigNumber } from "ethers"
_56
import "@nomiclabs/hardhat-waffle"
_56
_56
export default {
_56
solidity: {
_56
compilers: [
_56
{
_56
version: "0.5.16"
_56
},
_56
{
_56
version: "0.6.2"
_56
},
_56
{
_56
version: "0.6.4"
_56
},
_56
{
_56
version: "0.7.0"
_56
},
_56
{
_56
version: "0.8.0"
_56
}
_56
]
_56
},
_56
networks: {
_56
hardhat: {
_56
gasPrice: 225000000000,
_56
chainId: 98200 //Only specify a chainId if we are not forking
_56
},
_56
local: {
_56
url: 'http://localhost:9650/ext/bc/C/rpc',
_56
gasPrice: 225000000000,
_56
chainId: 12345,
_56
accounts: [
_56
"0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
_56
"0xbbc2865b76ba28016bc2255c7504d000e046ae01934b04c694592a6276988630",
_56
"0xcdbfd34f687ced8c6968854f8a99ae47712c4f4183b78dcc4a903d1bfe8cbf60",
_56
"0x86f78c5416151fe3546dece84fda4b4b1e36089f2dbc48496faf3a950f16157c",
_56
"0x750839e9dbbd2a0910efe40f50b2f3b2f2f59f5580bb4b83bd8c1201cf9a010a"
_56
]
_56
},
_56
testnet: {
_56
url: 'https://testnet.dijets.io/ext/bc/C/rpc',
_56
gasPrice: 225000000000,
_56
chainId: 98199,
_56
accounts: []
_56
},
_56
mainnet: {
_56
url: 'https://dijets.ukwest.cloudapp.azure.com:443/ext/bc/C/rpc',
_56
gasPrice: 225000000000,
_56
chainId: 98200,
_56
accounts: []
_56
}
_56
}
_56
}

This configures necessary network information to provide smooth interaction with Dijets. There are also some pre-defined private keys for testing on a local test network.

Hardhat Tasks#

You can define custom hardhat tasks in hardhat.config.ts. There are two tasks included as examples: accounts and balances.


_16
task("accounts", "Prints the list of accounts", async (args, hre): Promise<void> => {
_16
const accounts: SignerWithAddress[] = await hre.ethers.getSigners()
_16
accounts.forEach((account: SignerWithAddress): void => {
_16
console.log(account.address)
_16
})
_16
})
_16
_16
task("balances", "Prints the DJT account balances for each address", async (args, hre): Promise<void> => {
_16
const accounts: SignerWithAddress[] = await hre.ethers.getSigners()
_16
for(const account of accounts){
_16
const balance: BigNumber = await hre.ethers.provider.getBalance(
_16
account.address
_16
);
_16
console.log(`${account.address} has balance ${balance.toString()}`);
_16
}
_16
})

npx hardhat accounts prints the list of accounts. npx hardhat balances prints the list of DJT account balances. As with other yarn scripts you can pass in a --network flag to hardhat tasks.

Accounts#

Prints a list of accounts on the local network started with Dijets-up.


_10
npx hardhat accounts --network local

Balances#

Prints a list of accounts and their corresponding DJT balances on the local network started with Dijets-up.


_10
npx hardhat balances --network local

ERC20 Balances#


_10
task("check-erc20-balance", "Prints out the ERC20 balance of your account").setAction(async function (taskArguments, hre) {
_10
const genericErc20Abi = require("./erc20.abi.json");
_10
const tokenContractAddress = "0x...";
_10
const provider = ethers.getDefaultProvider("https://dijets.ukwest.cloudapp.azure.com:443/ext/bc/C/rpc");
_10
const contract = new ethers.Contract(tokenContractAddress, genericErc20Abi, provider);
_10
const balance = await contract.balanceOf("0x...");
_10
console.log(`Balance in wei: ${balance}`)
_10
});

This will return the result in wei. If you want to know the exact amount of token with its token name then you need to divide it with its decimal.

Here's an example of erc20.abi.json - genericErc20Abi

The example uses the Utility Chain Public API for the provider. For a local Dijets network use http://127.0.0.1:9650/ext/bc/C/rpc and for Testnet use https://testnet.dijets.io/ext/bc/C/rpc.

Hardhat Help#

Run yarn hardhat to list Hardhat's version, usage instructions, global options and available tasks.

Compile Smart Contracts#

In package.json there's a script named compile for compiling smart contracts


_10
"compile": "npx hardhat compile",

To compile the smart contract run:

Terminal

_10
npx hardhat compile

Deploy Smart Contracts#

Hardhat enables deploying to multiple environments. You can do so by editing the deployment script in scripts/deploy.ts


_10
"deploy": "npx hardhat run scripts/deploy.ts",

You can choose which environment you want to deploy to by passing in the --network flag with local (for example a local network created with Dijets-Up CLI), testnet, or mainnet for each respective environment. If you don't pass in --network then it will default to the hardhat network. For example, if you want to deploy to Mainnet:

To deploy the contract to Mainnet run:

Terminal

_10
npx hardhat run scripts/deploy.ts --network mainnet

To deploy the contract to your local network run:

Terminal

_10
npx hardhat run scripts/deploy.ts --network local

We now have a token deployed at 0x17aB05351fC94a1a67Bf3f56DdbB941aE6.

Interact with Smart Contract#

Hardhat has a developer console to interact with contracts and the network. For more information about Hardhat's console see here. Hardhat console is a NodeJS-REPL, and you can use different tools in it. Ethers is the library that we'll use to interact with our network.

You can open the hardhat console with:

Terminal

_10
npx hardhat console --network local

Get the contract instance with factory and contract address to interact with our contract:


_10
> const Coin = await ethers.getContractFactory('ExampleERC20');
_10
undefined
_10
> const coin = await Coin.attach('0x17aB05351fC94a1a67Bf3f56DdbB941aE6')
_10
undefined

The first line retrieves contract factory with ABI & bytecode. The second line retrieves an instance of that contract factory with given contract address. Recall that our contract was already deployed to 0x17aB05351fC94a1a67Bf3f56DdbB941aE6 in the previous step.

Fetch the accounts:


_10
> let accounts = await ethers.provider.listAccounts()
_10
undefined
_10
> accounts
_10
[
_10
'0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC',
_10
'0x78A23300E04FB5d5D2820E23cc679738982e1fd5',
_10
'0x3C7daE394BBf8e9EE1359ad14C1C47003bD06293',
_10
'0x61e0B3CD93F36847Abbd5d40d6F00a8eC6f3cfFB',
_10
'0x0Fa8EA536Be85F32724D57A37758761B86416123'
_10
]

This is exactly the same account list as in yarn accounts.

Now we can interact with our ERC-20 contract:


_10
> let value = await coin.balanceOf(accounts[0])
_10
undefined
_10
> value.toString()
_10
'123456789'
_10
> value = await coin.balanceOf(accounts[1])
_10
BigNumber { _hex: '0x00', _isBigNumber: true }
_10
> value.toString()
_10
'0'

account[0] has a balance because account[0] is the default account. The contract is deployed with this account. The constructor of ERC20.sol mints TOTAL_SUPPLY of 123456789 token to the deployer of the contract.

accounts[1] currently has no balance. Send some tokens to accounts[1], which is 0x9632a79656af553F58738B0FB750320158495942.


_25
> let result = await coin.transfer(accounts[1], 100)
_25
undefined
_25
> result
_25
{
_25
hash: '0x35eec91011f9089ba7689479617a90baaf8590395b5c80bb209fa7000e4848a5',
_25
type: 0,
_25
accessList: null,
_25
blockHash: null,
_25
blockNumber: null,
_25
transactionIndex: null,
_25
confirmations: 0,
_25
from: '0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC',
_25
gasPrice: BigNumber { _hex: '0x34630b8a00', _isBigNumber: true },
_25
gasLimit: BigNumber { _hex: '0x8754', _isBigNumber: true },
_25
to: '0x17aB05351fC94a1a67Bf3f56DdbB941aE6c63E25',
_25
value: BigNumber { _hex: '0x00', _isBigNumber: true },
_25
nonce: 3,
_25
data: '0xa9059cbb0000000000000000000000009632a79656af553f58738b0fb7503201584959420000000000000000000000000000000000000000000000000000000000000064',
_25
r: '0xc2b9680771c092a106eadb2887e5bff41fcda166c8e00f36ae79b196bbc53d36',
_25
s: '0x355138cb5e2b9f20c15626638750775cfc9423881db374d732a8549d05ebf601',
_25
v: 86260,
_25
creates: null,
_25
chainId: 98200,
_25
wait: [Function (anonymous)]
_25
}

Note: Since this is a local network, we did not need to wait until transaction is accepted. However for other networks like testnet or mainnet you need to wait until transaction is accepted with: await result.wait().

Now we can ensure that tokens are transferred:


_10
> value = await coin.balanceOf(accounts[0])
_10
BigNumber { _hex: '0x075bccb1', _isBigNumber: true }
_10
> value.toString()
_10
'123456689'
_10
> value = await coin.balanceOf(accounts[1])
_10
BigNumber { _hex: '0x64', _isBigNumber: true }
_10
> value.toString()
_10
'100'

As you might noticed there was no "sender" information in await coin.transfer(accounts[1], 100); this is because ethers uses the first signer as the default signer. In our case this is account[0]. If we want to use another account we need to connect with it first.


_10
> let signer1 = await ethers.provider.getSigner(1)
_10
> let contractAsSigner1 = coin.connect(signer1)

Now we can call the contract with signer1, which is account[1].


_23
> await contractAsSigner1.transfer(accounts[0], 5)
_23
{
_23
hash: '0x807947f1c40bb723ac312739d238b62764ae3c3387c6cdbbb6534501577382dd',
_23
type: 0,
_23
accessList: null,
_23
blockHash: null,
_23
blockNumber: null,
_23
transactionIndex: null,
_23
confirmations: 0,
_23
from: '0x9632a79656af553F58738B0FB750320158495942',
_23
gasPrice: BigNumber { _hex: '0x34630b8a00', _isBigNumber: true },
_23
gasLimit: BigNumber { _hex: '0x8754', _isBigNumber: true },
_23
to: '0x17aB05351fC94a1a67Bf3f56DdbB941aE6c63E25',
_23
value: BigNumber { _hex: '0x00', _isBigNumber: true },
_23
nonce: 2,
_23
data: '0xa9059cbb0000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fc0000000000000000000000000000000000000000000000000000000000000005',
_23
r: '0xcbf126dd0b109491d037c5f3af754ef2d0d7d06149082b13d0e27e502d3adc5b',
_23
s: '0x5978521804dd15674147cc6b532b8801c4d3a0e94f41f5d7ffaced14b9262504',
_23
v: 86259,
_23
creates: null,
_23
chainId: 98200,
_23
wait: [Function (anonymous)]
_23
}

Let's check balances now:


_10
> value = await coin.balanceOf(accounts[0])
_10
BigNumber { _hex: '0x075bccb6', _isBigNumber: true }
_10
> value.toString()
_10
'123456694'
_10
> value = await coin.balanceOf(accounts[1])
_10
BigNumber { _hex: '0x5f', _isBigNumber: true }
_10
> value.toString()
_10
'95'

Join Dijets Public support space on Qowalts to learn more and ask any questions you may have.