Distributor
0x011bbcf5913da7fe77889824b9cb628e68cf9ca8
Verification
Verified
v0.8.20+commit.a1b79de6
Type
Contract
5,949 bytes
ABI entries
40
8 read · 9 write
License
none
Contract information
- Address
- 0x011bbcf5913da7fe77889824b9cb628e68cf9ca8
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.20+commit.a1b79de6
- Optimization
- Enabled
- Creator
- 0x5522427804…6eedC6C35f
- Creation tx
- 0x5124a8505c…3b326a5e84
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (8)
claimed(address, address, address, bytes) → uint256
getUnclaimedAmount(address, address, address, uint256, bytes) → uint256
isHarvested(address, address, address, uint256, bytes) → bool
merkleUpdater() → address
owner() → address
paused() → bool
root() → bytes32
wNative() → address
Events (7)
ClaimedDistributorPausedDistributorUnpausedMerkleUpdaterChangedOwnershipTransferredRecoveredERC20RootUpdated
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
},
{
"internalType": "address",
"name": "_updater",
"type": "address"
},
{
"internalType": "address",
"name": "_wNative",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "target",
"type": "address"
}
],
"name": "AddressEmptyCode",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "AddressInsufficientBalance",
"type": "error"
},
{
"inputs": [],
"name": "AlreadyClaimed",
"type": "error"
},
{
"inputs": [],
"name": "ContractIsPaused",
"type": "error"
},
{
"inputs": [],
"name": "FailedInnerCall",
"type": "error"
},
{
"inputs": [],
"name": "FailedToSendNative",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLengths",
"type": "error"
},
{
"inputs": [],
"name": "InvalidMerkleUpdater",
"type": "error"
},
{
"inputs": [],
"name": "InvalidProof",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "accAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes",
"name": "identifier",
"type": "bytes"
}
],
"name": "Claimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "DistributorPaused",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "DistributorUnpaused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "old",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "merkleUpdater",
"type": "address"
}
],
"name": "MerkleUpdaterChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "RecoveredERC20",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bytes32",
"name": "root",
"type": "bytes32"
}
],
"name": "RootUpdated",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "bytes",
"name": "identifier",
"type": "bytes"
}
],
"name": "claimed",
"outputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "identifier",
"type": "bytes"
}
],
"name": "getUnclaimedAmount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "identifier",
"type": "bytes"
},
{
"internalType": "bytes32[]",
"name": "proof",
"type": "bytes32[]"
}
],
"name": "harvest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "identifier",
"type": "bytes"
}
],
"name": "isHarvested",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "merkleUpdater",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address[]",
"name": "pools",
"type": "address[]"
},
{
"internalType": "address[]",
"name": "tokens",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
},
{
"internalType": "bytes[]",
"name": "identifiers",
"type": "bytes[]"
},
{
"internalType": "bytes32[][]",
"name": "proofs",
"type": "bytes32[][]"
}
],
"name": "multiHarvest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "recoverERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "root",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "unpause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "updater",
"type": "address"
}
],
"name": "updateMerkleUpdater",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "newRoot",
"type": "bytes32"
}
],
"name": "updateRoot",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "wNative",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {Ownable} from "@openzeppelin/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {MerkleProof} from "@openzeppelin/utils/cryptography/MerkleProof.sol";
import {IDistributor} from "./IDistributor.sol";
import {IWeth} from "./IWeth.sol";
contract Distributor is IDistributor, Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
/// @inheritdoc IDistributor
address public immutable wNative;
/// @inheritdoc IDistributor
bytes32 public root;
/// @notice Mapping of whether a user has claimed their rewards
mapping(
address user
=> mapping(
address pool
=> mapping(address token => mapping(bytes identifier => uint256 amount))
)
) public claimed;
/// @inheritdoc IDistributor
address public merkleUpdater;
/// @notice Whether the contract is paused or not
bool public paused;
/// @param _owner The owner of the contract
/// @param _updater Address that can update the merkle root of the contract
/// @param _wNative The address of the wrapped native token for the deployed chain
constructor(address _owner, address _updater, address _wNative) Ownable(_owner) {
if (_updater == address(0)) revert InvalidMerkleUpdater();
merkleUpdater = _updater;
wNative = _wNative;
}
/// @inheritdoc IDistributor
function harvest(
address user,
address pool,
address token,
uint256 amount,
bytes calldata identifier,
bytes32[] calldata proof
) external nonReentrant {
_requireNotPaused();
_harvest(user, pool, token, amount, identifier, proof);
}
/// @inheritdoc IDistributor
function multiHarvest(
address user,
address[] calldata pools,
address[] calldata tokens,
uint256[] calldata amounts,
bytes[] calldata identifiers,
bytes32[][] calldata proofs
) external nonReentrant {
_requireNotPaused();
if (
tokens.length == 0 || tokens.length != pools.length || tokens.length != amounts.length
|| tokens.length != identifiers.length || tokens.length != proofs.length
) revert InvalidLengths();
for (uint256 i; i < tokens.length;) {
_harvest(user, pools[i], tokens[i], amounts[i], identifiers[i], proofs[i]);
unchecked {
i++;
}
}
}
/// @inheritdoc IDistributor
function isHarvested(
address user,
address pool,
address token,
uint256 amount,
bytes calldata identifier
) public view returns (bool) {
return claimed[user][pool][token][identifier] >= amount;
}
// @inheritdoc IDistributor
function getUnclaimedAmount(
address user,
address pool,
address token,
uint256 amount,
bytes calldata identifier
) public view returns (uint256) {
uint256 claimedAmount = claimed[user][pool][token][identifier];
return amount < claimedAmount ? 0 : amount - claimedAmount;
}
/// @inheritdoc IDistributor
function updateRoot(bytes32 newRoot) external {
_requireOnlyMerkleUpdater();
root = newRoot;
emit RootUpdated(newRoot);
}
/// @inheritdoc IDistributor
function recoverERC20(address to, address token, uint256 amount) external {
_requireOnlyOwner();
IERC20(token).safeTransfer(to, amount);
emit RecoveredERC20(to, token, amount);
}
/// @inheritdoc IDistributor
function pause() external {
_requireOnlyOwner();
paused = true;
emit DistributorPaused();
}
/// @inheritdoc IDistributor
function unpause() external {
_requireOnlyOwner();
paused = false;
emit DistributorUnpaused();
}
/// @inheritdoc IDistributor
function updateMerkleUpdater(address updater) external {
_requireOnlyOwner();
if (updater == address(0)) revert InvalidMerkleUpdater();
address old = merkleUpdater;
merkleUpdater = updater;
emit MerkleUpdaterChanged(old, merkleUpdater);
}
receive() external payable {}
/// @dev Checks if contract is paused
function _requireNotPaused() internal view {
if (paused) revert ContractIsPaused();
}
/// @dev Checks if caller has operator rights
function _requireOnlyOwner() internal view {
if (msg.sender != owner()) revert Unauthorized();
}
/// @dev Checks if caller has updater rights
function _requireOnlyMerkleUpdater() internal view {
if (msg.sender != merkleUpdater) revert Unauthorized();
}
/// @dev Internal function to handle harvest of rewards
function _harvest(
address user,
address pool,
address token,
uint256 amount,
bytes calldata identifier,
bytes32[] calldata proof
) internal {
bytes32 leaf = keccak256(abi.encode(user, pool, token, amount, identifier));
if (!MerkleProof.verify(proof, root, leaf)) revert InvalidProof();
uint256 toSend = getUnclaimedAmount(user, pool, token, amount, identifier);
if (toSend == 0) revert AlreadyClaimed();
claimed[user][pool][token][identifier] = amount;
if (token == wNative) {
_unwrap(toSend);
(bool ok,) = user.call{value: toSend}("");
if (!ok) revert FailedToSendNative();
} else {
IERC20(token).safeTransfer(user, toSend);
}
emit Claimed(user, pool, token, toSend, amount, identifier);
}
/// @dev Internal function to withdraw wrapped native into native
function _unwrap(uint256 _amount) internal {
IWeth(wNative).withdraw(_amount);
}
}
Chain explorer2540msChain node85ms