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
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);
}
}
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
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);
});
});
Run test:
yarn hardhat test
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;
});
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
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
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
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
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
Get Smart contract:
const address = '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512';
const token = await ethers.getContractAt("ClassToken", address);
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'
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
Task 3.4 Token balance of deployer
deployer='0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
await token.balanceOf(deployer).then((r)=>console.log(formatEther(r)))
//10000.0
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
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'
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'
Task 4.4 Connect wallet to provider
Connect wallet to provider
walletsigner = wallet.connect(ethers.provider)
walletsigner.address
//still be the one
Task 4.5 Set walletsigner as signer of ClassToken
newtokeninstance = await token.connect(walletsigner)
newtokeninstance.signer.address
//'0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'
Task 4.6 send ClassToken from this address
deployer='0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
await newtokeninstance.transfer(deployer,parseEther('66'))
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
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.
Top comments (1)
Do you have a youtube channel or a video of this tutorial?!?!