DEV Community

fangjun
fangjun

Posted on • Updated on

03 Understanding Blockchain with `ethers.js`: 4 Tasks of interacting with smart contract

In third part of this ethers.js tutorial, we will use ethers.js to interact with smart contract.

What we do here is a little beyond the scope of learning ethers.js:

  • Write a ERC20 token smart contract with OpenZeppelin
  • Deploy the smart contract to Hardhat local testnet
  • Interact with smart contract using Ethers.js

Task 1: Write ERC20 token smart contract

We will write an ERC20 token smart contract using the verified and popular solidity token library OpenZeppelin.

Task 1.1 Install OpenZeppelin

In the hardhat project directory, install OpenZeppelin:

yarn add @openzeppelin/contracts
Enter fullscreen mode Exit fullscreen mode

Task 1.2 Write smart contract in Solidity

There is a "Greeter.sol" and related test script "test/index.ts" and deploy script "scripts/deploy.ts" in the project. We will leave them there.

Add "contracts/classtoken.sol"

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";

contract ClassToken is ERC20PresetMinterPauser {
        constructor(uint256 initialSupply) ERC20PresetMinterPauser("ClassToken", "CLT") {
        _mint(msg.sender, initialSupply);
        }
}
Enter fullscreen mode Exit fullscreen mode

We create an ERC20 token smart contract which inherits ERC20PresetMinterPauser of OpenZeppelin. The token name is "ClassToken", symbol is "CLT".

Initial supply of this token will be set in deployment script. Initial supply of CLT will be sent to (mint to) deployer's address.

Compile smart contract by running:

yarn hardhat compile
Enter fullscreen mode Exit fullscreen mode

Task 1.3 Write Unit Test for smart contract

Unit test of smart contract is a must. We should cover smart contract with unit test as much as possible. The following script is included here to illustrate that we can't skip this step.

Add "test/ClassToken.ts":

import { expect } from "chai";
import { ethers } from "hardhat";

describe("ClassToken", function () {
  it("Should have the correct initial supply", async function () {
    const initialSupply = ethers.utils.parseEther('10000')
    const ClassToken = await ethers.getContractFactory("ClassToken");
    const token = await ClassToken.deploy(initialSupply);
    await token.deployed();

    expect(await token.totalSupply()).to.equal(initialSupply);
  });
});
Enter fullscreen mode Exit fullscreen mode

Run test:

yarn hardhat test
Enter fullscreen mode Exit fullscreen mode

Unit test passed. We will try to deploy it to a local testnet.


Task 2: Deploy smart contract to local testnet

We will deploy smart contract both to in-process and stand-alone local testnet to display usage and difference between these two.

Task 2.1: Write deployment script

Add "scripts/deploy_classtoken.ts":

import { ethers } from "hardhat";

async function main() {

  const initialSupply = ethers.utils.parseEther('10000')
  const ClassToken = await ethers.getContractFactory("ClassToken");
  const token = await ClassToken.deploy(initialSupply);
  await token.deployed();

  console.log("ClassToken deployed to:", token.address);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

The intialSupply is set to be 10000.0 CLS.

2.2 deploy on in-process local testnet.

yarn hardhat run scripts/deploy_classtoken.ts
//ClassToken deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Enter fullscreen mode Exit fullscreen mode

If you run the script again, you will find the smart contract is always deployed to the same address as in-process is new every time.

2.3 Run a stand-alone local testnet

Run a stand-alone local testnet at another terminal:

yarn hardhat node
Enter fullscreen mode Exit fullscreen mode

2.4 Deploy ClassToken to stand-alone local testnet

Deploy smart contract:

yarn hardhat run scripts/deploy_classtoken.ts --network localhost
//ClassToken deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Enter fullscreen mode Exit fullscreen mode

If you run the script again, you will find the smart contract is deployed to a new address:

yarn hardhat run scripts/deploy_classtoken.ts --network localhost
//ClassToken deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
Enter fullscreen mode Exit fullscreen mode

We will use the smart contract instance at: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512


Task 3: Interact with smart contract from console

Task 3.1 Get smart contract instance

Run console connecting to stand-alone local testnet:

yarn hardhat console --network localhost
Enter fullscreen mode Exit fullscreen mode

Get Smart contract:

const address = '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512';
const token = await ethers.getContractAt("ClassToken", address);
Enter fullscreen mode Exit fullscreen mode

Task 3.2 Read properties of the smart contract

Docs can be found at: https://docs.ethers.io/v5/api/contract/contract/#Contract--properties

token.interaface
token.provider
token.signer

token.signer.address
//'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
Enter fullscreen mode Exit fullscreen mode

Task 3.3 Get data from smart contract

parseEther =ethers.utils.parseEther
formatEther = ethers.utils.formatEther
await token.totalSupply().then((r)=>console.log(formatEther(r)))
//10000.0

await token.symbol()
//CLT
await token.name()
//ClassToken
Enter fullscreen mode Exit fullscreen mode

Task 3.4 Token balance of deployer

deployer='0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
await token.balanceOf(deployer).then((r)=>console.log(formatEther(r)))
//10000.0
Enter fullscreen mode Exit fullscreen mode

Task 3.5 Transfer Token from deployer to other

We will transfer CLS token by calling transfer() of ERC20 standard. You can also send ERC20 token using MeteMask in two steps: 1) add token using token address(contract address), 2) send some amount of it to other's address.

