账户
0xc6...e28c
0xC6...e28c

0xC6...e28c

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.25+commit.b61c2a91
语言
Solidity
合同源代码
文件 1 的 3:IERC20.sol
// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.25;

interface IERC20 {

    function transfer(
        address _recipient,
        uint256 _amount
    )
        external
        returns (bool);

    function transferFrom(
        address _sender,
        address _recipient,
        uint256 _amount
    )
        external
        returns (bool);

    function balanceOf(
        address _account
    )
        external
        view
        returns (uint256);
}
合同源代码
文件 2 的 3:MerkleProof.sol
// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.25;

library MerkleProof {

    function verify(
        bytes32[] memory _proof,
        bytes32 _root,
        bytes32 _leaf
    )
        internal
        pure
        returns (bool)
    {
        uint256 i;
        uint256 l = _proof.length;
        bytes32 computedHash = _leaf;

        while (i < l) {

            bytes32 proofElement = _proof[i];

            computedHash <= proofElement
                ? computedHash = keccak256(abi.encodePacked(computedHash, proofElement))
                : computedHash = keccak256(abi.encodePacked(proofElement, computedHash));

            unchecked {
                ++i;
            }
        }

        return computedHash == _root;
    }
}
合同源代码
文件 3 的 3:WiseRewards.sol
// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.25;

import "./IERC20.sol";
import "./MerkleProof.sol";

error InvalidClaim();
error InvalidAmount();
error AlreadyCreated();
error AlreadyClaimed();

/**
  * @title Wise Merkle Rewards
  * @author Vitally Marinchenko
  */

