前言在2026年初的Web3领域,DePIN(去中心化物理基础设施网络)已成为最具增长潜力的赛道之一。作为其中的领军项目,由Andrena团队开发的DAWN(DecentralizedAutonomousWirelessNetwork)凭借其“去中心化宽带”的愿景和
在 2026 年初的 Web3 领域,DePIN(去中心化物理基础设施网络) 已成为最具增长潜力的赛道之一。作为其中的领军项目,由 Andrena 团队开发的 DAWN (Decentralized Autonomous Wireless Network) 凭借其 “去中心化宽带” 的愿景和超过 4850 万美元的融资背景(Polychain 领投),吸引了全球开发者的关注。本文将从项目核心逻辑出发,只做项目核心运行逻辑拆解,不做项目投资建议,演示如何使用 Solidity 0.8.24 与 OpenZeppelin V5 实现其激励机制,并分享在使用 Viem + Hardhat 进行自动化测试时的避坑指南。
一、 DAWN 的业务本质:带宽即服务
与 Grass 等数据爬虫类 DePIN 不同,DAWN 的目标是成为去中心化 ISP(互联网服务供应商) 。
基于安全性与 Gas 效率的考虑,DAWN 采用了增量领取机制。合约不记录单次奖励,而是记录用户的“生涯总领金额”,通过差额发放代币。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
/**
* @title DawnNetworkToken
* @dev 模拟 DAWN DePIN 激励合约
*/
contract DawnNetworkToken is ERC20, Ownable, EIP712 {
using ECDSA for bytes32;
address public validator; // 官方验证者地址
mapping(address => uint256) public totalClaimed;
bytes32 private constant CLAIM_TYPEHASH = keccak256(
"Claim(address user,uint256 totalAmount)"
);
constructor(address _validator)
ERC20("DAWN Network", "DAWN")
Ownable(msg.sender)
EIP712("DAWN_Network", "1")
{
validator = _validator;
}
// 设置验证节点地址
function setValidator(address _validator) external onlyOwner {
validator = _validator;
}
/**
* @notice 领取带宽奖励
* @param totalAmount 验证者核实的累计奖励总额
* @param signature 验证者的加密签名
*/
function claimRewards(uint256 totalAmount, bytes calldata signature) external {
uint256 claimable = totalAmount - totalClaimed[msg.sender];
require(claimable > 0, "No new rewards");
// 验证 EIP-712 签名
bytes32 structHash = keccak256(abi.encode(CLAIM_TYPEHASH, msg.sender, totalAmount));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, signature);
require(signer == validator, "Invalid validator signature");
totalClaimed[msg.sender] = totalAmount;
_mint(msg.sender, claimable);
}
}
测试用例:DAWN DePIN 激励合约深度测试
import assert from "node:assert/strict";
import { describe, it, beforeEach } from "node:test";
import { network } from "hardhat";
import { parseEther, type Address, getAddress } from "viem";describe("DAWN DePIN 激励合约深度测试", function () { let token: any, dawnDistributor: any; let admin: any, user: any, validator: any; let vClient: any, pClient: any;
// EIP-712 类型定义常量化
const DOMAIN_NAME = "DAWN_Network";
const VERSION = "1";
beforeEach(async function () {
const { viem } = await (network as any).connect();
vClient = viem;
[admin, user, validator] = await vClient.getWalletClients();
pClient = await vClient.getPublicClient();
// 部署 DAWN 代币与分配器(此处假设为统一合约)
dawnDistributor = await vClient.deployContract("DawnNetworkToken", [
validator.account.address
]);
});
it("成功逻辑:验证有效签名并更新领取额度", async function () {
const totalAmount = parseEther("1000"); // 验证者核实的用户生涯总收益
const chainId = await pClient.getChainId();
const domain = {
name: DOMAIN_NAME,
version: VERSION,
chainId,
verifyingContract: dawnDistributor.address as Address,
} as const;
const types = {
Claim: [
{ name: 'user', type: 'address' },
{ name: 'totalAmount', type: 'uint256' },
],
} as const;
// 验证者签名
const signature = await validator.signTypedData({
domain,
types,
primaryType: 'Claim',
message: {
user: user.account.address,
totalAmount: totalAmount,
},
});
// 执行领取
const hash = await dawnDistributor.write.claimRewards([totalAmount, signature], {
account: user.account
});
await pClient.waitForTransactionReceipt({ hash });
// 验证余额
const balance = await dawnDistributor.read.balanceOf([user.account.address]);
assert.strictEqual(balance, totalAmount, "用户应获得全部初始奖励");
// 验证累计领取记录
const claimed = await dawnDistributor.read.totalClaimed([user.account.address]);
assert.strictEqual(claimed, totalAmount, "合约应记录已领取总额");
});
it("重放攻击测试:禁止使用同一签名重复领取", async function () { const totalAmount = parseEther("100"); const chainId = await pClient.getChainId();
const domain = { name: "DAWN_Network", version: "1", chainId, verifyingContract: dawnDistributor.address };
const types = { Claim: [{ name: 'user', type: 'address' }, { name: 'totalAmount', type: 'uint256' }] };
const signature = await validator.signTypedData({
domain,
types,
primaryType: 'Claim',
message: { user: user.account.address, totalAmount }
});
// 1. 第一次领取:应当成功
const hash = await dawnDistributor.write.claimRewards([totalAmount, signature], { account: user.account });
await pClient.waitForTransactionReceipt({ hash });
// 2. 第二次领取:使用 simulateContract 捕获具体的合约错误
await assert.rejects(
async () => {
// simulateContract 会执行 eth_call,比直接 write 更容易拿到 revert reason
await pClient.simulateContract({
address: dawnDistributor.address,
abi: dawnDistributor.abi,
functionName: 'claimRewards',
args: [totalAmount, signature],
account: user.account,
});
},
(err: any) => {
// 打印错误以防万一
// console.log("Caught Error:", err.message);
// 检查错误信息。在 simulate 模式下,viem 通常能抓到 "No new rewards"
// 或者检查是否包含 "reverted" 关键字
return err.message.includes("No new rewards") || err.message.includes("revert");
},
"应当因为没有新奖励额度而回滚"
);
});
it("安全边界:修改金额后原签名应失效", async function () {
const totalAmount = parseEther("100");
const tamperedAmount = parseEther("200"); // 尝试篡改金额
const chainId = await pClient.getChainId();
const signature = await validator.signTypedData({
domain: { name: DOMAIN_NAME, version: VERSION, chainId, verifyingContract: dawnDistributor.address },
types: { Claim: [{ name: 'user', type: 'address' }, { name: 'totalAmount', type: 'uint256' }] },
primaryType: 'Claim',
message: { user: user.account.address, totalAmount }
});
await assert.rejects(
async () => {
// 提交被篡改的金额 tamperedAmount,但使用 totalAmount 的签名
await dawnDistributor.write.claimRewards([tamperedAmount, signature], { account: user.account });
},
/Invalid validator signature/,
"签名恢复的地址应与验证者不符"
);
});
});
# 四、部署脚本
// scripts/deploy.js import { network, artifacts } from "hardhat"; async function main() { // 连接网络 const { viem } = await network.connect({ network: network.name });//指定网络进行链接
// 获取客户端 const [deployer] = await viem.getWalletClients(); const publicClient = await viem.getPublicClient();
const deployerAddress = deployer.account.address; console.log("部署者的地址:", deployerAddress); // 加载合约 const DawnNetworkArtifact = await artifacts.readArtifact("DawnNetworkToken");
// 部署(构造函数参数:recipient, initialOwner) const DawnNetworkHash = await deployer.deployContract({ abi: DawnNetworkArtifact.abi,//获取abi bytecode: DawnNetworkArtifact.bytecode,//硬编码 args: [deployerAddress],//process.env.RECIPIENT, process.env.OWNER });
// 等待确认并打印地址 const DawnNetworkReceipt = await publicClient.waitForTransactionReceipt({ hash:DawnNetworkHash }); console.log("合约地址:", DawnNetworkReceipt.contractAddress);
}
main().catch(console.error);
# 五、DAWN 与 Grass 的横向对比
| 维度 | Grass (小草) | DAWN (黎明) |
| -------- | ------------- | ----------------- |
| **底层链** | Solana | Solana |
| **激励方式** | 带宽挖矿积分 (早期) | 带宽挖矿积分 (当前) |
| **技术核心** | 数据采集与 AI 模型对齐 | 去中心化无线网状网络 (Mesh) |
| **合约挑战** | 高频、小额奖励发放 | 大规模节点的身份校验与带宽证明 |
* * *
# 六、DAWN 项目的风险提示
1. **隐私与安全风险**:长期运行浏览器插件可能导致个人上网行为被追踪,或因插件漏洞造成数据泄露。
1. **违约封号风险**:家用宽带协议通常禁止“带宽转售”,运行该项目可能触发运营商(如电信、联通)的监测,导致宽带被限速或停机。
1. **收益不确定性**:项目融资虽高,但发币时间(TGE)未知。如果后期参与人数爆棚或币价破发,挂机累积的积分可能并不值钱。
1. **落地难度大**:DAWN 最终需要用户安装物理基站(硬件),这比纯软件挂机难得多。如果硬件普及失败,项目愿景可能无法实现。
**一句话建议**:用**闲置设备**“零成本”参与,不要为了它专门升级宽带或购买贵重硬件。
# 七、 结语
DAWN 不仅仅是一个挂机项目,它是对传统中心化电信运营商(ISP)的有力挑战。对于开发者而言,理解其底层的 **EIP-712 签名机制** 和 **增量领取逻辑**,是构建健壮 DePIN 应用的基础。随着 2026 年主网上线的临近,这类结合了硬件贡献与链上激励的模型将成为 Web3 落地的重要标杆。 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!