第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. 设计智能合约逻辑
核心功能:
- 继承
ERC721和Ownable(权限控制) - 支持公开铸造(
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. 上线主网
- 将
hardhat.config.ts网络配置改为polygon(使用Alchemy Polygon主网节点) - 运行部署命令
- 在 Polygonscan 验证合约
- 更新前端
CONTRACT_ADDRESS - 重新部署前端至IPFS
总结
本流程展示了2025年生产级DApp开发全貌:从Solidity合约、Hardhat测试、React前端到IPFS托管,每一步均采用最新、最安全的技术方案。