Skip to content

调研:Merkl Protocol

Merkl Protocol 是一个 DeFi 奖励分发协议,通过链上合约 + 链下计算引擎 + Merkle 树验证,实现项目方自助创建奖励活动、用户一键领取多协议奖励。


调研动机

  1. DeFi 业务有接入 Merkl Protocol 的计划
  2. Merkl Protocol 的技术架构有创新性,值得学习参考
  3. 当前 DeFi Bonus 活动创建仍需后端和合约的手动操作,希望效仿 Merkl 实现自动化

背景:为什么需要 Bonus

Bonus 类似于 Web2 的大促打折,通过额外奖励吸引用户参与 DeFi 协议。对于小众协议尤其重要——用户不了解也没有意愿参与,Bonus 是引流的有效手段。

DeFi Bonus 演进

维度Bonus 2.0Bonus 3.0(Merkl)
技术架构链上金库 + 链下计算链上金库 + 链下计算
领取方式用户主动 claim用户主动 claim
活动创建手动申请 → 部署合约 → 配置活动项目方一键创建
领取验证私钥验签Merkle 树验证
领取范围一次只能领单一协议一次领取一条链上所有奖励

产品功能

Merkl Platform 面向两类用户:

graph TB
    subgraph 项目方
        C1["Create 页面"] --> C2["选择活动类型<br/>设置奖励参数<br/>存入奖励资金"]
    end

    subgraph 普通用户
        U1["Opportunities 页面<br/>发现感兴趣的活动"]
        U2["Dashboard 页面<br/>查看奖励 & 一键领取"]
    end

    C2 -->|"活动上线"| U1
    U1 -->|"参与活动"| U2

支持的活动类型

  • Concentrated Liquidity Pool (CLAMM) 激励活动
  • Airdrop 空投活动
  • ERC20 代币激励活动(包含 LP Token、借贷代币等)
  • Silo 和 Radiant 借贷激励
  • Token Snapshot 快照活动

收费策略

只向项目方收费,用户不收费(领取时需自付 Gas):

  • 按量付费:活动抽成 3%,空投仅 0.5%
  • 订阅制:年奖励超 $1M 的项目可获定制方案和折扣

市场数据

  • 上线时间:2023 年 3 月
  • 每周分发奖励约 $610k
  • 活动主要集中在 ArbitrumEthereum 链上
  • 80% 以上的活动为流动性池类型

各链奖励发放排名

排名累计发放金额
Arbitrum1$15.32M
Ethereum2$14.47M
Polygon3$5.098M
Optimism4$2.776M
Immutable zkEVM5$1.395M
Base6$1.314M
Blast7$713.7K
Manta Pacific8$504.5K
Mode9$334.8K
Polygon zkEVM10$246.1K

Dune 数据看板:https://dune.com/clarkcui/merkl-angle


技术原理

整体架构

graph TB
    subgraph 链上合约
        CREATOR["Creator Contract<br/>创建活动 & 存储活动信息"]
        DIST["Distributor Contract<br/>存储奖励资金 & Merkle Root<br/>用户 claim 交互"]
        ACM["AccessControlManager<br/>多签管理合约"]
    end

    subgraph 链下服务
        ENGINE["Merkl Engine<br/>(非公开)<br/>计算奖励 & 生成 Merkle Root"]
        API["Merkl Public API<br/>(公开)<br/>供前端查询奖励和 Proof"]
        BOT["Dispute Bot<br/>验证 Merkle Root 正确性"]
        BUCKET["Merkl Rewards Bucket<br/>存储历史 Merkle Root"]
    end

    CREATOR -->|"活动信息"| ENGINE
    ENGINE -->|"查询链上数据"| RPC["RPC / Subgraph"]
    ENGINE -->|"提交 Merkle Root"| DIST
    ENGINE -->|"存储历史"| BUCKET
    API -->|"读取"| ENGINE
    BOT -->|"验证"| BUCKET
    BOT -->|"发现错误则回滚"| DIST

完整生命周期

sequenceDiagram
    participant Project as 项目方
    participant Creator as Creator 合约
    participant Engine as Merkl Engine
    participant Chain as 链上数据
    participant Dist as Distributor 合约
    participant Bot as Dispute Bot
    participant User as 用户

    Project->>Creator: 创建活动 & 存入奖励

    Note over Engine: 每 3-12 小时执行一次
    Engine->>Creator: 获取活动信息
    Engine->>Chain: 查询 LP 持仓等数据
    Engine->>Engine: 计算每个地址的奖励
    Engine->>Engine: 聚合生成 Merkle Root
    Engine->>Dist: 提交新的 Merkle Root

    Note over Bot: 争议期 1-2 小时
    Bot->>Bot: 验证 Merkle Root 正确性
    alt 发现错误
        Bot->>Dist: 回滚到上一个 Merkle Root
    end

    Note over User: 争议期结束后
    User->>Dist: 调用 claim 方法
    Dist->>Dist: Merkle 证明验证
    Dist->>User: 发送奖励代币 💰

关键机制

Distribution Epochs(分发时期)

Merkl Engine 统计和计算收益的时间周期,一般 3-12 小时(不同链可能不同)。这决定了用户每天最多可以领取几次新奖励。例如 8 小时周期意味着每天最多 3 次。

Dispute Periods(争议期)

新 Merkle Root 生成后的 1-2 小时窗口期。在此期间:

  • 用户 claim 会失败(返回上一个有效的 Merkle Root)
  • Dispute Bot 验证新 Root 的正确性
  • 发现错误可使其无效并回滚

任何人都可以运行争议机器人:https://github.com/AngleProtocol/merkl-dispute

每条链只有 3 个合约

  • Creator:项目方创建活动,保存活动信息
  • Distributor:存储奖励资金和 Merkle Root,供用户 claim
  • AccessControlManager:2-3 多签管理合约

合约代码分析

Creator 合约

活动数据结构:

solidity
struct CampaignParameters {
    bytes32 campaignId;
    address creator;
    address rewardToken;
    uint256 amount;
    uint32 campaignType;
    uint32 startTimestamp;
    uint32 duration;
    bytes campaignData;
}

创建活动时自动扣除手续费,将奖励资金转入 Distributor 合约,并将活动信息存入 campaignList

Distributor 合约

1. Merkle Root 存储与更新

solidity
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 证明验证

solidity
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

solidity
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)级别——用户可以一次性领取同一条链上所有协议的所有币种奖励。

争议期内 claim 如何被阻止

getMerkleRoot() 在争议期内返回的是 lastTree.merkleRoot(上一个已验证的 Root)。新周期的奖励数据只存在于新 Root 的 Merkle 树中,用旧 Root 验证新奖励的 Proof 会失败,所以争议期内新奖励无法被领取。

Merkle 验证流程图

graph TB
    LEAF["叶子节点<br/>keccak256(user, token, amount)"]
    LEAF -->|"+ proof[0]"| N1["中间节点 1"]
    N1 -->|"+ proof[1]"| N2["中间节点 2"]
    N2 -->|"+ proof[n]"| ROOT["计算得到的 Root"]

    STORED["链上存储的<br/>Merkle Root"]

    ROOT -->|"比对"| CHECK{"相等?"}
    STORED --> CHECK
    CHECK -->|"Yes"| OK["✅ 验证通过<br/>发放奖励"]
    CHECK -->|"No"| FAIL["❌ 验证失败<br/>拒绝领取"]

集成方式

通过 Merkl Public API 集成,前端可以独立构建 claim 页面:

API 调用示例

GET https://api.merkl.xyz/v3/userRewards?user={address}&chainId={chainId}&proof=true

返回数据包含每个代币的累计奖励、未领取金额和 Merkle Proof:

json
{
  "0x代币地址": {
    "accumulated": "1474003470054576877800",
    "symbol": "ANGLE",
    "unclaimed": "1474003470054576877800",
    "reasons": {
      "Arrakis": { "accumulated": "996...", "unclaimed": "996..." },
      "UniswapV3": { "accumulated": "349...", "unclaimed": "349..." }
    },
    "proof": ["0xf1c7...", "0x7192...", "..."]
  }
}

前端 Claim 代码

javascript
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();
};

总结

优势

  • 项目方自助创建活动,无需人工操作,节省人力和合规风险
  • 用户一键领取所有奖励,无需辗转各协议页面,节省 Gas
  • Merkle 树验证机制安全可靠,争议期 + Dispute Bot 提供额外保障

不足

  • 核心 Merkl Engine 不开源,存在中心化风险
  • Public API 无需鉴权,容易被攻击(曾出现过 API 故障)
  • 奖励计算依赖链下服务,若引擎出错可能导致奖励分配不准确

参考资料