Day 20. Setting up the development environment (project-specific)

In Day 19, we completed the common setup for development using Hardhat. Following that, this guide will explain the setup for each individual project.

TOC

Setup Procedure

The setup is performed by entering commands into the terminal or console, similar to last time. The following are the initial setup steps for starting a new project. The meaning of each command is as mentioned in the comments following the ‘#’. In the initial setup of Hardhat, you can choose between JavaScript or TypeScript, but here we will select TypeScript.

Zsh
% mkdir funding           # Create a folder
% cd funding              # Move into the created folder
% yarn init               # Initialize package management (create a configuration file called package.json)
  yarn init v1.22.19      # Fill in the details for each of the following questions. You can simply press Enter to skip
  question name (funding): funding
  question version (1.0.0): 0.0.1
  question description: crowdfunding web3 application
  question entry point (index.js): 
  question repository url: 
  question author: 
  question license (MIT): 
  question private: 
% yarn add --dev @nomicfoundation/hardhat-ethers @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers ethers @nomiclabs/hardhat-etherscan @nomiclabs/hardhat-waffle chai ethereum-waffle hardhat hardhat-contract-sizer hardhat-deploy hardhat-gas-reporter prettier prettier-plugin-solidity solhint solidity-coverage dotenv @typechain/ethers-v6 @typechain/hardhat @types/chai @types/node ts-node typechain typescript # Add the necessary libraries (For Typescript. For Javascript, delete everything after dotenv)
% yarn hardhat            # Perform the initial setup of hardhat. Answer the following questions (the last two can be skipped by just pressing Enter)
   What do you want to do? · Create a TypeScript project
   Hardhat project root: · /Users/username/code/funding
   Do you want to add a .gitignore? (Y/n) · y
% code .                  # Launch VS Code

The ‘yarn add’ command is used to specify and install the required libraries. Later, you can check the added libraries in package.json. The following is a sample of package.json (as a result of the above commands). The scripts section is manually written (serving as shortcuts for commands).

JSON
{
  "name": "funding",
  "version": "0.0.1",
  "description": "crowdfunding web3 application",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "@nomicfoundation/hardhat-ethers": "^3.0.4",
    "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers",
    "@nomiclabs/hardhat-etherscan": "^3.1.7",
    "@nomiclabs/hardhat-waffle": "^2.0.6",
    "@typechain/ethers-v6": "^0.4.3",
    "@typechain/hardhat": "^8.0.2",
    "@types/chai": "^4.3.5",
    "@types/node": "^20.4.2",
    "chai": "^4.3.7",
    "dotenv": "^16.3.1",
    "ethereum-waffle": "^3.0.0", //*
    "ethers": "^6.6.4",
    "hardhat": "^2.17.0",
    "hardhat-contract-sizer": "^2.10.0",
    "hardhat-deploy": "^0.11.34",
    "hardhat-gas-reporter": "^1.0.9",
    "prettier": "^3.0.0",
    "prettier-plugin-solidity": "^1.1.3",
    "solhint": "^3.4.1",
    "solidity-coverage": "^0.8.4",
    "ts-node": "^10.9.1",
    "typechain": "^8.3.0",
    "typescript": "^5.1.6"
  }, 
  "scripts": {
    "test": "yarn hardhat test",
    "test:staging": "yarn hardhat test --network sepolia",
    "lint": "yarn solhint 'contracts/*.sol'",
    "lint:fix": "yarn solhint 'contracts/*.sol' --fix",
    "format": "yarn prettier --write .",
    "coverage": "yarn hardhat coverage"
  }
}

There are two ways to add libraries (packages):
1. Directly install them using the ‘yarn add (yarn add –dev) ‘ command.
2. Write the target package name in package.json and execute ‘yarn install’.
When writing in package.json, add to either the dependencies or devDependencies section. The latter is for libraries used only during development.

If the versions differ from the ones mentioned above, errors may occur in this guide. For instance, during my trial, a version above 4 of ethereum-waffle was installed, but this caused errors in the testing chapter. Therefore, we specifically set the package to version 3.0.0.
1. Edit the ethereum-waffle version in package.json
2. Delete the node_modules folder
3. Execute ‘yarn install’

Major Libraries and Their Roles

prettier

