以太坊作为全球第二大加密货币平台,更是一个强大的去中心化应用(DApp)开发平台,它通过智能合约实现了可编程的信任,为金融、游戏、供应链、社交媒体等众多领域带来了革命性的可能,对于初学者而言,理论学习固然重要,但“以太坊基础项目实战”才是真正掌握其核心魅力的不二法门,本文将带你走完一个简单的以太坊基础项目实战流程,从环境搭建到部署交互,让你亲身体验DApp开发的乐趣与挑战。
项目实战准备:工欲善其事,必先利其器
在开始编码之前,我们需要准备好开发环境:
- 安装Node.js和npm/yarn:Node.js是JavaScript运行时环境,npm是其包管理器,DApp开发的前端部分通常需要它们,从Node.js官网下载并安装LTS版本即可。
- 安装MetaMask:MetaMask是一款流行的浏览器钱包插件,它让你能与以太坊区块链进行交互,管理私钥、发送交易、与DApp进行连接,从MetaMask官网下载并安装到你的浏览器(如Chrome、Firefox)。
- 选择开发框架与库:
- Hardhat:一个流行的以太坊开发环境,编译、测试、部署智能合约非常方便,插件丰富。
- Truffle:老牌的以太坊开发框架,功能全面,社区庞大。
- Ethers.js:一个功能强大且灵活的JavaScript库,用于与以太坊网络和智能合约进行交互。
- React/Vue:用于构建DApp的前端界面,本文将以React为例。
- 配置本地测试网络:为了避免在开发过程中消耗真实的ETH(测试币),我们通常在本地运行一个以太坊节点,Hardhat内置了Hardhat Network,开箱即用,非常适合开发测试。
项目实战步骤:构建一个简单的“留言板”DApp
我们将构建一个经典的“以太坊留言板”DApp,用户可以通过这个DApp在区块链上留下自己的留言,并查看所有留言。
初始化项目与安装依赖
- 创建一个新的项目目录,并初始化一个Node.js项目:
mkdir ethereum-message-board cd ethereum-message-board npm init -y
- 安装Hardhat和Ethers.js:
npm install --save-dev hardhat npm install ethers
- 初始化Hardhat项目:
npx hardhat
按照提示选择"Create a basic JavaScript project"(或其他你熟悉的语言),然后一路回车,Hardhat会帮你生成基本的
contracts/、scripts/、test/等目录。
编写智能合约
-
在
contracts目录下,创建一个新的Solidity文件MessageBoard.sol。 -
编写智能合约代码:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract MessageBoard { // 定义一个结构体来存储留言信息 struct Message { address author; // 留言者地址 string content; // 留言内容 uint256 timestamp; // 留言时间戳 } // 存储留言的数组 Message[] public messages; // 留言事件,方便前端监听 event NewMessage(address indexed author, string content, uint256 timestamp); // 留言函数 function leaveMessage(string memory _content) public { require(bytes(_content).length > 0, "Message content cannot be empty"); messages.push(Message(msg.sender, _content, block.timestamp)); emit NewMessage(msg.sender, _content, block.timestamp); } // 获取所有留言 function getMessages() public view returns (Message[] memory) { return messages; } }这个合约定义了一个
Message结构体,一个存储留言的数组messages,以及两个核心函数:leaveMessage用于用户留言,getMessages用于获取所有留言。
编译智能合约
- 在Hardhat项目中,运行编译命令:
npx hardhat compile
编译成功后,你会在
artifacts/contracts/MessageBoard.sol/目录下看到编译后的ABI(应用程序二进制接口)和字节码文件,ABI是前端与智能合约交互的桥梁。
部署智能合约
-
在
scripts目录下,创建一个部署脚本deploy.js:const hre = require("hardhat"); async function main() { // 获取MessageBoard合约工厂 const MessageBoard = await hre.ethers.getContractFactory("MessageBoard"); // 部署合约 const messageBoard = await MessageBoard.deploy(); // 等待部署完成 await messageBoard.deployed(); console.log("MessageBoard deployed to:", messageBoard.address); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); -
配置Hardhat网络:在
hardhat.config.js中,确保默认网络是Hardhat Network(通常默认配置即可)。 -
运行部署脚本:
npx hardhat run scripts/deploy.js --network localhost
执行成功后,你会看到输出的合约地址,这就是我们部署到本地测试网的留言板合约地址。请务必记下这个地址,前端需要用到它。
构建前端界面
-
在项目根目录下,创建一个
src文件夹,用于存放前端代码。 -
初始化React应用(可选,也可以直接用HTML/CSS/JS):
npx create-react-app frontend cd frontend npm install ethers
-
在
frontend/src目录下,修改App.js或创建新的组件来构建界面:import React, { useState, useEffect } from 'react'; import { ethers } from 'ethers'; import MessageBoard from '../contracts/MessageBoard.json'; // 导入编译后的ABI function App() { const [account, setAccount] = useState(null); const [messageBoard, setMessageBoard] = useState(null); const [messages, setMessages] = useState([]); const [newMessage, setNewMessage] = useState(''); // 初始化连接 useEffect(() => { const init = async () => { if (window.ethereum) { try { // 请求账户访问 const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); setAccount(accounts[0]); // 创建provider和signer const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner(); // 替换为你的合约部署地址 const contractAddress = "0x..."; // 这里填入你部署的合约地址 const messageBoardInstance = new ethers.Contract(contractAddress, MessageBoard.abi, signer); setMessageBoard(messageBoardInstance); // 获取初始留言 const initialMessages = await messageBoardInstance.getMessages(); setMessages(initialMessages); } catch (error) { console.error("Error connecting to MetaMask:", error); } } else { alert("Please install MetaMask!"); } }; init(); }, []); // 留言功能 const handleLeaveMessage = async () => { if (messageBoard && newMessage.trim() !== '') { try { const tx = await messageBoard.leaveMessage(newMessage); await tx.wait(); // 等待交易确认 // 更新留言列表 const updatedMessages = await messageBoard.getMessages(); setMessages(updatedMessages); setNewMessage(''); } catch (error) { console.error("Error leaving message:", error); } } }; return ( <div className="App"> <h1>以太坊留言板</h1> {account ? ( <p>当前账户: {account}</p> ) : ( <p>请连接MetaMask</p> )} <div> <input type="text" value={newMessage} onChange={(e) => setNewMessage(e.target.value)} placeholder="输入你的留言" /> <button onClick={handleLeaveMessage}>留言</button> </div> <div> <h2>留言列表:</h2> <ul> {messages.map((msg, index) => ( <li key={index}> <strong>{msg.author}:</strong> {msg.content} <em>({new Date(msg.timestamp * 1000).toLocaleString()})