以太坊App开发的核心概念
在动手开发前,需先理解以太坊App(DApp)的核心逻辑:“前端+智能合约+区块链交互”的三层架构。
- 智能合约:运行在以太坊虚拟机(EVM)上的自动执行代码,是DApp的“后端”,负责业务逻辑与数据存储(如用户账户、交易记录等),以Solidity语言编写。
- 前端:用户直接交互的界面(网页/移动端),通过Web3.js或Ethers.js等库与智能合约通信,发起交易或读取数据。
- 区块链交互:通过节点(如Infura、Alchemy)或本地节点(Geth)连接以太坊网络,广播交易、同步数据。
开发前准备:环境与工具搭建
开发环境配置
- Node.js:前端运行环境,建议版本≥16(通过
node -v检查)。 - 代码编辑器:VS Code(推荐安装Solidity、Hardhat、Remix IDE插件)。
- MetaMask:浏览器钱包插件,用于测试网交互与私钥管理(开发必备)。
- 本地以太坊节点:可选,Hardhat内置节点,或使用Geth搭建本地测试链。
核心工具链
- Hardhat:以太坊开发框架,支持编译、测试、部署智能合约,内置调试工具(比Truffle更现代)。
- Solidity:智能合约编程语言,需掌握基础语法(变量、函数、修饰符、事件等)。
- Web3.js/Ethers.js:JavaScript库,用于前端与智能合约交互(Ethers.js更简洁,推荐新手使用)。
- 测试网:Ropsten(即将淘汰)、Sepolia、Goerli(以太坊测试网),用于免费测试合约功能。
开发步骤:从智能合约到前端上线
第一步:设计智能合约逻辑
以简单“投票DApp”为例,合约需实现:
- 候选人列表管理;
- 用户投票功能(每人一票);
- 实时显示投票结果。
// contracts/Voting.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Voting {
mapping(address => bool) public hasVoted;
mapping(string => uint256) public voteCount;
string[] public candidates;
constructor(string[] memory _candidates) {
candidates = _candidates;
}
function vote(string memory candidate) public {
require(!hasVoted[msg.sender], "You have already voted!");
require(_isValidCandidate(candidate), "Invalid candidate!");
hasVoted[msg.sender] = true;
voteCount[candidate]++;
}
function _isValidCandidate(string memory candidate) private view returns (bool) {
for (uint i = 0; i < candidates.length; i++) {
if (keccak256(bytes(candidates[i])) == keccak256(bytes(candidate))) {
return true;
}
}
return false;
}
function getCandidates() public view returns (string[] memory) {
return candidates;
}
}
第二步:使用Hardhat编译与测试合约
-
初始化Hardhat项目:
mkdir voting-dapp && cd voting-dapp npm init -y npm install --save-dev hardhat npx hardhat # 选择"Create a basic sample project",默认配置
-
添加合约文件:将上述
Voting.sol放入contracts/目录。 -
编写测试脚本(
test/voting.test.js):const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("Voting", function () { let voting; const candidates = ["Alice", "Bob"]; beforeEach(async function () { const Voting = await ethers.getContractFactory("Voting"); voting = await Voting.deploy(candidates); await voting.deployed(); }); it("Should initialize with candidates", async function () { const retrievedCandidates = await voting.getCandidates(); expect(retrievedCandidates).to.deep.equal(candidates); }); it("Should allow voting", async function () { await voting.vote("Alice"); const voteCount = await voting.voteCount("Alice"); expect(voteCount).to.equal(1); }); it("Should prevent double voting", async function () { await voting.vote("Alice"); await expect(voting.vote("Alice")).to.be.revertedWith("You have already voted!"); }); }); -
运行测试:
npx hardhat test
第三步:部署智能合约到测试网
-
配置网络信息:在
hardhat.config.js中添加测试网配置(以Sepolia为例):require("@nomicfoundation/hardhat-toolbox"); require('dotenv').config(); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: "0.8.0", networks: { sepolia: { url: process.env.SEPOLIA_URL, // 从Infura/Alchemy获取 accounts: [process.env.PRIVATE_KEY], // 测试账户私钥(需.env文件管理) }, }, }; -
创建部署脚本(
scripts/deploy.js):async function main() { const Voting = await ethers.getContractFactory("Voting"); const voting = await Voting.deploy(["Alice", "Bob"]); await voting.deployed(); console.log("Voting contract deployed to:", voting.address); } main().catch((error) => { console.error(error); process.exitCode = 1; }); -
部署到测试网:
npx hardhat run scripts/deploy.js --network sepolia
部署成功后,合约地址会显示在控制台,可复制到Etherscan测试网查看。
第四步:开发前端界面与交互
-
安装前端依赖:
npm install react react-dom @ethersproject/providers ethers
-
创建React应用(
src/App.js):import { useState, useEffect } from 'react'; import { ethers } from 'ethers'; function App() { const [contract, setContract] = useState(null); const [candidates, setCandidates] = useState([]); const [voteCounts, setVoteCounts] = useState({}); const [selectedCandidate, setSelectedCandidate] = useState(''); const [account, setAccount] = useState(''); // 初始化:连接钱包与合约 useEffect(() => { const init = async () => { // 连接MetaMask if (window.ethereum) { const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); setAccount(accounts[0]); const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner(); // 部署合约地址(替换为实际部署地址) const votingAddress = "0xYourContractAddress"; const votingContract = new ethers.Contract(votingAddress, [ "function getCandidates() view returns (string[])", "function vote(string)", "function voteCount(string) view returns (uint256)" ], signer); setContract(votingContract); // 获取候选人数据 const candidatesList = await votingContract.getCandidates(); setCandidates(candidatesList); const counts = {}; for (const candidate of candidatesList) { counts[candidate] = (await votingContract.voteCount(candidate)).toString(); } setVoteCounts(counts); } }; init(); }, []); // 投票函数 const handleVote = async () => { if (!selectedCandidate || !contract) return; try { const tx = await contract.vote(selectedCandidate); await tx.wait(); alert("投票成功!"); // 刷新投票数据 const newCounts = { ...voteCounts }; newCounts[selectedCandidate] = (parseInt(newCounts[selectedCandidate]) + 1).toString(); setVoteCounts(newCounts); } catch (error) { console.error(error); alert("投票失败:" + error.message); } }; return ( <div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}> <h1>以太坊投票DApp</h1> <p>当前账户: {account}</p> <h2>候选人列表</h2> <ul> {candidates.map((candidate, index) => ( <li key={index}> {candidate} - 票数: {voteCounts[candidate] || 0} <input type="radio" name="candidate








