以太坊,作为全球领先的智能合约平台,不仅开创了去中心化应用(DApps)的新纪元,也为开发者和创业者提供了无限的创新可能,本教程将带你从环境搭建到智能合约编写,再到前端交互,一步步完成一个简单的以太坊DApp开发实战,助你快速入门以太坊开发的世界。
开发环境准备:工欲善其事,必先利其器
在正式开始之前,我们需要准备好一系列开发工具:
- Node.js 和 npm:JavaScript 运行时环境和包管理器,建议从 Node.js 官网 下载 LTS 版本。
- Truffle Suite:以太坊最受欢迎的开发框架之一,包含开发环境、测试框架和资产管道,它简化了智能合约的编译、测试和部署流程。
- 安装:
npm install -g truffle
- 安装:
- Ganache:一款个人区块链,用于快速部署和测试智能合约,它会在本地创建一个区块链网络,并提供预设的测试账户和以太坊。
- 下载:Ganache 官网
- MetaMask:一款浏览器插件钱包,用于与以太坊区块链交互,在开发中,它可以模拟用户钱包,让我们能够与本地或测试网的智能合约进行交互。
- 安装:从 MetaMask 官网 或浏览器应用商店安装,并创建一个测试账户。
- 代码编辑器:推荐使用 Visual Studio Code (VS Code),并安装 Solidity 插件以获得语法高亮和智能提示。
- Git:版本控制工具,用于管理代码。
创建项目结构
- 创建一个新的项目文件夹,
my-first-dapp,并在终端中进入该文件夹。 - 初始化一个新的 Truffle 项目:
truffle init
这会生成以下标准目录结构:
contracts/:存放 Solidity 智能合约文件。migrations/:存放部署脚本文件。test/:存放测试文件。truffle-config.js:Truffle 配置文件。
编写智能合约
我们的目标是创建一个简单的“存储合约”,允许用户存储和获取一个字符串。
-
在
contracts目录下创建一个新的 Solidity 文件,Storage.sol。 -
编写以下合约代码:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @title Storage * @dev 一个简单的存储合约,允许用户存储和检索一个字符串。 */ contract Storage { string private storedData; event DataStored(string newData); /** * @dev 存储一个字符串。 * @param _data 要存储的字符串。 */ function set(string memory _data) public { storedData = _data; emit DataStored(_data); } /** * @dev 获取存储的字符串。 * @return 存储的字符串。 */ function get() public view returns (string memory) { return storedData; } }
编译智能合约
在终端中,确保你位于项目根目录,运行以下命令编译合约:
truffle compile
如果成功,你会在 build/contracts 目录下看到编译后的合约 JSON 文件,其中包含合约的 ABI(应用程序二进制接口)和字节码。
编写部署脚本
-
在
migrations目录下创建一个新的迁移脚本文件,2_deploy_storage.js。 -
编写以下部署代码:
const Storage = artifacts.require("Storage"); module.exports = function (deployer) { deployer.deploy(Storage); };
部署到本地 Ganache 网络
- 确保你的 Ganache 已经启动并运行。
- 在
truffle-config.js中,确保已经配置了本地开发网络,默认情况下,Truffle 会自动检测 Ganache,如果没有,你需要手动添加网络配置:module.exports = { // ... 其他配置 networks: { development: { host: "127.0.0.1", // Localhost (default: none) port: 7545, // Standard Ethereum port (default: none) network_id: "*", // Any network (default: none) }, }, // ... 其他配置 }; - 部署合约到本地网络:
truffle migrate --network development
如果成功,你会在终端看到部署的合约地址,Ganache 的交易列表中会显示相应的部署交易。
创建前端界面与智能合约交互
-
在项目根目录下创建一个
src文件夹,用于存放前端代码。 -
在
src文件夹下创建index.html和app.js。 -
index.html:
<!DOCTYPE html> <html lang
="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>以太坊存储DApp</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } input, button { padding: 8px; margin: 5px; } #result { margin-top: 10px; font-weight: bold; } </style> </head> <body> <h1>以太坊存储DApp</h1> <div> <input type="text" id="dataInput" placeholder="输入要存储的数据"> <button onclick="setData()">存储数据</button> </div> <div> <button onclick="getData()">获取数据</button> <div id="result">结果将显示在这里...</div> </div> <script src="app.js"></script> </body> </html>
-
app.js:
// 引入 Web3 库 (假设通过 CDN 引入) // 在实际项目中,你可以使用 npm install web3 if (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider); } else { // 如果没有 MetaMask,则连接到本地 Ganache web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:7545")); } // 合约地址和 ABI (从 build/contracts/Storage.json 复制) const contractAddress = '0x...'; // 替换为你的部署合约地址 const contractABI = [ // 从 build/contracts/Storage.json 中复制 ABI 数组 // // { // "constant": false, // "inputs": [ // { // "name": "_data", // "type": "string" // } // ], // "name": "set", // "outputs": [], // "payable": false, // "stateMutability": "nonpayable", // "type": "function" // }, // { // "constant": true, // "inputs": [], // "name": "get", // "outputs": [ // { // "name": "", // "type": "string" // } // ], // "payable": false, // "stateMutability": "view", // "type": "function" // } ]; // 创建合约实例 const storageContract = new web3.eth.Contract(contractABI, contractAddress); // 获取当前账户 let currentAccount; web3.eth.getAccounts().then(accounts => { currentAccount = accounts[0]; console.log("当前账户:", currentAccount); }); // 存储数据 async function setData() { const dataInput = document.getElementById('dataInput').value; if (!dataInput) { alert("请输入数据!"); return; } try { await storageContract.methods.set(dataInput).send({ from: currentAccount }); alert("数据存储成功!"); document.getElementById('dataInput').value = ""; } catch (error) { console.error("存储数据失败:", error); alert("存储数据失败:" + error.message); } } // 获取数据 async function getData() { try { const result = await storageContract.methods.get().call(); document.getElementById('result').innerText = "存储的数据: