从零开始,手把手教你构建你的第一个以太坊DApp

区块链技术的浪潮下,去中心化应用(DApp)正逐渐从概念走向现实,以太坊作为全球最大的智能合约平台,凭借其强大的图灵完备性、活跃的开发者社区和丰富的生态资源,成为了构建DApp的首选平台,如果你也对DApp开发充满好奇,渴望亲手打造一个属于自己的去中心化应用,那么本教程将为你铺设一条清晰的学习路径。

什么是DApp?

在开始之前,我们先简单了解一下DApp,DApp(Decentralized Application),即去中心化应用,其核心特点在于:

  1. 去中心化:应用运行在分布式网络上(如以太坊区块链),而非单一的服务器。
  2. 智能合约:应用的核心逻辑由部署在区块链上的智能合约(Smart Contract)执行,合约代码即法律,一旦部署不可篡改。
  3. 代币经济:通常拥有原生代币(Token),用于应用内的激励、治理或支付。
  4. 用户控制:用户拥有自己的数据和私钥,真正掌控自己的数字资产。

构建以太坊DApp的必备组件

在动手之前,你需要了解和准备以下核心工具与环境:

  1. 钱包:如MetaMask,它是与以太坊交互的浏览器插件钱包,用于管理账户、私钥、发送交易以及与DApp进行身份验证和资产交互。
  2. 以太坊节点/测试网:为了安全和学习,我们通常在测试网(如Ropsten, Goerli, Sepolia)上进行开发和测试,而不是消耗真实的以太币,你可以使用Infura或Alchemy等服务来连接以太坊节点。
  3. 开发环境
    • 代码编辑器:Visual Studio Code 是首选,配合Solidity插件(如Hardhat或Truffle Suite)。
    • Node.js 和 npm/yarn:用于运行JavaScript/TypeScript代码和管理项目依赖。
  4. 开发框架
    • Hardhat:一个现代的、灵活的以太坊开发环境,编译、测试、部署和调试智能合约非常方便,是目前社区推荐的主流工具之一。
    • Truffle:老牌的以太坊开发框架,功能全面,拥有成熟的测试和部署流程。
  5. 前端框架:如React, Vue.js, Angular等,用于构建DApp的用户界面(UI),我们将以React为例。
  6. 智能合约语言:Solidity是以太坊最主流的智能合约编程语言,类似于JavaScript,但专为智能合约设计。

手把手构建你的第一个简单DApp:一个“Hello, DApp!”

我们将创建一个简单的投票DApp,包含一个智能合约用于记录投票选项,和一个简单的React前端用于用户交互。

步骤1:项目初始化与安装依赖

  1. 创建一个新的项目文件夹,并初始化一个npm项目:
    hello-dapp
    cd hello-dapp
    npm init -y
  2. 安装Hardhat和相关依赖:
    npm install --save-dev hardhat
    npx hardhat

    按照提示选择 "Create a basic sample project",然后安装示例依赖。

  3. 安装React和相关依赖(用于前端):
    npx create-react-app frontend
    cd frontend
    npm install ethers
    npm install --save-dev @nomicfoundation/hardhat-toolbox
    cd ..

步骤2:编写智能合约

  1. contracts目录下,创建一个新的Solidity文件,例如Voting.sol

  2. 编写一个简单的投票合约:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.9;
    contract Voting {
        mapping(string => uint256) public votes;
        string[] public candidates;
        constructor(string[] memory _candidates) {
            candidates = _candidates;
        }
        function vote(string memory candidateName) public {
            bool found = false;
            for (uint i = 0; i < candidates.length; i++) {
                if (keccak256(bytes(candidates[i])) == keccak256(bytes(candidateName))) {
                    found = true;
                    break;
                }
            }
            require(found, "Candidate not found");
            votes[candidateName]++;
        }
        function getVotes(string memory candidateName) public view returns (uint256) {
            return votes[candidateName];
        }
    }