Prettier is one of the code formatters. A code formatter is software that automatically formats program code, making the format uniformly consistent and improving readability and maintainability.

You can define the settings for formatting in a file named .prettierrc. Below is an example of .prettierrc, and we will apply these settings for this time.

JSON
// .prettierrc
{
    "tabWidth": 4, // Number of characters for tabs (equivalent to 4 spaces)
    "useTabs": false, // Whether to use tabs for indentation (do not use; spaces will be used instead)
    "semi": false, // Whether to add a semicolon at the end of lines (do not add)
    "singleQuote": false // Whether to use single quotes for strings (do not use; double quotes will be used)
}

Also, the .prettierignore file allows you to define files that should not be formatted. The following is an example, and we will apply these settings similarly. For example, node_modules are libraries installed by yarn (or npm) and are not intended for direct modification, so they are excluded. The same applies to other directories and files.

Zsh
# .prettierignore
node_modules
package.json
img
artifacts
cache
coverage
.env
.*
README.md
coverage.json

dotenv

dotenv is a module that enables developers to load environment variables from a .env file. This .env file is typically placed in the root directory of the project.

Environment variables are used to store configuration values that depend on the environment in which the application is run, such as private keys or API keys. These values are usually managed separately from the source code. This is because configuration values may vary depending on the environment (development, test, production, etc.) and including sensitive information (like private keys or API keys) in the source code poses a security risk.

Zsh
# .env
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR-API-KEY
PRIVATE_KEY=a301a301a301a301a301a301a301a301a301a301a301a301a301a301a301a301
ETHERSCAN_API_KEY=THISISEXAMPLEKEY000000999999992222
COINMARKETCAP_API_KEY=fabc0000-0000-0abc-0000-999999999999
UPDATE_FRONT_END=true

If a .env file as described above exists, variables defined in the .env file can be used in the form shown in the following example of hardhat.config.ts (process.env.ETHERSCAN_API_KEY).

TypeScript
// hardhat.config.ts
import "dotenv/config" // Import dotenv at the beginning so that
...
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY // Values can be read using process.env.variableName
...
etherscan: {
  apiKey: ETHERSCAN_API_KEY, // Use the variable with the value directly
},
...

chai

chai is one of the assertion libraries. An assertion library is a tool for verifying that specific parts of a program are functioning as expected.

In the following example, the ninth line compares two arguments (the former is the result obtained by executing a contract function, and the latter is the expected value). If the comparison results are the same, it is true, indicating that the contract is functioning as expected. It is used to verify whether the contract produces the expected results (used in tools for unit tests, integration tests, etc.).

TypeScript
// sample ts file
const { assert, expect } = from "chai" // Enables the use of assert and expect functions
...
describe("fund", function() {
...
  it("Updates the amount funded data structure", async () => {
    await funding.fund({ value: ethers.utils.parseEther("1") })
    const response = await funding.s_addressToAmountFunded(deployer.address)
    assert.equal(response.toString(), ethers.utils.parseEther("1").toString()) // Compares the first and second arguments to check if the values are the same
  })
}

solhint

Solhint is a static code analysis tool (linter) for the Solidity language. A linter is a tool that analyzes program code to detect errors, bugs, and potentially suspicious forms in the program. Solhint has the following features:

  • Enforcing style guides: Solhint enforces consistent code styles, improving code readability and maintainability.
  • Security checks: Solhint provides rules to prevent common security issues.
  • Performance checks: Detects code that could potentially incur unnecessary costs.
  • Enforcing best practices: Enforces best practices in blockchain and smart contract development.

Solhint settings are made in .solhint.json, and excluded files are specified in .solhintignore.

Zsh
% yarn solhint --init # Initialize Solhint (creates .solhint.json)
% touch .solhintignore # Manually create .solhintignore (creates an empty file)
JSON
{
  "extends": "solhint:recommended", // Apply Solhint's recommended rules
  "rules": {
    "compiler-version": ["error", "^0.8.0"], // Specify compiler version as 0.8.0 or higher, error if not met
    "func-visibility": ["warn", { "ignoreConstructors": true }] // Warn for omitted visibility (public, private, etc.) in function definitions. Exclude constructors from this rule.
  }
}

*) List of Solhint rules

