第6章:去中心化应用(DApp)开发全流程(2025年10月最新版)

113 阅读4分钟

第6章:去中心化应用(DApp)开发全流程(2025年10月最新版)


声明:仅供学习参考,不构成投资建议

国外可访问:rainweb3知识库

国内可访问:rainweb3知识库


⚠️ 免责声明:本文档旨在提供教育性、参考性的技术指导,基于当前(2025年)社区广泛认可的最佳实践。它不构成任何形式的投资、法律或专业建议。智能合约开发风险极高,任何部署前都应进行严格的自我审查、自动化扫描和第三方审计。

本章将带你完成一个生产级、最新技术栈的去中心化应用(DApp)开发全流程。以构建一个NFT铸造平台为例,涵盖从需求分析到主网上线的完整步骤(作为参考)。


1. 需求分析:确定 DApp 功能

项目目标:开发一个去中心化NFT铸造DApp,允许用户:

  • 连接钱包(MetaMask)
  • 查看NFT总供应量与余额
  • 铸造NFT(支付ETH)
  • 查看已铸造的NFT

技术栈选择(2025年标准)

  • 智能合约:Solidity + OpenZeppelin ERC721
  • 开发框架:Hardhat
  • 前端:React + TypeScript + Ethers.js v6
  • 钱包集成:MetaMask + EIP-1193 兼容
  • 部署网络:Polygon Mumbai(测试网) → Polygon PoS(主网)
  • 前端托管:IPFS(via Pinata 或 Fleek)

2. 设计智能合约逻辑