contract WiseRewards {

    uint256 public rewardsCount;
    uint256 public totalRequired;
    uint256 public totalCollected;
    uint256 public latestRootAdded;

    address public masterAccount;
    address public workerAccount;

    struct Reward {
        bytes32 root;
        uint256 total;
        uint256 claimed;
        uint256 created;
    }

    IERC20 public immutable REWARD_TOKEN;

    mapping(uint256 => string) public ipfsData;
    mapping(bytes32 => Reward) public rewardsData;

    mapping(bytes32 => mapping(address => bool)) public hasClaimed;

    modifier onlyMaster() {
        require(
            msg.sender == masterAccount,
            "WiseRewards: INVALID_MASTER"
        );
        _;
    }

    modifier onlyWorker() {
        require(
            msg.sender == workerAccount,
            "WiseRewards: INVALID_WORKER"
        );
        _;
    }

    event Deposit(
        address indexed account,
        uint256 amount
    );

    event Withdraw(
        address indexed account,
        uint256 amount
    );

    event NewRewards(
        bytes32 indexed hash,
        address indexed master,
        string indexed ipfsAddress,
        uint256 total
    );

    event Claimed(
        uint256 indexed index,
        address indexed account,
        uint256 amount
    );

    event Thanks(
        address indexed account,
        uint256 indexed amount
    );

    receive()
        external
        payable
    {
        payable(masterAccount).transfer(
            msg.value
        );

        emit Thanks(
            msg.sender,
            msg.value
        );
    }

    constructor(
        address _rewardToken,
        address _masterAccount,
        address _workerAccount
    ) {
        REWARD_TOKEN = IERC20(
            _rewardToken
        );

        masterAccount = _masterAccount;
        workerAccount = _workerAccount;
    }

    function createRewards(
        bytes32 _root,
        uint256 _total,
        string calldata _ipfsAddress
    )
        external
        onlyMaster
    {
        if (_total == 0) {
            revert InvalidAmount();
        }

        bytes32 ipfsHash = getHash(
            _ipfsAddress
        );

        if (rewardsData[ipfsHash].total > 0) {
            revert AlreadyCreated();
        }

        rewardsData[ipfsHash] = Reward({
            root: _root,
            total: _total,
            created: block.timestamp,
            claimed: 0
        });

        rewardsCount =
        rewardsCount + 1;

        ipfsData[rewardsCount] = _ipfsAddress;

        totalRequired =
        totalRequired + _total;

        latestRootAdded = block.timestamp;

        emit NewRewards(
            _root,
            masterAccount,
            _ipfsAddress,
            _total
        );
    }

    function getHash(
        string calldata _ipfsAddress
    )
        public
        pure
        returns (bytes32)
    {
        return keccak256(
            abi.encodePacked(
                _ipfsAddress
            )
        );
    }

    function isClaimed(
        bytes32 _hash,
        address _account
    )
        public
        view
        returns (bool)
    {
        return hasClaimed[_hash][_account];
    }

    function isClaimedBulk(
        bytes32[] calldata _hash,
        address _account
    )
        external
        view
        returns (bool[] memory)
    {
        uint256 i;
        uint256 l = _hash.length;
        bool[] memory result = new bool[](l);

        while (i < l) {
            result[i] = isClaimed(
                _hash[i],
                _account
            );

            unchecked {
                ++i;
            }
        }

        return result;
    }

    function getClaim(
        bytes32 _hash,
        uint256 _index,
        uint256 _amount,
        bytes32[] calldata _merkleProof
    )
        external
    {
        _doClaim(
            _hash,
            _index,
            _amount,
            msg.sender,
            _merkleProof
        );
    }

    function getClaimBulk(
        bytes32[] calldata _hash,
        uint256[] calldata _index,
        uint256[] calldata _amount,
        bytes32[][] calldata _merkleProof
    )
        external
    {
        uint256 i;
        uint256 l = _hash.length;

        while (i < l) {
            _doClaim(
                _hash[i],
                _index[i],
                _amount[i],
                msg.sender,
                _merkleProof[i]
            );

            unchecked {
                ++i;
            }
        }
    }

    function giveClaim(
        bytes32 _hash,
        uint256 _index,
        uint256 _amount,
        address _account,
        bytes32[] calldata _merkleProof
    )
        external
        onlyWorker
    {
        _doClaim(
            _hash,
            _index,
            _amount,
            _account,
            _merkleProof
        );
    }

    function giveClaimBulk(
        bytes32[] calldata _hash,
        uint256[] calldata _index,
        uint256[] calldata _amount,
        address[] calldata _account,
        bytes32[][] calldata _merkleProof
    )
        external
        onlyWorker
    {
        uint256 i;
        uint256 l = _hash.length;

        while (i < l) {
            _doClaim(
                _hash[i],
                _index[i],
                _amount[i],
                _account[i],
                _merkleProof[i]
            );

            unchecked {
                ++i;
            }
        }
    }

    function _doClaim(
        bytes32 _hash,
        uint256 _index,
        uint256 _amount,
        address _account,
        bytes32[] calldata _merkleProof
    )
        private
    {
        if (isClaimed(_hash, _account) == true) {
            revert AlreadyClaimed();
        }

        bytes32 node = keccak256(
            abi.encodePacked(
                _index,
                _account,
                _amount
            )
        );

        require(
            MerkleProof.verify(
                _merkleProof,
                rewardsData[_hash].root,
                node
            ),
            "WiseRewards: INVALID_PROOF"
        );

        totalCollected =
        totalCollected + _amount;

        rewardsData[_hash].claimed =
        rewardsData[_hash].claimed + _amount;

        if (rewardsData[_hash].claimed > rewardsData[_hash].total) {
            revert InvalidClaim();
        }

        _setClaimed(
            _hash,
            _account
        );

        REWARD_TOKEN.transfer(
            _account,
            _amount
        );

        emit Claimed(
            _index,
            _account,
            _amount
        );
    }

    function _setClaimed(
        bytes32 _hash,
        address _account
    )
        private
    {
        hasClaimed[_hash][_account] = true;
    }

    function donateFunds(
        uint256 _donationAmount
    )
        external
    {
        if (_donationAmount == 0) {
            revert InvalidAmount();
        }

        REWARD_TOKEN.transferFrom(
            msg.sender,
            address(this),
            _donationAmount
        );

        emit Deposit(
            msg.sender,
            _donationAmount
        );
    }

    function withdrawEth(
        uint256 _amount
    )
        external
        onlyMaster
    {
        payable(
            masterAccount
        ).transfer(
            _amount
        );

        emit Withdraw(
            masterAccount,
            _amount
        );
    }

    function changeMaster(
        address _newMaster
    )
        external
        onlyMaster
    {
        masterAccount = _newMaster;
    }

    function changeWorker(
        address _newWorker
    )
        external
        onlyMaster
    {
        workerAccount = _newWorker;
    }

    function getBalance()
        public
        view
        returns (uint256)
    {
        return REWARD_TOKEN.balanceOf(
            address(this)
        );
    }

    function showRemaining(
        bytes32 _hash
    )
        public
        view
        returns (uint256)
    {
        return rewardsData[_hash].total - rewardsData[_hash].claimed;
    }

    function showExcess(
        bytes32 _hash
    )
        external
        view
        returns (int256)
    {
        return int256(getBalance()) - int256(showRemaining(_hash));
    }

    function showRemaining()
        public
        view
        returns (uint256)
    {
        return totalRequired - totalCollected;
    }

    function showExcess()
        external
        view
        returns (int256)
    {
        return int256(getBalance()) - int256(showRemaining());
    }

    function rescueTokens(
        address _token,
        address _target,
        uint256 _amount
    )
        external
        onlyMaster
    {
        IERC20(_token).transfer(
            _target,
            _amount
        );
    }
}
设置
{
  "compilationTarget": {
    "WiseRewards.sol": "WiseRewards"
  },
  "evmVersion": "cancun",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 99999
  },
  "remappings": []
}
ABI
[{"inputs":[{"internalType":"address","name":"_rewardToken","type":"address"},{"internalType":"address","name":"_masterAccount","type":"address"},{"internalType":"address","name":"_workerAccount","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyClaimed","type":"error"},{"inputs":[],"name":"AlreadyCreated","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidClaim","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"master","type":"address"},{"indexed":true,"internalType":"string","name":"ipfsAddress","type":"string"},{"indexed":false,"internalType":"uint256","name":"total","type":"uint256"}],"name":"NewRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Thanks","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"REWARD_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newMaster","type":"address"}],"name":"changeMaster","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newWorker","type":"address"}],"name":"changeWorker","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_root","type":"bytes32"},{"internalType":"uint256","name":"_total","type":"uint256"},{"internalType":"string","name":"_ipfsAddress","type":"string"}],"name":"createRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_donationAmount","type":"uint256"}],"name":"donateFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_hash","type":"bytes32"},{"internalType":"uint256","name":"_index","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes32[]","name":"_merkleProof","type":"bytes32[]"}],"name":"getClaim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"_hash","type":"bytes32[]"},{"internalType":"uint256[]","name":"_index","type":"uint256[]"},{"internalType":"uint256[]","name":"_amount","type":"uint256[]"},{"internalType":"bytes32[][]","name":"_merkleProof","type":"bytes32[][]"}],"name":"getClaimBulk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_ipfsAddress","type":"string"}],"name":"getHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_hash","type":"bytes32"},{"internalType":"uint256","name":"_index","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_account","type":"address"},{"internalType":"bytes32[]","name":"_merkleProof","type":"bytes32[]"}],"name":"giveClaim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"_hash","type":"bytes32[]"},{"internalType":"uint256[]","name":"_index","type":"uint256[]"},{"internalType":"uint256[]","name":"_amount","type":"uint256[]"},{"internalType":"address[]","name":"_account","type":"address[]"},{"internalType":"bytes32[][]","name":"_merkleProof","type":"bytes32[][]"}],"name":"giveClaimBulk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"hasClaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"ipfsData","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_hash","type":"bytes32"},{"internalType":"address","name":"_account","type":"address"}],"name":"isClaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"_hash","type":"bytes32[]"},{"internalType":"address","name":"_account","type":"address"}],"name":"isClaimedBulk","outputs":[{"internalType":"bool[]","name":"","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestRootAdded","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"masterAccount","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_target","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"rescueTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"rewardsData","outputs":[{"internalType":"bytes32","name":"root","type":"bytes32"},{"internalType":"uint256","name":"total","type":"uint256"},{"internalType":"uint256","name":"claimed","type":"uint256"},{"internalType":"uint256","name":"created","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_hash","type":"bytes32"}],"name":"showExcess","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"showExcess","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"showRemaining","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_hash","type":"bytes32"}],"name":"showRemaining","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalCollected","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalRequired","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawEth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"workerAccount","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]