to_address='0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'
await token.transfer(to_address,parseEther('1000'))

await token.balanceOf(to_address).then((r)=>console.log(formatEther(r)))
//1000.0
Enter fullscreen mode Exit fullscreen mode

Now, only user with the correct private key of the address can use this token in address 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1.


Task 4: Connect wallet and transfer token

Note: you can refer to the second part of this tutorial "Understanding Blockchain with ethers.js: 4 Tasks of wallet" to know more about wallet usage.

Task 4.1 Create wallet

//account not in hardhat default
privateKey='0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d'
wallet = new ethers.Wallet(privateKey)

wallet.address
//'0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'
Enter fullscreen mode Exit fullscreen mode

Task 4.2 Send ethers from account 0 for gas fee usage

If adress 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 has no test ETH, it will not have the ability to send transactions.

Do remember to send some ETH to this address for gas usage. If it has test ETH, you can skip this step Task 4.2.

signer = await ethers.provider.getSigner()
send_address = await signer.getAddress()

to_address = wallet.address
nonce = await signer.getTransactionCount()
gas_price = await signer.getGasPrice()
gas_limit = ethers.utils.hexlify(21000)
value = ethers.utils.parseUnits('1.0')

tx = {
  from: send_address,
  to: to_address,
  value: value,
  nonce: nonce,
  gasLimit: gas_limit,
  gasPrice: gas_price,
}

await signer.sendTransaction(tx)
await ethers.provider.getBalance(wallet.address).then(result=>ethers.utils.formatEther(result))
//'1.0'
Enter fullscreen mode Exit fullscreen mode

Task 4.4 Connect wallet to provider

Connect wallet to provider

walletsigner = wallet.connect(ethers.provider)
walletsigner.address
//still be the one
Enter fullscreen mode Exit fullscreen mode

Task 4.5 Set walletsigner as signer of ClassToken

newtokeninstance = await token.connect(walletsigner)
newtokeninstance.signer.address
//'0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'
Enter fullscreen mode Exit fullscreen mode

Task 4.6 send ClassToken from this address

deployer='0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
await newtokeninstance.transfer(deployer,parseEther('66'))
Enter fullscreen mode Exit fullscreen mode

Do read the returned receipt of this contract method call.

Let's check whether we get the correct result:

await token.balanceOf(deployer).then((r)=>console.log(formatEther(r)))
//9066.0
await token.balanceOf(to_address).then((r)=>console.log(formatEther(r)))
//934.0
Enter fullscreen mode Exit fullscreen mode

After going through 12 tasks in this tutorial, you can begin to use Ethers.js in Web3 development. Ethers.js, as well as web3.js, is very important as it stands at the overlapped space between blockchain and web App.


If you find this note useful, please follow my twitter @fjun99.


Discussion (0)