你已经掌握了:
合约开发的核心语法
各类典型攻击原理与防御方式
工具链的使用与审计流程
部署、验证、前端对接的安全规范
是时候,真正构建一个完整的 DApp 项目。
本章将带你做一件事:
从零搭建一套功能明确、安全设计清晰、部署可验证的智能合约项目。
用户可以:
使用钱包连接
领取每周一次的奖励(带 claim 签名 + 防 bot)
所有操作前端可视化交互
合约使用权限控制、结构化签名校验
项目可部署到 Sepolia 测试网 + 验证
模块 | 技术栈 | 说明 |
---|---|---|
合约 | Solidity + Hardhat | 奖励领取逻辑 / 权限控制 / 防多次 claim |
安全审计工具链 | Slither + Foundry Test + Verify | 全面测试 + 安全检查 |
前端交互 | React + Ethers.js | 钱包连接 + 签名授权 + 调用合约 |
签名生成(后端) | Node.js + express / NestJS | 验证钱包 + 生成 EIP-712 签名 |
部署验证 | Etherscan / Hardhat Verify | 自动部署脚本 + 主网/测试网自动验证 |
// 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 {}
}
import { ethers } from "ethers";
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
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);
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 });
});
function testFail_claimTwice() public {
// 调用 claim 一次成功,再次 claim 应失败
}
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 二层过滤 |
提交 GitHub:分 contracts/
frontend/
scripts/
docs/
添加 README + 部署地址 + Etherscan link + 签名 API 示例
可部署到 Vercel / Netlify
项目结构完整,可作为 portfolio、求职项目使用
你已完成一次完整的 DApp 构建流程:
合约:设计安全、权限清晰、签名验证机制
前端:钱包集成、合约调用、签名交互
后端:结构化签名服务、安全校验
部署:Etherscan 验证、测试网联调、上线发布
EIP 演进中有哪些与安全强相关的提案?
审计公司如何定级合约风险?
审计赏金平台 × Web3 安全岗位 × 面试准备指南