空投合约和链下服务可以配合实现高效的代币分发,具体如下:
1. 架构设计
- 链下服务
- 准备空投名单,包括每个用户的地址和分发的代币数量
- 生成 Merkle 树和对应的 Merkle 根
- 为每个用户生成 Merkle 证明路径
- 将 Merkle 根部署到链上,用于空投验证。
- 链上合约
- 存储 Merkle 根
- 提供领取空投的接口,验证用户提交的地址、金额和 Merkle 证明是否有效
- 记录已领取的地址,防止重复领取
2. 代码实现
2.1 链下服务
- Python 示例:生成 Merkle 树和证明
import hashlib
def hash(data):
"""计算数据的哈希值"""
return hashlib.sha256(data.encode('utf-8')).hexdigest()
def build_merkle_tree(data_list):
"""构建 Merkle 树"""
if len(data_list) % 2 != 0:
data_list.append(data_list[-1]) # 如果节点数是奇数,则复制最后一个节点
tree = [data_list] # 第一层是叶节点
while len(tree[-1]) > 1:
level = []
for i in range(0, len(tree[-1]), 2):
combined = hash(tree[-1][i] + tree[-1][i + 1])
level.append(combined)
tree.append(level)
return tree
def get_merkle_root(tree):
"""获取 Merkle 树根"""
return tree[-1][0]
def get_proof(tree, index):
"""生成某个节点的 Merkle 证明"""
proof = []
for level in tree[:-1]: # 不包含根
sibling_index = index ^ 1 # 获取兄弟节点索引
if sibling_index < len(level):
proof.append(level[sibling_index])
index //= 2 # 上一层的索引
return proof
# 示例:生成空投数据
airdrop_list = {
"0x123...abc": 100,
"0x456...def": 200,
"0x789...ghi": 150,
}
# 计算每个用户的哈希值
hashed_data = [hash(f"{addr}:{amount}") for addr, amount in airdrop_list.items()]
# 构建 Merkle 树
merkle_tree = build_merkle_tree(hashed_data)
merkle_root = get_merkle_root(merkle_tree)
# 生成证明
for i, (addr, amount) in enumerate(airdrop_list.items()):
proof = get_proof(merkle_tree, i)
print(f"Address: {addr}, Amount: {amount}, Proof: {proof}")
print("Merkle Root:", merkle_root)
2.2链上合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract MerkleAirdrop {
address public admin;
bytes32 public merkleRoot;
IERC20 public token;
// 记录已经领取的地址
mapping(address => bool) public hasClaimed;
event Claimed(address indexed account, uint256 amount);
event MerkleRootUpdated(bytes32 merkleRoot);
constructor(address _token) {
admin = msg.sender;
token = IERC20(_token);
}
modifier onlyAdmin() {
require(msg.sender == admin, "Not admin");
_;
}
/**
* @notice 更新 Merkle 根
* @param _merkleRoot 新的 Merkle 根
*/
function updateMerkleRoot(bytes32 _merkleRoot) external onlyAdmin {
merkleRoot = _merkleRoot;
emit MerkleRootUpdated(_merkleRoot);
}
/**
* @notice 领取空投
* @param amount 领取的代币数量
* @param proof Merkle 证明路径
*/
function claim(uint256 amount, bytes32[] calldata proof) external {
require(!hasClaimed[msg.sender], "Already claimed");
// 验证 Merkle 证明
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
require(verify(leaf, proof), "Invalid proof");
// 标记为已领取并发送代币
hasClaimed[msg.sender] = true;
require(token.transfer(msg.sender, amount), "Transfer failed");
emit Claimed(msg.sender, amount);
}
/**
* @notice 验证 Merkle 证明
* @param leaf 被验证的叶子节点
* @param proof Merkle 证明路径
* @return 是否有效
*/
function verify(bytes32 leaf, bytes32[] memory proof) public view returns (bool) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
}
return computedHash == merkleRoot;
}
}
3. 完整流程
- 链下服务
- 准备空投数据,包括地址和金额
- 计算每个地址的哈希值并生成 Merkle 树
- 将 Merkle 根部署到链上
- 为每个用户生成证明路径,并提供给用户
- 链上合约
- 管理员调用 updateMerkleRoot 设置新的空投 Merkle 根
- 用户调用 claim 提交领取请求,提供地址、金额和证明路径
- 合约验证 Merkle 证明,通过后将代币转给用户
The Web3 社区简介
The Web3 是一个专注于 Web3 技术解决方案设计与开发的社区,致力于为个人和企业提供专业提升的教程设计、研发与培训服务。此外,The web3 还提供项目安全审计、投研分析和项目孵化等全方位支持。