第 11 章 | 结课项目实战:从合约设计到前端部署的完整 DApp 安全流程

第 11 章 | 结课项目实战:从合约设计到前端部署的完整 DApp 安全流程

——用一套安全可复用的开发规范,把你写的项目真正上线


✅ 本章导读

你已经掌握了:

  • 合约开发的核心语法

  • 各类典型攻击原理与防御方式

  • 工具链的使用与审计流程

  • 部署、验证、前端对接的安全规范

是时候,真正构建一个完整的 DApp 项目。

本章将带你做一件事:

从零搭建一套功能明确、安全设计清晰、部署可验证的智能合约项目。


项目目标:构建一个「奖励领取 DApp」

用户可以:

  • 使用钱包连接

  • 领取每周一次的奖励(带 claim 签名 + 防 bot)

  • 所有操作前端可视化交互

  • 合约使用权限控制、结构化签名校验

  • 项目可部署到 Sepolia 测试网 + 验证


项目模块一览

模块 技术栈 说明
合约 Solidity + Hardhat 奖励领取逻辑 / 权限控制 / 防多次 claim
安全审计工具链 Slither + Foundry Test + Verify 全面测试 + 安全检查
前端交互 React + Ethers.js 钱包连接 + 签名授权 + 调用合约
签名生成(后端) Node.js + express / NestJS 验证钱包 + 生成 EIP-712 签名
部署验证 Etherscan / Hardhat Verify 自动部署脚本 + 主网/测试网自动验证

1️⃣ 合约设计(ClaimContract.sol)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract RewardClaimer is Ownable {
    using ECDSA for bytes32;

    mapping(address => bool) public claimed;
    address public signer; // 后端签名地址

    event Claimed(address user, uint256 amount);

    constructor(address _signer) {
        signer = _signer;
    }

    function setSigner(address _signer) external onlyOwner {
        signer = _signer;
    }

    function claim(uint256 amount, uint256 deadline, bytes calldata signature) external {
        require(!claimed[msg.sender], "Already claimed");
        require(block.timestamp <= deadline, "Signature expired");

        bytes32 hash = keccak256(abi.encodePacked(msg.sender, amount, deadline)).toEthSignedMessageHash();
        require(hash.recover(signature) == signer, "Invalid signature");

        claimed[msg.sender] = true;
        payable(msg.sender).transfer(amount);

        emit Claimed(msg.sender, amount);
    }

    receive() external payable {}
}

2️⃣ 前端交互逻辑(React + Ethers.js)

钱包连接

import { ethers } from "ethers";

const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

获取签名 + 调用 claim

const res = await fetch("/api/sign", {
  method: "POST",
  body: JSON.stringify({
    address: userAddress,
    amount: 0.1e18,
    deadline: Date.now() + 600000
  })
});
const { signature } = await res.json();

const contract = new ethers.Contract(contractAddress, abi, signer);
await contract.claim(amount, deadline, signature);

3️⃣ 后端签名服务(Node.js)

const { ethers } = require("ethers");
const privateKey = process.env.SIGNER_PRIVATE_KEY;
const wallet = new ethers.Wallet(privateKey);

app.post("/api/sign", async (req, res) => {
  const { address, amount, deadline } = req.body;
  const messageHash = ethers.utils.solidityKeccak256(["address", "uint256", "uint256"], [address, amount, deadline]);
  const signature = await wallet.signMessage(ethers.utils.arrayify(messageHash));
  res.json({ signature });
});

4️⃣ 测试与部署

✅ Foundry / Hardhat 测试覆盖

function testFail_claimTwice() public {
    // 调用 claim 一次成功,再次 claim 应失败
}

✅ Slither 检查无 Reentrancy / 权限逻辑问题

slither contracts/RewardClaimer.sol

✅ 部署脚本

npx hardhat run scripts/deploy.ts --network sepolia
npx hardhat verify --network sepolia 0x... "0xSignerAddress"

安全亮点说明

风险点 安全机制
重复领取 claimed[msg.sender] 显式防止重复 claim
签名钓鱼 使用结构化 hash + eth_signedMessage + recover
前端合约地址调包 hardcode 地址 + 网络识别
签名伪造 仅有指定 signer 地址生成的签名被接受
非钱包地址 claim(合约 bot) 可加入 tx.origin == msg.sender 二层过滤

5️⃣ 项目打包发布建议

  • 提交 GitHub:分 contracts/ frontend/ scripts/ docs/

  • 添加 README + 部署地址 + Etherscan link + 签名 API 示例

  • 可部署到 Vercel / Netlify

  • 项目结构完整,可作为 portfolio、求职项目使用


✅ 本章小结

你已完成一次完整的 DApp 构建流程:

  1. 合约:设计安全、权限清晰、签名验证机制

  2. 前端:钱包集成、合约调用、签名交互

  3. 后端:结构化签名服务、安全校验

  4. 部署:Etherscan 验证、测试网联调、上线发布


✅ 下一章预告|第 12 章(番外):最新安全趋势 × 审计生态 × 职业路径规划

EIP 演进中有哪些与安全强相关的提案?
审计公司如何定级合约风险?
审计赏金平台 × Web3 安全岗位 × 面试准备指南

你可能感兴趣的:(web3安全审计,Solidity,安全硬核教程,前端,安全,智能合约,区块链,solidity,web3)