基于动态 CAP 管理的 GToken 挖矿分配系统,通过 DAO 委员会治理,实现对基础设施节点贡献的公平奖励。
// GToken.sol 已实现
uint256 public constant MAX_SUPPLY = 21_000_000 ether;
function remainingMintableSupply() external view returns (uint256) {
return cap() - totalSupply();
}关键特性:
- ✅ 硬顶 2100万 GToken
- ✅ burn() 自动创造铸造空间
- ✅ 无需额外存储变量
- ✅ totalSupply() 是唯一真理源
// 只要 remainingMintableSupply > 0, 即可 mint
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount); // ERC20Capped 自动检查 cap
}无需新增接口:
- ERC20Capped 已内置 cap 检查
- 只需查询
remainingMintableSupply()
| 类别 | 描述 | 权重基数 | 示例 |
|---|---|---|---|
| Validator | 出块验证节点 | 10 | 验证交易、生成区块 |
| Indexer | 数据索引节点 | 7 | 提供 GraphQL API、历史数据查询 |
| RPC Provider | RPC 服务节点 | 8 | eth_call, eth_sendTransaction |
| Bundler | UserOp 打包节点 | 9 | AA 交易打包、Gas 优化 |
| Paymaster | Gas 赞助节点 | 6 | SuperPaymaster Operator |
| Storage | 存储节点 | 5 | IPFS, Arweave 网关 |
| Relay | 中继节点 | 4 | MEV 保护、隐私交易 |
贡献分 = 出块数量 × 10
+ 验证成功率 × 5
+ 在线时长(小时) × 0.01
贡献分 = 请求响应数量 / 1000 × 8
+ 平均响应时间(ms) × (-0.01) // 越快分数越高
+ 正常运行时间(%) × 2
贡献分 = UserOp 打包数量 / 100 × 9
+ Gas 节省率(%) × 5
+ 提交成功率 × 3
贡献分 = 赞助交易数量 / 50 × 6
+ 赞助金额(ETH) × 10
+ xPNTs 销毁量 × 0.5
链上数据:
- 区块链事件日志(Block Produced, UserOp Executed)
- 合约调用记录(Paymaster Deposits, Slash Events)
链下数据(经 Oracle 验证):
- RPC 请求日志 → Chainlink Oracle
- 节点在线时长 → 多签验证者签名
- 响应时间统计 → TEE 可信计算
建议: 每季度(Epoch)进行一次分配
uint256 public constant EPOCH_DURATION = 90 days;
uint256 public lastMintEpoch;
modifier onlyNewEpoch() {
require(block.timestamp >= lastMintEpoch + EPOCH_DURATION, "Epoch not ready");
_;
}DAO 委员会投票决定: 当前 Epoch 使用 remainingMintableSupply 的百分比
struct EpochAllocation {
uint256 epochId;
uint256 totalAvailable; // remainingMintableSupply 快照
uint256 allocationPercent; // DAO 投票决定 (basis points, 2000 = 20%)
uint256 actualMinted;
mapping(address => uint256) nodeRewards;
}示例场景:
- Epoch 1:
remainingMintableSupply= 5,000,000 - DAO 投票: 使用 20% (1,000,000 GToken)
- 剩余: 4,000,000 继续留存
1. Oracle 提交链下数据 → Merkle Root
2. DAO 委员会审核 → 投票
3. 投票通过 → 执行链上分配
4. 节点领取 → Merkle Proof 验证
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "./GToken.sol";
/**
* @title MiningDistributor
* @notice DAO-governed mining reward distribution for infrastructure nodes
* @dev Uses Merkle Tree for efficient reward claiming
*/
contract MiningDistributor is AccessControl {
bytes32 public constant DAO_ROLE = keccak256("DAO_ROLE");
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
GToken public immutable gToken;
uint256 public currentEpoch;
uint256 public constant EPOCH_DURATION = 90 days;
uint256 public epochStartTime;
struct Epoch {
bytes32 merkleRoot; // 节点奖励 Merkle 树根
uint256 totalAllocated; // 本 Epoch 总分配量
uint256 allocationPercent; // 使用百分比 (basis points)
uint256 mintedAmount; // 已铸造总量
mapping(address => bool) claimed; // 领取记录
}
mapping(uint256 => Epoch) public epochs;
event EpochProposed(uint256 indexed epochId, bytes32 merkleRoot, uint256 allocation);
event EpochApproved(uint256 indexed epochId, uint256 totalMinted);
event RewardClaimed(uint256 indexed epochId, address indexed node, uint256 amount);
constructor(address _gToken, address _daoAdmin) {
gToken = GToken(_gToken);
_grantRole(DEFAULT_ADMIN_ROLE, _daoAdmin);
_grantRole(DAO_ROLE, _daoAdmin);
epochStartTime = block.timestamp;
}
/**
* @notice DAO 提案新 Epoch 的分配方案
* @param merkleRoot 节点奖励 Merkle 树根
* @param allocationPercent 使用 remainingMintableSupply 的百分比 (basis points)
*/
function proposeEpoch(
bytes32 merkleRoot,
uint256 allocationPercent
) external onlyRole(DAO_ROLE) {
require(block.timestamp >= epochStartTime + EPOCH_DURATION, "Epoch not ready");
require(allocationPercent <= 5000, "Max 50% per epoch"); // 安全上限
uint256 newEpochId = currentEpoch + 1;
uint256 available = gToken.remainingMintableSupply();
uint256 allocation = available * allocationPercent / 10000;
epochs[newEpochId].merkleRoot = merkleRoot;
epochs[newEpochId].allocationPercent = allocationPercent;
epochs[newEpochId].totalAllocated = allocation;
emit EpochProposed(newEpochId, merkleRoot, allocation);
}
/**
* @notice DAO 批准并执行分配(进入新 Epoch)
*/
function approveEpoch() external onlyRole(DAO_ROLE) {
uint256 newEpochId = currentEpoch + 1;
require(epochs[newEpochId].merkleRoot != bytes32(0), "No proposal");
currentEpoch = newEpochId;
epochStartTime = block.timestamp;
emit EpochApproved(newEpochId, epochs[newEpochId].totalAllocated);
}
/**
* @notice 节点领取奖励
* @param epochId Epoch ID
* @param amount 奖励数量
* @param proof Merkle 证明
*/
function claimReward(
uint256 epochId,
uint256 amount,
bytes32[] calldata proof
) external {
require(epochId <= currentEpoch, "Future epoch");
require(!epochs[epochId].claimed[msg.sender], "Already claimed");
// 验证 Merkle Proof
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
require(
MerkleProof.verify(proof, epochs[epochId].merkleRoot, leaf),
"Invalid proof"
);
epochs[epochId].claimed[msg.sender] = true;
epochs[epochId].mintedAmount += amount;
// 铸造奖励(GToken owner 需授权此合约)
gToken.mint(msg.sender, amount);
emit RewardClaimed(epochId, msg.sender, amount);
}
/**
* @notice 查询节点在指定 Epoch 的奖励领取状态
*/
function hasClaimed(uint256 epochId, address node) external view returns (bool) {
return epochs[epochId].claimed[node];
}
/**
* @notice 获取当前可分配总量
*/
function getAvailableForMinting() external view returns (uint256) {
return gToken.remainingMintableSupply();
}
}输入:
- Oracle 提交链下数据汇总(Merkle Root)
- 包含所有符合条件节点的贡献分及奖励金额
DAO 审核:
- 验证 Oracle 数据真实性
- 检查分配比例合理性
- 投票决定
allocationPercent
// 简单多签 DAO (可升级为 Governor)
contract SimpleDAO {
uint256 public constant QUORUM = 3; // 最少投票数
uint256 public constant THRESHOLD = 2; // 通过阈值
mapping(bytes32 => uint256) public proposalVotes;
mapping(bytes32 => mapping(address => bool)) public hasVoted;
function vote(bytes32 proposalId) external onlyMember {
require(!hasVoted[proposalId][msg.sender], "Already voted");
hasVoted[proposalId][msg.sender] = true;
proposalVotes[proposalId]++;
if (proposalVotes[proposalId] >= THRESHOLD) {
// 执行提案
distributor.approveEpoch();
}
}
}- 批准执行: DAO 调用
approveEpoch() - 节点领取: 节点自行调用
claimReward()+ Merkle Proof - Gas 优化: 只需链上存储 Merkle Root,节点数据存储在 IPFS
interface NodeReward {
address: string;
contributionScore: number;
rewardAmount: bigint;
category: string;
}
const nodeRewards: NodeReward[] = [
{
address: "0xValidator1...",
contributionScore: 1250,
rewardAmount: parseEther("50000"),
category: "Validator"
},
{
address: "0xRPC_Provider...",
contributionScore: 890,
rewardAmount: parseEther("35000"),
category: "RPC Provider"
},
// ...
];import { MerkleTree } from 'merkletreejs';
import { keccak256, solidityPacked } from 'ethers';
function generateMerkleTree(rewards: NodeReward[]): { root: string, tree: MerkleTree } {
const leaves = rewards.map(r =>
keccak256(solidityPacked(['address', 'uint256'], [r.address, r.rewardAmount]))
);
const tree = new MerkleTree(leaves, keccak256, { sortPairs: true });
const root = tree.getHexRoot();
return { root, tree };
}
function getProof(tree: MerkleTree, address: string, amount: bigint): string[] {
const leaf = keccak256(solidityPacked(['address', 'uint256'], [address, amount]));
return tree.getHexProof(leaf);
}// 将完整奖励数据发布到 IPFS
const ipfsHash = await ipfs.add(JSON.stringify({
epochId: 5,
merkleRoot: root,
totalAllocated: parseEther("1000000"),
rewards: nodeRewards,
timestamp: Date.now()
}));
// DAO 链上只需存储
distributor.proposeEpoch(root, 2000); // 使用20%- GToken v2.1.0 (已完成)
- GTokenStaking v3.1.0 (已完成)
- MiningDistributor v1.0.0
- SimpleDAO / Governor
- Chainlink Oracle 适配器
- 链下数据聚合脚本
- Merkle Tree 生成工具
- Testnet 部署
- 首个 Epoch 模拟投票
- 节点领取流程验证
- 安全审计
- 主网部署
- 社区公告与教育
单 Epoch 上限:
require(allocationPercent <= 5000, "Max 50% per epoch");时间锁:
uint256 public constant PROPOSAL_DELAY = 3 days;紧急暂停:
bool public paused;
modifier whenNotPaused() {
require(!paused, "Paused");
_;
}多 Oracle 验证:
mapping(bytes32 => uint256) public oracleConfirmations;
uint256 public constant REQUIRED_CONFIRMATIONS = 3;数据时效性:
require(block.timestamp - dataTimestamp < 1 days, "Stale data");A: 无需追踪!totalSupply() 已实时更新:
// 每次 burn 调用时
function _burn(address account, uint256 amount) internal virtual {
_totalSupply -= amount; // 自动减少
// ...
}
// 查询时
function remainingMintableSupply() external view returns (uint256) {
return cap() - totalSupply(); // 实时计算
}A: 不可以! ERC20Capped._mint() 强制检查:
function _mint(address account, uint256 amount) internal virtual {
require(totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded");
super._mint(account, amount);
}A: 引入"质押乘数":
贡献分 = 基础分 × (1 + stakingDuration / 365 days × 0.5)运行满1年的节点,贡献分增加50%。
✅ 已实现:
- GToken v2.1.0: Burnable + remainingMintableSupply
- GTokenStaking v3.1.0: True Burn
🔄 待实施:
- MiningDistributor 合约
- DAO 治理合约
- Oracle 数据聚合
📊 核心优势:
- 透明: totalSupply = 唯一真理
- 灵活: DAO 动态调整分配比例
- 高效: Merkle Tree 节省 Gas
- 公平: 基于量化贡献的分配
Note
这是一个完全链上治理的分配系统,无需信任中心化实体,所有分配决策由 DAO 投票决定。