// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /* * RBC Demo — Ethereum (ERC-20) edition, MetaMask-compatible. * Self-contained: no imports, compiles as-is in Remix or with solc. * Mirror of contracts-tron/RBCDemoTron.sol with 18 decimals and ETH/wei * denominations. Deployable on Sepolia or any EVM chain. * * 1. RBCToken — ERC-20, 100,000,000 RBC, 18 decimals, fixed supply, burnable. * 2. RBCPresale — fixed-rate sale in ETH with soft/hard caps, per-wallet * limits, claim after finalize, automatic refunds. * 3. RBCStaking — stake RBC, earn RBC from a reserve-backed pool. */ abstract contract Ownable { address public owner; event OwnershipTransferred(address indexed from, address indexed to); constructor() { owner = msg.sender; emit OwnershipTransferred(address(0), msg.sender); } modifier onlyOwner() { require(msg.sender == owner, "not owner"); _; } function transferOwnership(address to) external onlyOwner { require(to != address(0), "zero address"); emit OwnershipTransferred(owner, to); owner = to; } } abstract contract ReentrancyGuard { uint256 private _lock = 1; modifier nonReentrant() { require(_lock == 1, "reentrancy"); _lock = 2; _; _lock = 1; } } interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function allowance(address holder, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed holder, address indexed spender, uint256 value); } contract RBCToken is IERC20 { string public constant name = "RBC Demo Token"; string public constant symbol = "RBC"; uint8 public constant decimals = 18; uint256 public override totalSupply; mapping(address => uint256) public override balanceOf; mapping(address => mapping(address => uint256)) public override allowance; constructor() { totalSupply = 100_000_000 * 10 ** decimals; balanceOf[msg.sender] = totalSupply; emit Transfer(address(0), msg.sender, totalSupply); } function transfer(address to, uint256 amount) external override returns (bool) { _transfer(msg.sender, to, amount); return true; } function approve(address spender, uint256 amount) external override returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transferFrom(address from, address to, uint256 amount) external override returns (bool) { uint256 allowed = allowance[from][msg.sender]; require(allowed >= amount, "allowance too low"); if (allowed != type(uint256).max) { allowance[from][msg.sender] = allowed - amount; } _transfer(from, to, amount); return true; } function burn(uint256 amount) external { require(balanceOf[msg.sender] >= amount, "balance too low"); balanceOf[msg.sender] -= amount; totalSupply -= amount; emit Transfer(msg.sender, address(0), amount); } function _transfer(address from, address to, uint256 amount) internal { require(to != address(0), "zero address"); require(balanceOf[from] >= amount, "balance too low"); balanceOf[from] -= amount; balanceOf[to] += amount; emit Transfer(from, to, amount); } } contract RBCPresale is Ownable, ReentrancyGuard { IERC20 public immutable token; uint256 public immutable rate; // RBC (whole tokens) received per 1 ETH uint256 public immutable softCap; // wei uint256 public immutable hardCap; // wei uint256 public immutable minBuy; // wei per transaction floor uint256 public immutable maxBuy; // wei cap per wallet uint256 public immutable endTime; uint256 public totalRaised; uint256 public totalSold; uint256 public totalClaimed; bool public finalized; mapping(address => uint256) public contributedWei; mapping(address => uint256) public purchasedTokens; mapping(address => bool) public claimed; event Bought(address indexed buyer, uint256 weiIn, uint256 tokensOut); event Claimed(address indexed buyer, uint256 tokens); event Refunded(address indexed buyer, uint256 weiOut); event Finalized(uint256 totalRaised, bool softCapMet); constructor( IERC20 _token, uint256 _rate, uint256 _softCap, uint256 _hardCap, uint256 _minBuy, uint256 _maxBuy, uint256 _durationSeconds ) { require(address(_token) != address(0), "token=0"); require(_rate > 0, "rate=0"); require(_softCap > 0 && _softCap <= _hardCap, "caps"); require(_minBuy > 0 && _minBuy <= _maxBuy, "buy limits"); token = _token; rate = _rate; softCap = _softCap; hardCap = _hardCap; minBuy = _minBuy; maxBuy = _maxBuy; endTime = block.timestamp + _durationSeconds; } function buy() external payable { require(!finalized && block.timestamp < endTime, "sale ended"); require(msg.value >= minBuy, "below min buy"); uint256 newContribution = contributedWei[msg.sender] + msg.value; require(newContribution <= maxBuy, "above max buy"); require(totalRaised + msg.value <= hardCap, "hard cap reached"); // wei (18d) * rate = token units (18d): 1 ETH * 20000 = 20,000 RBC uint256 tokensOut = msg.value * rate; require(totalSold + tokensOut <= token.balanceOf(address(this)), "sale underfunded"); contributedWei[msg.sender] = newContribution; purchasedTokens[msg.sender] += tokensOut; totalRaised += msg.value; totalSold += tokensOut; emit Bought(msg.sender, msg.value, tokensOut); } function softCapMet() public view returns (bool) { return totalRaised >= softCap; } function saleClosed() public view returns (bool) { return finalized || block.timestamp >= endTime || totalRaised >= hardCap; } function finalize() external onlyOwner { require(!finalized, "already finalized"); require(block.timestamp >= endTime || totalRaised >= hardCap, "still running"); finalized = true; emit Finalized(totalRaised, softCapMet()); if (softCapMet()) { (bool ok, ) = payable(owner).call{value: address(this).balance}(""); require(ok, "eth transfer failed"); } } function claim() external nonReentrant { require(finalized && softCapMet(), "not claimable"); require(!claimed[msg.sender], "already claimed"); uint256 amount = purchasedTokens[msg.sender]; require(amount > 0, "nothing to claim"); claimed[msg.sender] = true; totalClaimed += amount; require(token.transfer(msg.sender, amount), "transfer failed"); emit Claimed(msg.sender, amount); } function refund() external nonReentrant { require(saleClosed() && !softCapMet(), "refunds unavailable"); uint256 amount = contributedWei[msg.sender]; require(amount > 0, "nothing to refund"); contributedWei[msg.sender] = 0; purchasedTokens[msg.sender] = 0; (bool ok, ) = payable(msg.sender).call{value: amount}(""); require(ok, "eth transfer failed"); emit Refunded(msg.sender, amount); } function withdrawUnsold(address to) external onlyOwner { require(finalized, "not finalized"); uint256 owedToBuyers = softCapMet() ? totalSold - totalClaimed : 0; uint256 surplus = token.balanceOf(address(this)) - owedToBuyers; require(surplus > 0, "nothing to withdraw"); require(token.transfer(to, surplus), "transfer failed"); } } contract RBCStaking is Ownable, ReentrancyGuard { IERC20 public immutable token; uint256 public aprBps = 1200; // 12.00% APR uint256 public totalStaked; uint256 public rewardReserve; struct StakeInfo { uint256 amount; uint256 accrued; uint256 lastUpdate; } mapping(address => StakeInfo) public stakes; event Staked(address indexed user, uint256 amount); event Unstaked(address indexed user, uint256 amount); event RewardsClaimed(address indexed user, uint256 amount); event RewardsFunded(address indexed from, uint256 amount); event AprUpdated(uint256 newAprBps); constructor(IERC20 _token) { require(address(_token) != address(0), "token=0"); token = _token; } function _accrue(address user) internal { StakeInfo storage s = stakes[user]; if (s.amount > 0) { s.accrued += (s.amount * aprBps * (block.timestamp - s.lastUpdate)) / (10_000 * 365 days); } s.lastUpdate = block.timestamp; } function pendingRewards(address user) external view returns (uint256) { StakeInfo storage s = stakes[user]; uint256 live = s.amount > 0 ? (s.amount * aprBps * (block.timestamp - s.lastUpdate)) / (10_000 * 365 days) : 0; return s.accrued + live; } function stake(uint256 amount) external nonReentrant { require(amount > 0, "amount=0"); _accrue(msg.sender); stakes[msg.sender].amount += amount; totalStaked += amount; require(token.transferFrom(msg.sender, address(this), amount), "transfer failed"); emit Staked(msg.sender, amount); } function unstake(uint256 amount) external nonReentrant { StakeInfo storage s = stakes[msg.sender]; require(amount > 0 && amount <= s.amount, "bad amount"); _accrue(msg.sender); s.amount -= amount; totalStaked -= amount; require(token.transfer(msg.sender, amount), "transfer failed"); emit Unstaked(msg.sender, amount); } function claimRewards() external nonReentrant { _accrue(msg.sender); StakeInfo storage s = stakes[msg.sender]; uint256 reward = s.accrued; require(reward > 0, "no rewards"); require(reward <= rewardReserve, "reserve too low"); s.accrued = 0; rewardReserve -= reward; require(token.transfer(msg.sender, reward), "transfer failed"); emit RewardsClaimed(msg.sender, reward); } function fundRewards(uint256 amount) external { require(amount > 0, "amount=0"); rewardReserve += amount; require(token.transferFrom(msg.sender, address(this), amount), "transfer failed"); emit RewardsFunded(msg.sender, amount); } function setApr(uint256 newAprBps) external onlyOwner { require(newAprBps <= 5_000, "apr too high"); aprBps = newAprBps; emit AprUpdated(newAprBps); } } /// @title RBC Vesting Vault /// @notice Holds a bucket's tokens behind a hard cliff, then releases them /// linearly. Nobody — including the owner — can move tokens out /// faster: release() pays only what the schedule has vested, and /// only to the fixed beneficiary. contract RBCVestingVault { IERC20 public immutable token; address public immutable beneficiary; uint256 public immutable start; uint256 public immutable cliffTime; uint256 public immutable duration; // seconds from start until fully vested uint256 public released; event Released(uint256 amount); constructor(IERC20 _token, address _beneficiary, uint256 _cliffSeconds, uint256 _durationSeconds) { require(address(_token) != address(0) && _beneficiary != address(0), "zero address"); require(_cliffSeconds <= _durationSeconds, "cliff > duration"); token = _token; beneficiary = _beneficiary; start = block.timestamp; cliffTime = block.timestamp + _cliffSeconds; duration = _durationSeconds; } function totalAllocation() public view returns (uint256) { return token.balanceOf(address(this)) + released; } function vestedAmount() public view returns (uint256) { if (block.timestamp < cliffTime) return 0; uint256 total = totalAllocation(); if (block.timestamp >= start + duration) return total; return (total * (block.timestamp - start)) / duration; } function releasable() public view returns (uint256) { return vestedAmount() - released; } function release() external { uint256 amount = releasable(); require(amount > 0, "nothing vested"); released += amount; require(token.transfer(beneficiary, amount), "transfer failed"); emit Released(amount); } }