Zsh
# .solhintignore
node_modules
contracts/test

The following is an example of running Solhint, where there are three warnings but no particular errors (the content is about variable names not being in mixedCase, for instance, the variable name on line 14, s_funders, could be changed to sFunders to remove the warning. However, this naming is intentionally for clarity, so it is ignored here).

Zsh
# console
% yarn solhint contracts/Funding.sol # Run the linter on Funding.sol
yarn run v1.22.19
$ /Users/someone/code/token-village/funding/node_modules/.bin/solhint contracts/Funding.sol

contracts/Funding.sol
  14:5  warning  Variable name must be in mixedCase    var-name-mixedcase
  15:5  warning  Variable name must be in mixedCase    var-name-mixedcase
  16:5  warning  Variable name must be in mixedCase    var-name-mixedcase

 3 problems (0 errors, 3 warnings)

  Done in 0.95s.

solidity-coverage

solidity-coverage is a code coverage analysis tool for smart contracts. Code coverage is a metric that indicates what portion of the code was executed by the test suite, generally expressed as a percentage of how much code is covered by tests. This helps assess the quality of software testing.

The following line in hardhat.config.ts (hardhat’s configuration file) enables its execution:

TypeScript
// hardhat.config.ts
import "solidity-coverage";

The following is an execution example. Here, as no tests have been defined yet, the coverage (the percentage indicating how much testing has been done) is zero.

Zsh
# console
% yarn hardhat coverage
yarn run v1.22.19
$ /Users/someone/code/funding/node_modules/.bin/hardhat coverage

Version
=======
> solidity-coverage: v0.8.4

...

--------------|----------|----------|----------|----------|----------------|
File          |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
--------------|----------|----------|----------|----------|----------------|
 contracts/   |        0 |        0 |        0 |        0 |                |
  Funding.sol |        0 |        0 |        0 |        0 |... 49,53,57,63 |
  Lock.sol    |        0 |        0 |        0 |        0 |... 27,28,30,32 |
--------------|----------|----------|----------|----------|----------------|
All files     |        0 |        0 |        0 |        0 |                |
--------------|----------|----------|----------|----------|----------------|

Defining scripts (in package.json)

The scripts section in package.json allows you to define shortcuts for frequently used commands. For example, if there is a definition like the following example, the previously mentioned yarn hardhat coverage can be executed with just yarn coverage.

JSON
// package.json
 ...
  "scripts": {
    "test": "yarn hardhat test",
    "test:staging": "yarn hardhat test --network sepolia",
    "lint": "yarn solhint 'contracts/*.sol'",
    "lint:fix": "yarn solhint 'contracts/*.sol' --fix",
    "format": "yarn prettier --write .",
    "coverage": "yarn hardhat coverage"
  }
}

Directory Structure

In hardhat, a specific directory structure is not enforced, but the structure adopted in this guide is as follows. Directories and files marked with * are used by the system.

Zsh
.
├── artifacts/* # Location where compiled contracts and their metadata are stored
├── cache/* # Location where Hardhat saves build cache
├── contracts/ # Directory where smart contract source (.sol) files are stored
├── deploy/ # Directory for storing scripts for deployment
├── node_modules/* # Location where npm (yarn) packages are installed
├── scripts/ # Directory for storing scripts (tasks, etc.)
├── test/ # Directory where test files (.js or .ts) are stored
├── utils/ # Directory for storing utility scripts commonly used during deployment
├── .env # File for defining environment variables
├── .gitignore # File defining files to be excluded from git (source control tool) management
├── .prettierrc # Configuration file for prettier
├── .prettierignore # File defining files to be excluded from prettier formatting
├── .solhint.json # Configuration file for solhint
├── .solhintignore # File defining files to be excluded from solhint analysis
├── hardhat.config.ts # Configuration file for Hardhat
├── package.json # Project's npm (yarn) configuration file
└── yarn.lock* # File automatically generated when using yarn (not directly edited)
Let's share this post !

Author of this article

After joining IBM in 2004, the author gained extensive experience in developing and maintaining distributed systems, primarily web-based, as an engineer and PM. Later, he founded his own company, designing and developing mobile applications and backend services. He is currently leading a Tech team at a venture company specializing in robo-advisory.

Comments

To comment

CAPTCHA


TOC