I am planning to construct a easy web3 pet store software from scratch. Day-after-day I am going to attempt to improve
the appliance and write down some notes. By doing so, hopefully I might educate myself about
Ethereum, Solidity, Hardhat, and web3 growth.
The undertaking is hosted on GitHub: https://github.com/zhengzhong/petshop
The primary day goals
- Arrange a Hardhat undertaking
- Create and compile a easy token in Solidity
- Create and run some exams for this contract
- Deploy the contract to the native Hardhat community
- Deploy the contract to the Goerli testnet
Arrange a Hardhat undertaking
I used to be mainly following the Hardhat’s tutorial for newcomers.
I used to be utilizing node v16.9.1 and npm 8.7.0. Every little thing went on easily. So I am summarizing the steps
beneath with out detailed rationalization.
Firstly, create an npm undertaking, set up some dependencies, and create an empty hardhat.config.js
file:
$ npm init
...
$ npm set up --save-dev
hardhat
@nomicfoundation/hardhat-toolbox
@nomiclabs/hardhat-ethers
ethers
dotenv
...
$ npx hardhat
888 888 888 888 888
888 888 888 888 888
888 888 888 888 888
8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
888 888 "88b 888P" d88" 888 888 "88b "88b 888
888 888 .d888888 888 888 888 888 888 .d888888 888
888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
👷 Welcome to Hardhat v2.9.9 👷
? What do you need to do? …
Create a JavaScript undertaking
Create a TypeScript undertaking
❯ Create an empty hardhat.config.js
Give up
Create a easy token
Following the Hardhat conference, I am going to use the next directories:
-
contracts/
to carry all sensible contracts (in Solidity) -
take a look at/
to carry all unit exams -
scripts/
to carry automation scripts -
duties/
to carry further Hardhat duties
Create contracts/SimpleToken.sol
that implements a easy (non-ERC20) token. The contract code is
copied and barely modified from Hardhat’s tutorial.
// SPDX-License-Identifier: UNLICENSED
// Solidity information have to start out with this pragma.
// It is going to be utilized by the Solidity compiler to validate its model.
pragma solidity ^0.8.16;
import "hardhat/console.sol";
// That is the primary constructing block for sensible contracts.
contract SimpleToken {
// Some string kind variables to establish the token.
string public title = "My Easy Token";
string public image = "MST";
// The fastened quantity of tokens, saved in an unsigned integer kind variable.
uint256 public totalSupply = 1_000_000;
// An tackle kind variable is used to retailer ethereum accounts.
tackle public proprietor;
// A mapping is a key/worth map. Right here we retailer every account's stability.
mapping(tackle => uint256) balances;
// The Switch occasion is emitted when somebody transfers some token(s) to another person.
// The occasion helps off-chain functions perceive what occurs throughout the contract.
occasion Switch(tackle listed _from, tackle listed _to, uint256 _value);
/**
* Contract initialization.
*/
constructor() {
// The totalSupply is assigned to the transaction sender, which is the
// account that's deploying the contract.
balances[msg.sender] = totalSupply;
proprietor = msg.sender;
}
/**
* A perform to switch tokens.
*
* The `exterior` modifier makes a perform *solely* callable from *exterior*
* the contract.
*/
perform switch(tackle to, uint256 quantity) exterior {
// Test if the transaction sender has sufficient tokens.
require(balances[msg.sender] >= quantity, "Not sufficient tokens");
if (msg.sender != to) {
console.log("Transferring %s tokens: %s => %s", quantity, msg.sender, to);
balances[msg.sender] -= quantity;
balances[to] += quantity;
// Notify off-chain functions of the switch.
emit Switch(msg.sender, to, quantity);
}
}
/**
* Learn solely perform to retrieve the token stability of a given account.
*
* The `view` modifier signifies that it would not modify the contract's
* state, which permits us to name it with out executing a transaction.
*/
perform balanceOf(tackle account) exterior view returns (uint256) {
console.log("Querying stability of %s: %s", account, balances[account]);
return balances[account];
}
}
Compile the contract:
$ npx hardhat compile
...
Compiled 2 Solidity information efficiently
On a profitable compilation, Hardhat will generate the artifact, together with the contract ABI and
bytecode, within the artifacts/
listing.
Check the straightforward token
We use ethers.js to work together with the contract we constructed and deployed on
Ethereum. We use Mocha as our take a look at runner.
Earlier than writing the take a look at, replace hardhat.config.js
to import
hardhat-toolbox plugin.
This plugin bundles all of the generally used packages and Hardhat plugins, together with ethers.js that
we’d like in our take a look at.
require("@nomicfoundation/hardhat-toolbox");
Then, create take a look at/SimpleToken.js
(this file, once more, is copied and barely modified from Hardhat’s
tutorial):
// That is an instance take a look at file. Hardhat will run each *.js file in `take a look at/`,
// so be happy so as to add new ones.
// Hardhat exams are usually written with Mocha and Chai.
// Non-obligatory: `ethers` is injected in international scope mechanically.
// TODO: Is that this due to `require("@nomicfoundation/hardhat-toolbox")` in `hardhat.config.js`?
const { ethers } = require("hardhat");
// We import Chai to make use of its asserting features right here.
// See additionally: https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-chai-matchers
const { count on } = require("chai");
// We use `loadFixture` to share widespread setups (or fixtures) between exams.
// Utilizing this simplifies your exams and makes them run quicker, by taking
// benefit of Hardhat Community's snapshot performance.
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
// `describe` is a Mocha perform that means that you can set up your exams.
// Having your exams organized makes debugging them simpler. All Mocha
// features can be found within the international scope.
//
// `describe` receives the title of a piece of your take a look at suite, and a
// callback. The callback should outline the exams of that part. This callback
// cannot be an async perform.
describe("SimpleToken contract", perform () {
// A fixture is a setup perform that's run solely the primary time it is invoked.
// On subsequent invocations, as an alternative of re-running it, Hardhat will reset the state
// of the community to what it was on the level after the fixture was initially executed.
async perform deploySimpleTokenFixture() {
const SimpleToken = await ethers.getContractFactory("SimpleToken");
const [owner, addr1, addr2] = await ethers.getSigners();
// To deploy our contract, we simply need to name Token.deploy() and await
// its deployed() technique, which occurs onces its transaction has been mined.
const simpleToken = await SimpleToken.deploy();
await simpleToken.deployed();
// Fixtures can return something you take into account helpful on your exams.
return { SimpleToken, simpleToken, proprietor, addr1, addr2 };
}
// You'll be able to nest describe calls to create subsections.
describe("Deployment", perform() {
// `it` is one other Mocha perform. That is the one you utilize to outline every
// of your exams. It receives the take a look at title, and a callback perform.
// If the callback perform is async, Mocha will `await` it.
it("ought to set the suitable proprietor", async perform() {
const { simpleToken, proprietor } = await loadFixture(deploySimpleTokenFixture);
count on(await simpleToken.proprietor()).to.equal(proprietor.tackle);
});
it("ought to assign the entire provide of tokens to the proprietor", async perform () {
const { simpleToken, proprietor } = await loadFixture(deploySimpleTokenFixture);
const ownerBalance = await simpleToken.balanceOf(proprietor.tackle);
count on(await simpleToken.totalSupply()).to.equal(ownerBalance);
});
it("ought to initialize the contract accurately", async perform () {
const { simpleToken } = await loadFixture(deploySimpleTokenFixture);
count on(await simpleToken.title()).to.equal("My Easy Token");
count on(await simpleToken.image()).to.equal("MST");
count on(await simpleToken.totalSupply()).to.equal(1000000);
});
});
describe("Transactions", perform() {
it("ought to switch tokens between accounts", async perform() {
const { simpleToken, proprietor, addr1, addr2 } = await loadFixture(deploySimpleTokenFixture);
count on(await simpleToken.balanceOf(addr1.tackle)).to.equal(0);
count on(await simpleToken.balanceOf(addr2.tackle)).to.equal(0);
// Switch 50 tokens from proprietor to addr1.
await count on(simpleToken.switch(addr1.tackle, 50))
.to.changeTokenBalances(simpleToken, [owner, addr1], [-50, 50]);
// Switch 50 tokens from addr1 to addr2.
await count on(simpleToken.join(addr1).switch(addr2.tackle, 50))
.to.changeTokenBalances(simpleToken, [addr1, addr2], [-50, +50]);
});
it("ought to emit Switch occasions", async perform () {
const { simpleToken, proprietor, addr1, addr2 } = await loadFixture(deploySimpleTokenFixture);
// Switch 50 tokens from proprietor to addr1.
await count on(simpleToken.switch(addr1.tackle, 50))
.to.emit(simpleToken, "Switch").withArgs(proprietor.tackle, addr1.tackle, 50);
// Switch 50 tokens from addr1 to addr2.
await count on(simpleToken.join(addr1).switch(addr2.tackle, 50))
.to.emit(simpleToken, "Switch").withArgs(addr1.tackle, addr2.tackle, 50);
});
it("ought to fail if sender would not have sufficient tokens", async perform () {
const { simpleToken, proprietor, addr1 } = await loadFixture(deploySimpleTokenFixture);
const initialOwnerBalance = await simpleToken.balanceOf(proprietor.tackle);
// Attempt to ship 1 token from addr1 (0 tokens) to proprietor (1000 tokens).
// `require` will consider false and revert the transaction.
await count on(simpleToken.join(addr1).switch(proprietor.tackle, 1))
.to.be.revertedWith("Not sufficient tokens");
// Proprietor stability should not have modified.
count on(await simpleToken.balanceOf(proprietor.tackle)).to.equal(initialOwnerBalance);
});
it("ought to do nothing if switch to self", async perform () {
const { simpleToken, proprietor } = await loadFixture(deploySimpleTokenFixture);
await count on(simpleToken.switch(proprietor.tackle, 1))
.to.changeTokenBalances(simpleToken, [owner], [0]);
await count on(simpleToken.switch(proprietor.tackle, 1))
.to.not.emit(simpleToken, "Switch");
});
});
});
Run the take a look at:
$ npx hardhat take a look at
SimpleToken contract
Deployment
✔ ought to set the suitable proprietor (1213ms)
✔ ought to assign the entire provide of tokens to the proprietor (44ms)
✔ ought to initialize the contract accurately (42ms)
Transactions
✔ ought to switch tokens between accounts (196ms)
✔ ought to emit Switch occasions (45ms)
✔ ought to fail if sender would not have sufficient tokens (60ms)
✔ ought to do nothing if switch to self (58ms)
7 passing (2s)
Deploy to the native Hardhat community
We at the moment are going to deploy the straightforward token contract to the native Hardhat community.
Create a deploy script in scripts/deploySimpleToken.js
:
async perform important() {
// In keeping with `hardhat.config.js` and the community we use,
// this can give us an array of accounts.
const [deployer] = await ethers.getSigners();
console.log(`Deployer: ${deployer.tackle}`);
console.log(`Deployer has a stability of: ${await deployer.getBalance()}`);
const SimpleToken = await ethers.getContractFactory("SimpleToken");
const simpleToken = await SimpleToken.deploy();
await simpleToken.deployed();
console.log(`Deployed SimpleToken at: ${simpleToken.tackle}`);
console.log(`Deployer now has a stability of: ${await deployer.getBalance()}`);
console.log(`Present block quantity: ${await ethers.supplier.getBlockNumber()}`);
}
important()
.then(() => course of.exit(0))
.catch((error) => {
console.error(error);
course of.exit(1);
});
Firstly, attempt to deploy the contract to the native Hardhat community. To take action, we invoke the script
with out offering a --network
argument:
$ npx hardhat run scripts/deploySimpleToken.js
...
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployer has a stability of: 10000000000000000000000
Deployed SimpleToken at: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Deployer now has a stability of: 9999998536390000000000
Present block quantity: 1
Now our first contract is deployed and we get the contract tackle. Attempt to write one other script to
work together with the contract.
Be aware: It is troublesome to cross command-line arguments to scripts that run by
hardhat run
command, so we hard-code the contract tackle contained in the script. Be certain theCONTRACT_ADDRESS
fixed is outlined correctly.
Let’s attempt to run the script on the Hardhat native community:
$ npx hardhat run scripts/testSimpleToken.js
Present block quantity: 0
Error: name revert exception [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ] (technique="title()", knowledge="0x", errorArgs=null, errorName=null, errorSignature=null, motive=null, code=CALL_EXCEPTION, model=abi/5.7.0)
at ... {
motive: null,
code: 'CALL_EXCEPTION',
technique: 'title()',
knowledge: '0x',
errorArgs: null,
errorName: null,
errorSignature: null,
tackle: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
args: [],
transaction: {
knowledge: '0x06fdde03',
to: '0x5FbDB2315678afecb367f032d93F642f64180aa3'
}
}
Oops! It failed at calling the contract technique title()
. Discover that the present block quantity is 0!
This means that, whereas our script is operating, the community is empty and the contract we need to
work together is NOT deployed.
It’s because the native Hardhat community is operating as an in-process node, which is began when
we invoke the hardhat run
command, and is stopped when the command ends. Every little thing on it,
together with our contract, couldn’t survive via two hardhat run
instructions.
To repair this, we are going to begin the Hardhat community as a standalone daemon. Open a brand new terminal and run:
$ npx hardhat node
Began HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
Accounts
========
WARNING: These accounts, and their non-public keys, are publicly identified.
Any funds despatched to them on Mainnet or some other stay community WILL BE LOST.
Account #0: 0xf39F**** (10000 ETH)
Personal Key: 0xac09****
Account #1: 0x7099**** (10000 ETH)
Personal Key: 0x59c6****
...
Hold it operating. Then we change to a different terminal and deploy our contract once more on this community
(named localhost
):
$ npx hardhat run scripts/deploySimpleToken.js --network localhost
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployer has a stability of: 10000000000000000000000
Deployed SimpleToken at: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Deployer now has a stability of: 9999999316982000000000
Present block quantity: 1
Be certain the CONTRACT_ADDRESS
fixed in our take a look at file matches the contract tackle. Then run
the take a look at once more:
$ npx hardhat run scripts/testSimpleToken.js --network localhost
Present block quantity: 1
Constructed token contract: My Easy Token (MST), proprietor is 0xf39F****
Identify | Tackle | ETH | Token (MST)
------- | ---------- | ----------------------- | ----------
Jason | 0xf39F**** | 9999.999316982 | 1000000
Orphee | 0x7099**** | 10000.0 | 0
------- | ---------- | ----------------------- | ----------
Transferring tokens from Jason to Orphée...
Transferring 16 MST from 0xf39F****... to 0x7099****...
* Creating tx - Used 0.09 seconds
Ready for tx 0xcbb4**** to be mined...
* Ready tx to be mined - Used 0.01 seconds
Tx 0xcbb4**** mined efficiently.
From / To : 0xf39F**** => 0x5FbD****
EIP-2718 Kind : 2
Standing : 1
Block Quantity : 2
Block Hash : 0x7ab8****
Gasoline Used : 56004 (771316817 wei / gasoline)
Discovered Switch occasion: 0xf39F**** => 0x7099****: 16
Identify | Tackle | ETH | Token (MST)
------- | ---------- | ----------------------- | ----------
Jason | 0xf39F**** | 9999.999273785172980732 | 999984
Orphee | 0x7099**** | 10000.0 | 16
------- | ---------- | ----------------------- | ----------
Transferring tokens from Orphée to Jason...
Transferring 16 MST from 0x7099**** to 0xf39F****...
* Creating tx - Used 0.22 seconds
Ready for tx 0x1c87**** to be mined...
* Ready tx to be mined - Used 0.01 seconds
Tx 0x1c87**** mined efficiently.
From / To : 0x7099**** => 0x5FbD****
EIP-2718 Kind : 2
Standing : 1
Block Quantity : 3
Block Hash : 0x7cc0****
Gasoline Used : 34104 (675262189 wei / gasoline)
Discovered Switch occasion: 0x7099**** => 0xf39F****: 16
Identify | Tackle | ETH | Token (MST)
------- | ---------- | ----------------------- | ----------
Jason | 0xf39F**** | 9999.999273785172980732 | 1000000
Orphee | 0x7099**** | 9999.999976970858306344 | 0
------- | ---------- | ----------------------- | ----------
Present block quantity: 3
Now every part appears to be like good.
Configure Goerli testnet in Hardhat
We need to deploy this contract to the Goerli testnet. As of this writing, Goerli is a proof-of-authority
testnet really useful for software builders.
Earlier than we are able to try this, we have to do some preparations:
- Export non-public keys of some take a look at accounts from MetaMask
- Create an API key for Alchemy
- Create an API key for Etherscan
- Configure Goerli testnet in
hardhat.config.js
- Request some ether from the Goerli faucet
I put in MetaMask as a Chrome plugin. Click on the MetaMask plugin icon. Change the community to
“Goerli Check Community”. Be certain we’ve got a minimum of 2 accounts in MetaMask. If not, create (or import)
some accounts.
TODO: MetaMask picture
Subsequent, export the non-public keys of the two accounts from MetaMask.
Be aware: Do NOT disclose your non-public keys to anybody!
Go to Alchemy, join, create a brand new app in its dashboard, and seize the
API key. Alchemy offers blockchain APIs and Ethereum node infrastructure. We are going to connect with the
Goerli testnet through a node (and a JSON-RPC server operating on the node) offered by Alchemy.
Go to Etherscan, join, and create an API key token. Etherscan is a
blockchain explorer permitting us to view contracts, transactions, and every part on the Ethereum
networks. It additionally present a service of supply code verification for sensible contracts. We are going to use
Etherscan to confirm our contract.
Now we’ve got 2 non-public keys for our accounts (I named these 2 accounts Jason and Orphée),
an Alchemy API key, and an Etherscan API key. We are going to put these secrets and techniques right into a .env
file,
preserve this file out of supply management, and use dotenv to
load them into course of.env
. Our .env
file appears to be like like this:
# My MetaMask take a look at accounts:
#
# | Alias | Tackle |
# +--------+--------------------------------------------+
# | Jason | 0xCc4c8184CC4A5A03babC13D832cEE3E41bE92d08 |
# | Orphée | 0xBf1381Fc04fe3e500e0fFD8adb985b9b8Fe95437 |
#
GOERLI_PRIVATE_KEY_JASON = "****"
GOERLI_PRIVATE_KEY_ORPHEE = "****"
# Grabbed from: https://dashboard.alchemyapi.io/
ALCHEMY_API_KEY = "****"
# Grabbed from: https://etherscan.io/myapikey
ETHERSCAN_API_KEY = "****"
Replace hardhat.config.js
like the next:
require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-etherscan");
// Load secrets and techniques from `.env` file into `course of.env`.
require('dotenv').config();
const {
GOERLI_PRIVATE_KEY_JASON,
GOERLI_PRIVATE_KEY_ORPHEE,
ALCHEMY_API_KEY,
ETHERSCAN_API_KEY,
} = course of.env;
/** @kind import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.16",
networks: {
goerli: {
url: `https://eth-goerli.alchemyapi.io/v2/${ALCHEMY_API_KEY}`,
accounts: [GOERLI_PRIVATE_KEY_JASON, GOERLI_PRIVATE_KEY_ORPHEE],
},
},
etherscan: {
apiKey: ETHERSCAN_API_KEY,
},
};
Lastly, request some take a look at ether from the Goerli faucet. To ship
transactions to the Goerli testnet, we are going to want some ether to pay for the gasoline price.
Deploy to Goerli testnet
Now we are able to re-run the deployment script on the Goerli testnet:
$ npx hardhat run scripts/deploySimpleToken.js --network goerli
Deployer: 0xCc4c8184CC4A5A03babC13D832cEE3E41bE92d08
Deployer has a stability of: 736171761944897241
Deployed SimpleToken at: 0xf7Df331661C4CFbbBb85Fca9c7b7C3657C50D783
Deployer now has a stability of: 736045099082540281
Present block quantity: 7464064
Good! Now our contract is stay on Goerli testnet, and we get its tackle. We are able to view our contract
through Etherscan.
Let’s attempt to run the opposite script to switch some tokens. Replace CONTRACT_ADDRESS
inscripts/testSimpleToken.js
and run:
$ npx hardhat run scripts/testSimpleToken.js --network goerli
Present block quantity: 7464081
Constructed token contract: My Easy Token (MST), proprietor is 0xCc4c****
Identify | Tackle | ETH | Token (MST)
---------- | ---------- | --------------------- | ----------
Jason | 0xCc4c**** | 0.736045099082540281 | 1000000
Orphee | 0xBf13**** | 0.0 | 0
---------- | ---------- | --------------------- | ----------
Transferring tokens from Jason to Orphée...
Transferring 16 MST from 0xCc4c**** to 0xBf13****...
* Creating tx - Used 1.09 seconds
Ready for tx 0x4273**** to be mined...
* Ready tx to be mined - Used 16.63 seconds
Tx 0x4273**** mined efficiently.
From / To : 0xCc4c**** => 0xf7Df****
EIP-2718 Kind : 2
Standing : 1
Block Quantity : 7464083
Block Hash : 0x7d4c****
Gasoline Used : 56004 (1003264582 wei / gasoline)
Discovered Switch occasion: 0xCc4c**** => 0xBf13****: 16
Identify | Tackle | ETH | Token (MST)
---------- | ---------- | --------------------- | ----------
Jason | 0xCc4c**** | 0.735988912252889953 | 999984
Orphee | 0xBf13**** | 0.0 | 16
---------- | ---------- | --------------------- | ----------
Transferring tokens from Orphée to Jason...
Transferring 16 MST from 0xBf13**** to 0xCc4c****...
Error: inadequate funds for intrinsic transaction price ...
Every little thing labored high quality apart from the final switch: It failed with an INSUFFICIENT_FUNDS
error.
Truly this is sensible as a result of my second take a look at account (Orphée) has no take a look at ether to pay for
the gasoline price.
Confirm the contract
We’ll use the hardhat-etherscan plugin
to confirm our contract. By verifying the contract, we add its supply code to match the contract
bytecode deployed at a given tackle. As soon as verified, a wise contract’s supply code turns into publicly
obtainable and verifiable. This creates transparency and belief. It additionally permits Etherscan to reveal
exterior strategies of our contract through its net UI.
$ npx hardhat confirm 0xf7Df331661C4CFbbBb85Fca9c7b7C3657C50D783 --network goerli
Nothing to compile
Efficiently submitted supply code for contract
contracts/SimpleToken.sol:SimpleToken at 0xf7Df331661C4CFbbBb85Fca9c7b7C3657C50D783
for verification on the block explorer. Ready for verification end result...
Efficiently verified contract SimpleToken on Etherscan.
https://goerli.etherscan.io/tackle/0xf7Df331661C4CFbbBb85Fca9c7b7C3657C50D783#code
Now return to Etherscan and think about our contract. This time, we are able to see all of the exterior strategies of
our contract and invoke them through the “Learn Contract” and “Write Contract” tabs:
Conclusion
That is sufficient for the primary day. Supply code from the primary day may be discovered at:
https://github.com/zhengzhong/petshop/releases/tag/day01