web3開發DApp項目入門教程(2022年最新)


作者:米都督
微信:Meng_Xiang987(加入web3交流群,請備注"web3")
郵箱:miduduur@gmail.com
 
web3.0時代正悄悄來臨。如果你想開發一個web3項目,或者想要未來從事web3工作,充實自己的web3簡歷,本文就是一篇很好的DApp開發技術入門教程。
 
這篇文章的主要內容包括:
  • 智能合約創建和部署
  • Solidity語言入門
  • hardhat框架使用及本地環境搭建
 
本文較長,包括 20 余張圖片、1 個 Hello-World 版本的 web3項目示例,以及完整的項目代碼及注釋。如果現在你正在使用手機閱讀,建議先收藏本文;之后打開電腦,沿着本文的思路,仔細閱讀。
 

一、Hardhat 是什么?

Hardhat框架,是一個以太坊DApp開發的本地集成開發套件。經常與之對比的是 Hardhat vs Truffle。相比其它作為web3基礎設施的開發工具,Hardhat 更加輕量,采用插件化的思想,非常適合作為新手dapp開發入門的工具。
 

二、環境准備 —— 安裝 Node.js

在使用 Hardhat 進行開發以前,首先需要在本地安裝 Node 環境。
這里是 Download | Node.js 官網下載地址,根據自己操作系統的配置,選擇合適的版本下載。
這里要注意,如果電腦上已經安裝過,記得升級到 node v14 及以上的版本,更早的版本不支持 Hardhat。
 
看一下是否安裝成功,分別輸入:
node -v
npm -v
npx -v
 
我這里是 Windows 系統,用 PowerShell 執行一下上面幾條命令,可以看到已經安裝成功了。
 

三、開始第一個web3項目

  1. 初始化

首先創建一個目錄 hardhat-demo,進到這個目錄里,執行 npx hardhat init 命令:
mkdir hardhat-demo
cd hardhat-demo
npx hardhat init
 
這時會問你幾個問題, 一路回車即可
 
npx hardhat init 的作用是,按照模板創建了一個示例項目。當出現 “Project created” 字樣時,代表項目創建成功。
 
可以看到,在上面的截圖里,提示了我們要安裝依賴。為了能讓這個示例項目跑起來,還需要安裝幾個依賴模塊:
npm install --save-dev "hardhat@^2.9.1" "@nomiclabs/hardhat-waffle@^2.0.0" "ethereum-waffle@^3.0.0" "chai@^4.2.0" "@nomiclabs/hardhat-ethers@^2.0.0" "ethers@^5.0.0"
 
安裝完以后,這個目錄下的文件應該有這些:
這樣,你就成功完成了第一個步驟。
 
  1. 項目中的文件作用

創建好這個示例項目以后,這里來分析一下這些文件的作用。這一部分略微有一些枯燥,你也可以先閱讀“3. 智能合約的編譯、部署和測試”這一部分,之后再來看這些文件的作用。
 

① node_modules/、package-lock.json 和 package.json

這些是 node 項目的必須組成部分,包括了項目的配置信息、安裝的依賴模塊等,這里可以先無視。
 

② contracts/Greeter.sol