步骤3:编译和测试合约

  1. 在项目根目录下,确保hardhat.config.js文件已正确配置(通常会自动生成)。
  2. 编译合约:
    npx hardhat compile
  3. 编写测试脚本(在test目录下),例如voting.test.js,并运行测试:
    npx hardhat test

步骤4:部署合约

  1. scripts目录下,创建一个部署脚本,例如deploy.js

    async function main() {
        const Voting = await hre.ethers.getContractFactory("Voting");
        const candidates = ["Alice", "Bob"];
        const voting = await Voting.deploy(candidates);
        await voting.deployed();
        console.log("Voting contract deployed to:", voting.address);
    }
    main().then(() => process.exit(0)).catch(error => {
        console.error(error);
        process.exit(1);
    });
  2. 配置部署网络(例如Goerli测试网)在hardhat.config.js中,并添加环境变量.env文件(包含你的Infura/Alchemy URL和测试网私钥)。

  3. 部署合约:随机配图

>
npx hardhat run scripts/deploy.js --network goerli

记录下部署后的合约地址。

步骤5:构建前端与交互

  1. 进入frontend目录,修改src/App.js

    import React, { useState, useEffect } from 'react';
    import { ethers } from 'ethers';
    function App() {
      const [contract, setContract] = useState(null);
      const [candidates, setCandidates] = useState([]);
      const [votes, setVotes] = useState({});
      const [selectedCandidate, setSelectedCandidate] = useState('');
      useEffect(() => {
        const loadContract = async () => {
          // 替换为你的合约地址和ABI
          const contractAddress = "YOUR_DEPLOYED_CONTRACT_ADDRESS";
          const contractABI = [/* 这里粘贴你的Voting合约的ABI */];
          if (window.ethereum) {
            const provider = new ethers.providers.Web3Provider(window.ethereum);
            const signer = provider.getSigner();
            const votingContract = new ethers.Contract(contractAddress, contractABI, signer);
            setContract(votingContract);
            // 获取候选人列表
            const candidatesList = await votingContract.candidates();
            setCandidates(candidatesList);
            // 获取各候选人票数
            const votesMap = {};
            for (let candidate of candidatesList) {
              votesMap[candidate] = (await votingContract.getVotes(candidate)).toString();
            }
            setVotes(votesMap);
          } else {
            alert("Please install MetaMask!");
          }
        };
        loadContract();
      }, []);
      const handleVote = async () => {
        if (contract && selectedCandidate) {
          try {
            const tx = await contract.vote(selectedCandidate);
            await tx.wait();
            alert("Vote cast successfully!");
            // 刷新票数
            const newVotes = {...votes};
            newVotes[selectedCandidate] = (parseInt(newVotes[selectedCandidate]) + 1).toString();
            setVotes(newVotes);
          } catch (error) {
            console.error(error);
            alert("Failed to vote!");
          }
        }
      };
      return (
        <div className="App">
          <h1>Voting DApp</h1>
          <div>
            <h2>Candidates:</h2>
            <ul>
              {candidates.map((candidate, index) => (
                <li key={index}>
                  <input
                    type="radio"
                    id={candidate}
                    name="candidate"
                    value={candidate}
                    checked={selectedCandidate === candidate}
                    onChange={() => setSelectedCandidate(candidate)}
                  />
                  <label htmlFor={candidate}>{candidate} - Votes: {votes[candidate] || 0}</label>
                </li>
              ))}
            </ul>
            <button onClick={handleVote} disabled={!selectedCandidate}>Vote</button>
          </div>
        </div>
      );
    }
    export default App;
    • 注意:你需要将YOUR_DEPLOYED_CONTRACT_ADDRESS替换成你实际部署的合约地址,并在contractABI数组中粘贴你的Voting.sol合约的ABI(可以在artifacts/contracts/Voting.sol/Voting.json中找到)。
  2. 启动React开发服务器:

    npm start
  3. 在浏览器中打开http://localhost:3000,确保

本文由用户投稿上传,若侵权请提供版权资料并联系删除!

上一篇:

下一篇: