Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions contracts/MerkleDistributorWithStartTime.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.11;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";

contract AccumulatingMerkleDistributor is Ownable {
uint public immutable timeout;
uint public immutable startTime;
address public immutable token;
bytes32 public merkleRoot;

// This is a packed array of booleans.
mapping(address => uint256) public claimedAmount;

event Claimed(uint256 index, address account, uint256 amount);
event RootUpdated(bytes32 newRoot);

constructor(address token_, bytes32 merkleRoot_, address owner_, uint timeout_, uint startTime_) {
transferOwnership(owner_);
token = token_;
merkleRoot = merkleRoot_;
timeout = timeout_;
startTime = startTime_;
}

function claim(uint256 index, address account, uint256 amount, bytes32[] calldata merkleProof) external claimStarted {
uint claimed = claimedAmount[account];
require(claimed < amount, 'AccumulatingMerkleDistributor: Drop already claimed.');

// Verify the merkle proof.
bytes32 node = keccak256(abi.encodePacked(index, account, amount));
require(MerkleProof.verify(merkleProof, merkleRoot, node), 'AccumulatingMerkleDistributor: Invalid proof.');

// Mark it claimed and send the token.
claimedAmount[account] = amount;
require(IERC20(token).transfer(account, amount - claimed), 'AccumulatingMerkleDistributor: Transfer failed.');

emit Claimed(index, account, amount);
}

// New root should include all old recipients plus new ones.
function updateRoot(bytes32 newRoot) public onlyOwner() {
merkleRoot = newRoot;
emit RootUpdated(newRoot);
}

function recover(address to, bytes calldata data) external onlyOwner() returns(bool, bytes memory) {
if (to == token) {
require(block.timestamp > timeout, 'AccumulatingMerkleDistributor: not timed out yet.');
}
return to.call(data);
}

modifier claimStarted() {
require(block.timestamp > startTime, 'Claiming has not started yet.');
_;
}
}