随着区块链技术的飞速发展,去中心化应用(DApps)正逐渐改变着我们与互联网交互的方式,以太坊作为全球最大的智能合约平台,为DApp的开发提供了强大的基础设施,而Web3.js(或其替代如Ethers.js)则是连接前端应用与以太坊区块链的桥梁,使得JavaScript开发者能够轻松与区块链进行交互,本文将通过一个简单的“待办事项(Todo List)”DApp实例,带你走进Web3和以太坊DApp开发的世界。
理解核心概念
在动手之前,我们首先要明确几个核心概念:
- 以太坊(Ethereum):一个开源的、有智能合约功能的公共区块链平台,它允许开发者构建和部署去中心化应用。
- 智能合约(Smart Contract):部署在以太坊区块链上的自动执行程序,它们在满足预定条件时会自动执行,不可篡改。
- DApp(Decentralized Application):结合了传统前端应用与后端智能合约的去中心化应用,前端通常运行在中心化服务器上,而后端逻辑则运行在区块链上。
- Web3.js:一个JavaScript库,它允许你的DApp与以太坊节点进行通信,从而读取区块链数据、发送交易、调用智能合约等。
- MetaMask:一款流行的浏览器钱包插件,它能让用户安全地管理其以太坊账户、私钥,并与DApp进行交互,充当DApp与以太坊网络之间的桥梁。
开发环境准备
在开始编码之前,我们需要准备好以下工具和环境:
- Node.js 和 npm/yarn:Node.js是JavaScript运行时环境,npm是Node.js的包管理器,从Node.js官网下载并安装LTS版本。
- 代码编辑器:如VS Code、Sublime Text等。
- MetaMask浏览器插件:在Chrome、Firefox等浏览器中安装MetaMask扩展,并创建一个测试账户。
- 以太坊测试网络:为了不消耗真实的以太币(ETH),我们使用测试网络,如Ropsten、Kovan或Goerli,你需要从某个“水龙头”(Faucet)网站获取测试ETH。
- Truffle框架:一个流行的以太坊开发框架,用于智能合约的编译、测试和部署。
- Ganache:一款个人以太坊区块链,可以让你在本地快速创建和运行区块链,方便开发和测试。
实战步骤:构建一个简单的Todo List DApp
我们将分步构建一个简单的Todo List DApp,包括智能合约编写、前端交互和部署。
步骤1:初始化项目与安装依赖
- 创建一个新的项目目录,并进入该目录:
mkdir ethereum-dapp-tutorial cd ethereum-dapp-tutorial
- 使用Truffle初始化项目:
truffle init
这会创建一些标准目录,如
contracts(存放智能合约)、migrations(部署脚本)、test(测试文件)等。 - 安装项目依赖:
npm install --save truffle-hdwallet-provider @openzeppelin/contracts
truffle-hdwallet-provider:允许Truffle使用MetaMask的助记词或私钥进行部署,支持多个账户。@openzeppelin/contracts:提供经过审计的、可重用的智能合约标准库,增强安全性。
步骤2:编写智能合约
在contracts目录下,创建一个新的智能合约文件TodoList.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
contract TodoList is Ownable {
struct Task {
uint id;
string content;
bool completed;
}
mapping(uint => Task) public tasks;
uint public taskCount = 0;
event TaskCreated(uint id, string content, bool completed);
event TaskCompleted(uint id, bool completed);
constructor() {
// 创建一个初始任务作为示例
createTask("Initial Task");
}
function createTask(string memory _content) public {
taskCount++;
tasks[taskCount] = Task(taskCount, _content, false);
emit TaskCreated(taskCount, _content, false);
}
function toggleCompleted(uint _id) public {
Task storage task = tasks[_id];
require(task.id != 0, "Task does not exist"); // 简单检查任务是否存在
task.completed = !task.completed;
emit TaskCompleted(_id, task.completed);
}
function getTask(uint _id) public view returns (uint id, string memory content, bool completed) {
Task storage task = tasks[_id];
return (task.id, task.content, task.completed);
}
}
这个合约实现了:
- 添加任务(
createTask) - 切换任务完成状态(
toggleCompleted) - 获取任务信息(
getTask) - 使用
Ownable来限制某些操作(虽然本例中未严格限制,但是一个好习惯)
步骤3:配置Truffle部署文件
在项目根目录下,创建或修改truffle-config.js文件:
require('dotenv').config(); // 用于加载环境变量
const { HDWalletProvider } = require('@truffle/hdwallet-provider');
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545, // Ganache默认端口
network_id: "*", // 匹配任何network id
},
goerli: { // 使用Goerli测试网
provider: () => new HDWalletProvider(
process.env.MNEMONIC, // 你的MetaMask助记词或私钥
`https://goerli.infura.io/v3/${process.env.INFURA_PROJECT_ID}` // 你的Infura项目ID
),
network_id: 5, // Goerli的network id
gas: 5500000,
confirmations: 2,
timeoutBlocks: 200,
skipDryRun: true
}
},
compilers: {
solc: {
version: "0.8.0", // 智能合约编译版本
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
}
},
mocha: {
timeout: 100000
}
};
你需要创建一个.env文件来存储敏感信息(如助记词和Infura项目ID),并在.gitignore中忽略.env文件。
步骤4:编写迁移脚本
在migrations目录下,创建一个新的迁移文件,例如2_deploy_todos.js:
const TodoList = artifacts.require("TodoList");
module.exports = function (deployer) {
deployer.deploy(TodoList);
};
步骤5:编译和部署智能合约
-
编译:
truffle compile
这会在
build/contracts目录下生成编译后的ABI(应用程序二进制接口)和字节码。 -
部署到本地开发网络(Ganache): 确保Ganache正在运行,然后执行:
truffle migrate --network development
这会将智能合约部署到你的本地Ganache区块链上。
-
部署到测试网络(如Goerli): 确保你的MetaMask账户有足够的测试ETH,并且
.env文件中的配置正确。 然后执行:truffle migrate --network goerli --reset
--reset会重新执行所有迁移脚本。
步骤6:开发前端应用
-
在项目根目录下创建一个
client目录,并初始化一个React应用(或使用纯HTML/CSS/JS):npx create-react-app client cd client npm install web3 # 或使用 ethers.js: npm install ethers cd ..
-
在
client/src目录下,我们可以创建一个App.js文件来与智能合约交互:import React, { useState, useEffect } from 'react'; import Web3 from 'web3'; import TodoListContract from '../../build/contracts/TodoList.json'; // 导入编译后的合约ABI function App() { const [web3, setWeb3] = useState(null); const [account, setAccount] = useState(null); const [contract, setContract] = useState(null); const [tasks, setTasks] = useState([]); const [newTaskContent, setNewTaskContent] = useState(''); useEffect(()