Merkl Protocol 是一个 DeFi 奖励分发协议,通过链上合约 + 链下计算引擎 + Merkle 树验证,实现项目方自助创建奖励活动、用户一键领取多协议奖励。
Bonus 类似于 Web2 的大促打折,通过额外奖励吸引用户参与 DeFi 协议。对于小众协议尤其重要——用户不了解也没有意愿参与,Bonus 是引流的有效手段。
| 维度 | Bonus 2.0 | Bonus 3.0(Merkl) |
|---|---|---|
| 技术架构 | 链上金库 + 链下计算 | 链上金库 + 链下计算 |
| 领取方式 | 用户主动 claim | 用户主动 claim |
| 活动创建 | 手动申请 → 部署合约 → 配置活动 | 项目方一键创建 |
| 领取验证 | 私钥验签 | Merkle 树验证 |
| 领取范围 | 一次只能领单一协议 | 一次领取一条链上所有奖励 |
Merkl Platform 面向两类用户:
只向项目方收费,用户不收费(领取时需自付 Gas):
| 链 | 排名 | 累计发放金额 |
|---|---|---|
| Arbitrum | 1 | $15.32M |
| Ethereum | 2 | $14.47M |
| Polygon | 3 | $5.098M |
| Optimism | 4 | $2.776M |
| Immutable zkEVM | 5 | $1.395M |
| Base | 6 | $1.314M |
| Blast | 7 | $713.7K |
| Manta Pacific | 8 | $504.5K |
| Mode | 9 | $334.8K |
| Polygon zkEVM | 10 | $246.1K |
Dune 数据看板:https://dune.com/clarkcui/merkl-angle
Merkl Engine 统计和计算收益的时间周期,一般 3-12 小时(不同链可能不同)。这决定了用户每天最多可以领取几次新奖励。例如 8 小时周期意味着每天最多 3 次。
新 Merkle Root 生成后的 1-2 小时窗口期。在此期间:
任何人都可以运行争议机器人:https://github.com/AngleProtocol/merkl-dispute
活动数据结构:
struct CampaignParameters {
bytes32 campaignId;
address creator;
address rewardToken;
uint256 amount;
uint32 campaignType;
uint32 startTimestamp;
uint32 duration;
bytes campaignData;
}创建活动时自动扣除手续费,将奖励资金转入 Distributor 合约,并将活动信息存入 campaignList。
1. Merkle Root 存储与更新
MerkleTree public tree; // 当前 Merkle Root
MerkleTree public lastTree; // 上一个 Merkle Root
function updateTree(MerkleTree calldata _tree) external {
// 检查:无争议 & 有更新权限 & 争议期已过
MerkleTree memory _lastTree = tree;
tree = _tree;
lastTree = _lastTree;
}合约只存储 Merkle Root(而非整棵树),因为验证只需要 Root + Proof 路径即可,完整的树数据存储在链下。
2. Merkle 证明验证
function getMerkleRoot() public view returns (bytes32) {
// 争议期内返回上一个有效 Root,争议期后返回最新 Root
if (block.timestamp >= endOfDisputePeriod && disputer == address(0))
return tree.merkleRoot;
else
return lastTree.merkleRoot;
}
function _verifyProof(bytes32 leaf, bytes32[] memory proof) internal view returns (bool) {
bytes32 currentHash = leaf;
for (uint256 i; i < proof.length; ) {
// 从叶子节点逐层向上计算 Root
if (currentHash < proof[i]) {
currentHash = keccak256(abi.encode(currentHash, proof[i]));
} else {
currentHash = keccak256(abi.encode(proof[i], currentHash));
}
unchecked { ++i; }
}
return currentHash == getMerkleRoot(); // 与链上存储的 Root 比对
}3. 用户 Claim
function claim(
address[] calldata users,
address[] calldata tokens,
uint256[] calldata amounts,
bytes32[][] calldata proofs
) external {
for (uint256 i; i < users.length; ) {
// 权限检查:只有用户自己或被授权者可以 claim
// 构造叶子节点:leaf = keccak256(user, token, amount)
// 验证 Merkle 证明
// 计算应发金额 = 累计金额 - 已领金额
// 转账代币给用户
unchecked { ++i; }
}
}Claim 的最小维度是币种(token)级别——用户可以一次性领取同一条链上所有协议的所有币种奖励。
getMerkleRoot() 在争议期内返回的是 lastTree.merkleRoot(上一个已验证的 Root)。新周期的奖励数据只存在于新 Root 的 Merkle 树中,用旧 Root 验证新奖励的 Proof 会失败,所以争议期内新奖励无法被领取。
通过 Merkl Public API 集成,前端可以独立构建 claim 页面:
GET https://api.merkl.xyz/v3/userRewards?user={address}&chainId={chainId}&proof=true返回数据包含每个代币的累计奖励、未领取金额和 Merkle Proof:
{
"0x代币地址": {
"accumulated": "1474003470054576877800",
"symbol": "ANGLE",
"unclaimed": "1474003470054576877800",
"reasons": {
"Arrakis": { "accumulated": "996...", "unclaimed": "996..." },
"UniswapV3": { "accumulated": "349...", "unclaimed": "349..." }
},
"proof": ["0xf1c7...", "0x7192...", "..."]
}
}import { Distributor__factory } from "@angleprotocol/sdk";
import axios from "axios";
export const claim = async (chainId, signer) => {
// 1. 从 API 获取用户奖励数据(含 proof)
const { data } = await axios.get(
`https://api.merkl.xyz/v3/userRewards?chainId=${chainId}&user=${signer._address}&proof=true`
);
// 2. 筛选有 proof 的代币
const tokens = Object.keys(data).filter(k => data[k].proof?.length > 0);
const claims = tokens.map(t => data[t].accumulated);
const proofs = tokens.map(t => data[t].proof);
// 3. 调用 Distributor 合约的 claim 方法
const contract = Distributor__factory.connect(distributorAddress, signer);
await (await contract.claim(
tokens.map(() => signer._address), // users
tokens, // tokens
claims, // amounts
proofs // proofs
)).wait();
};