// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// BremoTimelock: credible neutrality for the founder-held supply. /// /// The point of this contract is to make a promise unbreakable. The founder /// currently holds the entire supply, which is the single biggest reason a /// stranger should NOT trust a new coin: nothing stops the holder from selling /// everything into buyers. This contract removes that power. Tokens deposited /// here can only leave on a fixed, public schedule, to a fixed beneficiary. /// There is no owner, no admin, no early-release, no cancel, no clawback. /// Once funded, not even the founder can pull tokens out ahead of schedule. /// /// Each tranche is (unlockTime, amount). Anyone can read the full schedule on /// chain. Anyone can call release() after an unlock time; the tokens can only /// ever go to the immutable beneficiary. This turns "the founder holds it all" /// into "the supply is locked on a schedule everyone can see." contract BremoTimelock { using SafeERC20 for IERC20; struct Tranche { uint64 unlockTime; uint256 amount; bool released; } IERC20 public immutable token; address public immutable beneficiary; Tranche[] public tranches; event Released(uint256 indexed index, uint256 amount, uint64 unlockTime); /// @param _token the BREMO ERC-20 /// @param _beneficiary the only address tranches can ever be released to /// @param unlockTimes strictly increasing unix timestamps /// @param amounts amount unlocked at each corresponding time constructor( IERC20 _token, address _beneficiary, uint64[] memory unlockTimes, uint256[] memory amounts ) { require(_beneficiary != address(0), "beneficiary zero"); require(unlockTimes.length == amounts.length, "length mismatch"); require(unlockTimes.length > 0, "empty schedule"); token = _token; beneficiary = _beneficiary; uint64 last = 0; for (uint256 i = 0; i < unlockTimes.length; i++) { require(unlockTimes[i] > last, "times not increasing"); require(amounts[i] > 0, "zero amount"); last = unlockTimes[i]; tranches.push(Tranche({unlockTime: unlockTimes[i], amount: amounts[i], released: false})); } } function trancheCount() external view returns (uint256) { return tranches.length; } /// Total still locked in this contract across all unreleased tranches. function locked() external view returns (uint256 total) { for (uint256 i = 0; i < tranches.length; i++) { if (!tranches[i].released) total += tranches[i].amount; } } /// Release a single matured tranche to the beneficiary. Callable by anyone, /// but the destination is always the immutable beneficiary. Reverts if the /// tranche is not yet unlocked or already released. function release(uint256 index) public { Tranche storage t = tranches[index]; require(!t.released, "already released"); require(block.timestamp >= t.unlockTime, "not yet unlocked"); t.released = true; token.safeTransfer(beneficiary, t.amount); emit Released(index, t.amount, t.unlockTime); } /// Release every matured, unreleased tranche in one call. function releaseAllMatured() external { for (uint256 i = 0; i < tranches.length; i++) { if (!tranches[i].released && block.timestamp >= tranches[i].unlockTime) { release(i); } } } }