核心功能

  • 继承 ERC721Ownable(权限控制)
  • 支持公开铸造(publicMint
  • 限制单次铸造数量(防刷)
  • 事件通知(Minted

3. 使用 Hardhat 编写、测试合约

(1)初始化项目
mkdir nft-dapp && cd nft-dapp
npm init -y
npm install --save-dev hardhat
npx hardhat
# 选择 "Create a TypeScript project"
npm install @openzeppelin/contracts @nomicfoundation/hardhat-toolbox
(2)编写合约(contracts/MyNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyNFT is ERC721, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;

    uint256 public maxSupply = 1000;
    uint256 public mintPrice = 0.01 ether;
    uint256 public maxPerWallet = 5;

    mapping(address => uint256) public walletMints;

    event Minted(address indexed minter, uint256 tokenId);

    constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}

    function publicMint(uint256 amount) external payable {
        require(amount > 0, "Must mint at least one");
        require(amount <= maxPerWallet - walletMints[msg.sender], "Exceeds max per wallet");
        require(totalSupply() + amount <= maxSupply, "Exceeds max supply");
        require(msg.value >= mintPrice * amount, "Insufficient ETH");

        for (uint256 i = 0; i < amount; i++) {
            uint256 tokenId = _tokenIdCounter.current();
            _safeMint(msg.sender, tokenId);
            _tokenIdCounter.increment();
            emit Minted(msg.sender, tokenId);
        }

        walletMints[msg.sender] += amount;
    }

    function totalSupply() public view returns (uint256) {
        return _tokenIdCounter.current();
    }

    function withdraw() external onlyOwner {
        (bool sent, ) = payable(owner()).call{value: address(this).balance}("");
        require(sent, "Failed to send");
    }
}
(3)编写测试(test/MyNFT.test.ts
import { expect } from "@jest/globals";
import { ethers } from "hardhat";

describe("MyNFT", function () {
  it("Should mint and emit event", async function () {
    const MyNFT = await ethers.getContractFactory("MyNFT");
    const myNFT = await MyNFT.deploy();
    await myNFT.waitForDeployment();

    const [owner, addr1] = await ethers.getSigners();

    await expect(myNFT.connect(addr1).publicMint(1, { value: ethers.parseEther("0.01") }))
      .to.emit(myNFT, "Minted")
      .withArgs(addr1.address, 0);

    expect(await myNFT.totalSupply()).to.equal(1);
  });
});

运行测试:

npx hardhat test

4. 部署合约到测试网(Polygon Mumbai)

(1)配置 hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
  solidity: "0.8.20",
  networks: {
    mumbai: {
      url: "https://polygon-mumbai.g.alchemy.com/v2/YOUR_ALCHEMY_KEY",
      accounts: [process.env.PRIVATE_KEY],
    },
  },
  etherscan: {
    apiKey: process.env.POLYGONSCAN_API_KEY,
  },
};

export default config;
(2)部署脚本(scripts/deploy.ts
import { ethers } from "hardhat";

async function main() {
  const MyNFT = await ethers.getContractFactory("MyNFT");
  const myNFT = await MyNFT.deploy();
  await myNFT.waitForDeployment();

  console.log("MyNFT deployed to:", await myNFT.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

部署命令:

npx hardhat run scripts/deploy.ts --network mumbai

5. 开发前端(React + Ethers.js v6)

create-react-app frontend --template typescript
cd frontend
npm install ethers@6
核心代码(App.tsx
import { useState, useEffect } from "react";
import { ethers } from "ethers";

const CONTRACT_ADDRESS = "0x..."; // 替换为部署地址
const CONTRACT_ABI = [/* 从 artifacts 中复制 */];

function App() {
  const [provider, setProvider] = useState<ethers.Provider | null>(null);
  const [signer, setSigner] = useState<ethers.Signer | null>(null);
  const [contract, setContract] = useState<ethers.Contract | null>(null);
  const [account, setAccount] = useState<string | null>(null);
  const [supply, setSupply] = useState(0);

  useEffect(() => {
    if (window.ethereum) {
      const provider = new ethers.BrowserProvider(window.ethereum);
      setProvider(provider);
    }
  }, []);

  const connectWallet = async () => {
    if (!provider) return;
    const signer = await provider.getSigner();
    const address = await signer.getAddress();
    setSigner(signer);
    setAccount(address);
    setContract(new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, signer));
  };

  const mintNFT = async () => {
    if (!contract || !signer) return;
    const tx = await contract.publicMint(1, { value: ethers.parseEther("0.01") });
    await tx.wait();
  };

  useEffect(() => {
    const getSupply = async () => {
      if (contract) {
        const supply = await contract.totalSupply();
        setSupply(Number(supply));
      }
    };
    getSupply();
  }, [contract]);

  return (
    <div>
      <h1>MyNFT DApp</h1>
      <p>Total Minted: {supply}</p>
      {!account ? (
        <button onClick={connectWallet}>Connect MetaMask</button>
      ) : (
        <button onClick={mintNFT}>Mint NFT</button>
      )}
    </div>
  );
}

6. 集成钱包(MetaMask)

  • 用户点击“Connect MetaMask” → 触发 window.ethereum.request({ method: "eth_requestAccounts" })
  • MetaMask弹出授权窗口
  • 成功后返回用户地址并更新前端状态

7. 前后端联调与测试

  • 在本地运行前端:npm start
  • 使用MetaMask切换至Polygon Mumbai网络
  • 连接钱包并测试铸造功能
  • 检查控制台和交易哈希

8. 部署前端到 IPFS

npm run build
# 使用 Pinata 或 Fleek 上传 build/ 目录
# 获取 IPFS CID(如: QmXy...)
# 访问: https://ipfs.io/ipfs/QmXy...

9. 上线主网

  1. hardhat.config.ts 网络配置改为 polygon(使用Alchemy Polygon主网节点)
  2. 运行部署命令
  3. Polygonscan 验证合约
  4. 更新前端 CONTRACT_ADDRESS
  5. 重新部署前端至IPFS

总结

本流程展示了2025年生产级DApp开发全貌:从Solidity合约、Hardhat测试、React前端到IPFS托管,每一步均采用最新、最安全的技术方案。