這個文件是項目中的重點,叫 智能合約文件。什么是智能合約呢?你可以把智能合約,同樣理解為一種可以運行的程序。只不過這個程序比較特殊,它是運行在以太坊的 EVM 虛擬機(https://ethereum.org/zh/developers/docs/evm/)環境上。並且,程序本身、程序的輸入輸出、運行結果,對所有人可見。
 
Solidity語言,是智能合約開發的主流語言之一。Solidity語言的入門教程,不是本文涉及的重點,這里只是簡單提一下。
 
簡單用 Java 類比一下:原來你寫了一段 Java 代碼,放進一個源文件叫作 HelloWorld.java,用 javac 執行編譯,最終在自己的電腦或者服務器上,被 JVM 虛擬機執行;現在寫了一段 Greeter.sol 代碼,它也可以被編譯,被執行,只不過運行環境變成了以太坊的 EVM 虛擬機。
 
傳統Java開發
web3開發
語言
Java
Solidity
編譯
javac
solc
運行
JVM
EVM
初始化項目以后,自動生成的代碼是這樣的:
//SPDX-License-Identifier: Unlicense // 聲明 license
pragma solidity ^0.8.0; // 定義版本號
 
import "hardhat/console.sol"; // 導入其它智能合約文件
 
contract Greeter { // 定義一個合約,合約名字叫 Greeter
string private greeting; // 這個合約的一個私有變量
 
constructor(string memory _greeting) { // 合約的構造函數,當且僅當合約被部署時,會被執行一次
console.log("Deploying a Greeter with greeting:", _greeting);
greeting = _greeting;
}
 
function greet() public view returns (string memory) { // 可以被外部調用的合約方法,view 聲明了這是個只讀方法,不會改變合約的狀態
return greeting;
}
 
function setGreeting(string memory _greeting) public { // 這是一個寫方法,會改變合約的狀態,且外部調用時會消耗 gas
console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
greeting = _greeting;
}
}
 
第 1 行聲明了 License,比如 GPL-3.0;
第 2 行聲明了編譯器版本,這里面強制指定了版本不得低於 0.8.0,也不能高於 0.9.0;
第 4 行導入了其它合約文件,這里面的 hardhat/console.sol 文件,是 Hardhat 框架自帶的,是一個用於方便調試的合約文件,源碼在這里(https://github.com/NomicFoundation/hardhat/blob/master/packages/hardhat-core/console.sol);
第 6 行開始,就是合約文件的主體部分。從結構上看,它很像是在 C++ 或者 Java 中定義一個類,也包括了字段變量、構造方法、讀方法、寫方法等。這里面出現了幾個特殊的關鍵字:
  • contract :聲明一個合約;
  • memory:和 storage 關鍵字相反,代表了變量只會臨時放在內存中,不會存儲在合約的狀態中;
  • view:聲明該方法為只讀方法,不會改變合約本身的狀態。
 

③ scripts/sample_scripts.js

這個 JavaScript 文件的作用,是將剛才的 Greeter.sol 智能合約編譯並部署到鏈上。
 
const hre = require("hardhat"); // 聲明依賴庫
 
async function main() { // 定義 main 函數
const Greeter = await hre.ethers.getContractFactory("Greeter"); // 獲取合約 Greeter
const greeter = await Greeter.deploy("Hello, Hardhat!"); // 部署合約,並得到一個合約的實例
 
await greeter.deployed(); // 等待合約部署完成
 
console.log("Greeter deployed to:", greeter.address); // 打印日志,記錄合約的地址
}
 
// 執行 main 函數
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
 
注釋里寫上了各行代碼的作用。這里要注意幾點:
  • 合約的部署需要一定的時間,因為使用 await greeter.deployed() 異步方法,等待合約部署完成。
  • 用 Java 代碼類比一下:Greeter 這個合約像是一個類,而每次部署得到的 greeter 像是 new 了一個類的對象。因此每次部署,都會生成出不同的對象,得到的合約地址也就不同。

④ test/sample_test.js

單元測試對於 web3開發來說同樣重要。 sample_test.js 這個文件就是一個單元測試的文件,使用了 chai 這個測試框架:
 
// 引入依賴
const { expect } = require("chai");
const { ethers } = require("hardhat");
 
// describe 和 it 指示了在進行 case test
describe("Greeter", function () {
it("Should return the new greeting once it's changed", async function () {
// 這里和 scripts/sample_scripts.js 中的代碼一樣,也是獲取合約 + 部署合約 + 等待部署完成
const Greeter = await ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, world!");
await greeter.deployed();
 
expect(await greeter.greet()).to.equal("Hello, world!"); // 測試點 1
 
// 執行合約的寫方法
const setGreetingTx = await greeter.setGreeting("Hola, mundo!");
await setGreetingTx.wait();
 
expect(await greeter.greet()).to.equal("Hola, mundo!"); // 測試點 2
});
});
 

⑤ hardhat.config.js

接下來,我們會啟動 HardHat,進行區塊鏈本地環境的搭建部署。在正式啟動之前,需要進行一些配置。 hardhat.config.js 這個文件,就是在 Hardhat 啟動時,默認要讀取的配置文件。
 
// 聲明依賴
require("@nomiclabs/hardhat-waffle");
 
// 聲明 Hardhat 啟動時執行的任務,下面這個任務的作用是打印賬戶的信息
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
 
for (const account of accounts) {
console.log(account.address);
}
});
 
// 聲明配置項
module.exports = {
solidity: "0.8.4",
};
這里可以看到,文件的內容可以分為 3 個部分:
  • 聲明依賴的模塊。
  • 聲明啟動時執行的任務:在官方文檔 https://hardhat.org/guides/create-task.html 里面有更加詳細的說明,這里的示例 task 的作用是:啟動 Hardhat 時,打印出本地這條鏈的默認賬戶。
  • 聲明配置項:更詳細的說明可以參考官方文檔 https://hardhat.org/config/ 和 https://hardhat.org/hardhat-network/reference/#config,這里說明幾個常見配置的含義:
module.exports = {
solidity: "0.8.4", // solidity 編譯器的版本
networks: { // 網絡配置情況,下面可以添加多個網絡的配置
localhost: { // 本地網絡
url: "http://127.0.0.1:8545" // 本地網絡的 url
},
hardhat: { // Hardhat 網絡配置
chainId: 31337, // 鏈 ID,默認 31337
gasPrice: "auto" // gas 價格,默認 auto
},
rinkeby: { // rinkeby 網絡
url: "https://eth-rinkeby.alchemyapi.io/v2/123abc123abc123abc123abc123abcde", // rinkeby 結點的 url
accounts: [privateKey1, privateKey2, ...] // 使用的賬戶列表
}
},
};
 
在“1. 初始化”中,執行了 npx hardhat init 以后,會生成出一個默認的配置文件。 一般來說,使用這個默認配置即可,不需要更改
 
這里面有一個問題,上面的配置中,同時出現了 localhost 和 hardhat 兩種網絡。它們有什么不同呢?這里暫時先不提,在 “3. 智能合約的編譯、部署和測試”這一部分中,你就會找到答案。
 
這樣,項目中各個文件的作用就介紹完畢。可能略微有點枯燥,馬上就可以進入實戰環節了。
 
  1. 智能合約的編譯、部署和測試

① MetaMask 錢包下載、安裝和新建賬戶

MetaMask 是什么呢?它是一個以太坊生態下的錢包,可以管理你的賬戶,支持多種網絡。
 
