HolderDistributor
0x2fa0a7654ffaa37251688e8db4fcac656911245e
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
3,486 bytes
ABI entries
19
8 read · 4 write
License
none
Contract information
- Address
- 0x2fa0a7654ffaa37251688e8db4fcac656911245e
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0x77867A455f…263f70e357
- Creation tx
- 0xb1a7f7a561…ca74051020
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (8)
WETH() → address
claimed(address, address, address) → uint256
epoch(address) → uint256
merkleRoot(address) → bytes32
owner() → address
pool(address, address) → uint256
poolBalance(address, address) → uint256
totalClaimed(address, address) → uint256
Events (4)
DepositedHolderPaidOwnerUpdatedRootUpdated
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
},
{
"internalType": "address",
"name": "_weth",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Deposited",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
}
],
"name": "HolderPaid",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnerUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "epoch",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "root",
"type": "bytes32"
}
],
"name": "RootUpdated",
"type": "event"
},
{
"inputs": [],
"name": "WETH",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "cumulativeEth",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "cumulativeToken",
"type": "uint256"
},
{
"internalType": "bytes32[]",
"name": "proof",
"type": "bytes32[]"
}
],
"name": "claim",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "claimed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "deposit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "epoch",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "merkleRoot",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "pool",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "poolBalance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "bytes32",
"name": "root",
"type": "bytes32"
}
],
"name": "postRoot",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "totalClaimed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
interface IWETH9 {
function withdraw(uint256) external;
function balanceOf(address) external view returns (uint256);
}
interface IERC20Min {
function transfer(address, uint256) external returns (bool);
function transferFrom(address, address, uint256) external returns (bool);
function balanceOf(address) external view returns (uint256);
}
/**
* @title HolderDistributor
* @notice Dual-asset (ETH-via-WETH + the launch token) cumulative Merkle distributor for the
* 25% holder slice of xpad V3 swap fees. Modelled on PostGradRewards: per-token, per-asset
* solvency; cumulative leaves; updatable roots; NO owner withdraw. `claim` is
* account-parameterized and pays the leaf `account` (never msg.sender), so a keeper can
* auto-submit it for every holder (0-click) yet can never redirect funds; a holder can
* also self-claim if the keeper is offline. The WETH slice is unwrapped to native ETH.
*/
contract HolderDistributor is ReentrancyGuard {
address public owner; // root poster (keeper/treasury key)
address public immutable WETH;
mapping(address => mapping(address => uint256)) public pool; // token => asset => deposited
mapping(address => mapping(address => uint256)) public totalClaimed; // token => asset => claimed
mapping(address => mapping(address => mapping(address => uint256))) public claimed; // token => account => asset => cumulative claimed
mapping(address => bytes32) public merkleRoot; // token => root
mapping(address => uint256) public epoch; // token => epoch
event Deposited(address indexed token, address indexed asset, uint256 amount);
event RootUpdated(address indexed token, uint256 indexed epoch, bytes32 root);
event HolderPaid(address indexed token, address indexed account, uint256 ethAmount, uint256 tokenAmount);
event OwnerUpdated(address indexed owner);
modifier onlyOwner() { require(msg.sender == owner, "HD: not owner"); _; }
constructor(address _owner, address _weth) {
require(_owner != address(0) && _weth != address(0), "HD: zero");
owner = _owner;
WETH = _weth;
emit OwnerUpdated(_owner);
}
/// @dev receives native ETH only from WETH.withdraw during a claim.
receive() external payable {}
// ─── Funding: permissionless, pulls real funds, credits the real received delta ───
function deposit(address token, address asset, uint256 amount) external nonReentrant {
require(token != address(0) && asset != address(0), "HD: zero");
require(amount > 0, "HD: zero amount");
uint256 before = IERC20Min(asset).balanceOf(address(this));
require(IERC20Min(asset).transferFrom(msg.sender, address(this), amount), "HD: pull failed");
uint256 received = IERC20Min(asset).balanceOf(address(this)) - before;
pool[token][asset] += received;
emit Deposited(token, asset, received);
}
// ─── Admin ───
function postRoot(address token, bytes32 root) external onlyOwner {
require(root != bytes32(0), "HD: zero root");
uint256 e = epoch[token] + 1;
epoch[token] = e;
merkleRoot[token] = root;
emit RootUpdated(token, e, root);
}
function setOwner(address newOwner) external onlyOwner {
require(newOwner != address(0), "HD: zero");
owner = newOwner;
emit OwnerUpdated(newOwner);
}
// ─── Claim: pays the leaf `account`, callable by anyone (keeper auto-submits) ───
function claim(
address token,
address account,
uint256 cumulativeEth,
uint256 cumulativeToken,
bytes32[] calldata proof
) external nonReentrant {
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, cumulativeEth, cumulativeToken))));
require(MerkleProof.verify(proof, merkleRoot[token], leaf), "HD: bad proof");
uint256 ethDelta = _accrue(token, WETH, account, cumulativeEth);
uint256 tokDelta = _accrue(token, token, account, cumulativeToken);
require(ethDelta > 0 || tokDelta > 0, "HD: nothing to claim");
if (ethDelta > 0) {
IWETH9(WETH).withdraw(ethDelta);
(bool ok, ) = payable(account).call{value: ethDelta}("");
require(ok, "HD: eth send failed");
}
if (tokDelta > 0) {
require(IERC20Min(token).transfer(account, tokDelta), "HD: token send failed");
}
emit HolderPaid(token, account, ethDelta, tokDelta);
}
/// @dev effects for one asset: bump cumulative-claimed and enforce per-token solvency.
function _accrue(address token, address asset, address account, uint256 cumulative) private returns (uint256 delta) {
uint256 already = claimed[token][account][asset];
if (cumulative <= already) return 0;
delta = cumulative - already;
require(totalClaimed[token][asset] + delta <= pool[token][asset], "HD: pool exhausted");
claimed[token][account][asset] = cumulative;
totalClaimed[token][asset] += delta;
}
// ─── Views ───
function poolBalance(address token, address asset) external view returns (uint256) {
return pool[token][asset] - totalClaimed[token][asset];
}
}
Chain explorer5660msChain node92ms