MetaMask 錢包本質上是一個瀏覽器插件,這里有它的下載地址(https://metamask.io/download/),然后像正常添加其它瀏覽器插件一樣,添加上去就好。
接下來可以通過助記詞的方式,生成一個新賬戶;也可以通過粘貼私鑰的方式,導入你原有的賬戶。
這樣,我們的 MetaMask 錢包就准備好了。
 

② 在本地網絡上部署合約

接下來我們要啟動一個本地區塊鏈網絡節點。
首先打開一個 Terminal/Powershell 窗口,執行 npx hardhat node 命令:
這個命令的意思是,按照 hardhat.config.js 中聲明的配置,啟動一個本地區塊鏈網絡的節點。在每次啟動時,默認會提供 20 個錢包賬戶和私鑰,每個錢包提供 10000 個 ETH 做測試。
 
可以看到,Hardhat 啟動時,把這 20 個賬戶的信息打印了出來。為什么打印賬戶信息呢?因為在 hardhat.config.js 中定義了這個執行任務;如果你忘記了,可以回過頭看一下。
 
啟動成功了,還可以用 MetaMask 這個錢包再驗證一下。
新增一個 localhost 網絡,注意鏈 ID 要和 hardhat.config.js 中的 chainId 相同,默認是 31337.
導入一個賬戶,這里我選擇了那 20 個自動賬戶中的第 1 個,把私鑰粘貼進去。
這里注意一下,這些賬戶僅僅是為了本地測試使用,千萬不要在以太坊主網使用這些賬戶地址!切記!
 
現在,本地網絡環境已經就緒,我們把 contracts/Greeter.sol 部署到本地網絡上。
打開另一個 Terminal/PowerShell,執行 npx hardhat run .\scripts\sample-script.js --network localhost
於是會執行 scripts/sample_scripts.js 中的 main 方法,並且打印出了 Greeter 這個合約的部署地址。
同樣地,在啟動本地網絡的窗口中,也可以看到剛才這個合約的部署情況:
在部署合約時,會執行 constructor 構造函數,所以 console.log("Deploying a Greeter with greeting:", _greeting); 這句被執行並打印出來。
 
部署合約需要消耗 gas,所以可以看到第一個賬戶中的余額不再是 10000 個 ETH 了(默認使用第一個賬戶來操作):
再執行一次 npx hardhat run .\scripts\sample-script.js --network localhost,會發現雖然是同一份合約代碼,但是合約地址發生了變化:
正如在 “③ scripts/sample_scripts.js” 中所說,用 Java 代碼類比一下: Greeter 這個合約像是一個類,而每次部署得到的 greeter 像是一個類的對象,因此每次部署,生成出了不同的對象,得到的合約地址也就不同。
 
我們把 scripts/sample_scripts.js 稍作修改,就可以得到web3版本的 Hello-World 了:
const hre = require("hardhat");
 
async function main() {
const Greeter = await hre.ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, World!");
 
await greeter.deployed();
 
console.log("Greeter deployed to:", greeter.address);
}
 
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
 
到這步為止,我們的 Greeter 合約就成功在本地網絡上部署好了,也成功使用 MetaMask 錢包,進行了本地網絡連接、測試賬戶導入。
 

③ Hardhat 其它常見的命令

剛才我們學習了 Hardhat框架的 2 個命令:
  • npx hardhat node 命令,啟動區塊鏈網絡一個本地節點
  • npx hardhat run .\scripts\sample-script.js --network localhost 命令,把合約部署到了本地網絡上。
 
這一部分,會說明一下其它 Hardhat 的實用命令。
npx hardhat test
這個命令用於執行單元測試,在示例中,也就是運行了 test/sample_test.js 這個文件:
這代表測試成功。我們稍微修改一下 sample_test.js 文件:
const { expect } = require("chai");
const { ethers } = require("hardhat");
 
describe("Greeter", function () {
it("Should return the new greeting once it's changed", async function () {
const Greeter = await ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, world!");
await greeter.deployed();
 
expect(await greeter.greet()).to.equal("Hello, world!");
 
const setGreetingTx = await greeter.setGreeting("Hola, mundo!");
 
// wait until the transaction is mined
await setGreetingTx.wait();
 
expect(await greeter.greet()).to.equal("Hello, world!");
});
});
再次執行命令,就會發現測試失敗了:
你可能會問一個問題:在 sample_test.js 的代碼中,同樣出現了合約的部署語句,那么這個合約是被部署到了哪里呢?再次被部署到了 localhost 這個本地網絡上嗎?
 
答案是否定的。
 
還記得在 “⑤ hardhat.config.js” 一節中提過,有兩個很相似的網絡配置: localhost 和 hardhat。當執行 npx hardhat test 命令時,會內置創建 hardhat 網絡,並在 hardhat 網絡上完成合約部署、方法調用等,不會部署在 localhost 網絡上。這一點一定要注意。
 
npx hardhat console
啟動一個控制台程序,方便交互式輸入輸出。例如輸入 config 查看配置情況:
sample_scripts.js 中的代碼語句,可以換成在 console 中執行:
npx hardhat compile
在執行 npx hardhat run .\scripts\sample-script.js --network localhost 部署合約的時候,其實 hardhat 偷偷幫你做了一件事情: 編譯
 
我們把 artifacts 和 cache 目錄刪掉,現在的目錄結構應該是這樣:
現在我們執行 npx hardhat compile 命令,執行成功后,會發現剛才刪掉的 artifacts 和 cache 目錄,又重新生成出來了。
所以,這個命令的作用就是 編譯。cache 是編譯出來的緩存文件夾。artifacts 目錄下的文件很重要,看一下 artifacts/contracts/Greeter.sol/Greeter.json 這個文件:
{
"_format": "hh-sol-artifact-1",
"contractName": "Greeter",
"sourceName": "contracts/Greeter.sol",
"abi": [
{
"inputs": [
{
"internalType": "string",
"name": "_greeting",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "greet",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_greeting",
"type": "string"
}
],
"name": "setGreeting",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x......",
"deployedBytecode": "0x......",
"linkReferences": {},
"deployedLinkReferences": {}
}
這個文件和原始文件 contracts/Greeter.sol 有點兒像,又有點兒不像。它就是源文件編譯出來的樣子。這個文件很重要,我們之后還會用到它。
 
npx hardhat clean
這個命令和 npx hardhat compile 的作用恰好相反,是把編譯出來的文件清理一下。
 
最后,還有一個重要的命令,叫作 npx hardhat help,這條命令會展示 npx hardhat 系列命令的用法:當你忘記的時候,就可以用這條命令查詢一下。
 

四、結語

這就到了結語部分?
 
是的。跟隨本文,你已經掌握了 Hardhat 開發框架的基本用法;創建了一個web3項目;編寫修改了智能合約;執行了單元測試;啟動運行了本地區塊鏈節點;將合約文件成功進行編譯和部署;還安裝了 MetaMask 錢包,並連接了本地網絡和測試賬戶進行測試。
 
但是,現在這個項目,和我們編寫的第一個 C++ Hello-World 控制台程序一樣,還缺乏一個對用戶友好的前端頁面。如何快速編寫一個前端頁面?如何連接用戶錢包?如果調用智能合約的方法?這些會放在下一篇文章中進行講解。
 
有不清楚的地方,歡迎在評論區留言討論;如果覺得本文不錯的話,歡迎點贊、收藏、轉發。感謝支持!
 
作者:米都督
微信:Meng_Xiang987(加入web3交流群,請備注"web3")
郵箱:miduduur@gmail.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM