EthTimeVault

Description:

Smart contract deployed on Ethereum with Factory features.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "lockup2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/// @title ETH Time Vault (linear unlock, withdraw-all only)
contract EthTimeVault {
    /* ─────────────── Immutable & Storage ─────────────── */

    address public immutable creator;
    address public owner;

    uint256 public immutable startTime;
    uint256 public immutable endTime;

    uint256 public totalReceived;
    uint256 public withdrawnByOwner;

    /* ─────────────── Events ─────────────── */

    event Deposit(address indexed from, uint256 amount);
    event WithdrawAll(address indexed owner, uint256 ownerAmount, uint256 penaltyToCreator);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /* ─────────────── Minimal Reentrancy Guard ─────────────── */

    uint256 private _status;
    modifier nonReentrant() {
        require(_status == 0, "Reentrancy");
        _status = 1;
        _;
        _status = 0;
    }

    /* ─────────────── Constructor ─────────────── */

    constructor(uint256 _startTime, uint256 _endTime) {
        require(_endTime > _startTime, "endTime must be > startTime");
        creator = msg.sender;
        owner = msg.sender;
        startTime = _startTime;
        endTime = _endTime;
    }

    /* ─────────────── ETH Intake ─────────────── */

    receive() external payable {
        if (msg.value > 0) {
            totalReceived += msg.value;
            emit Deposit(msg.sender, msg.value);
        }
    }

    fallback() external payable {
        if (msg.value > 0) {
            totalReceived += msg.value;
            emit Deposit(msg.sender, msg.value);
        }
    }

    /* ─────────────── Ownership ─────────────── */

    function transferOwnership(address newOwner) external {
        require(msg.sender == owner || msg.sender == creator, "Not authorized");
        require(newOwner != address(0), "newOwner=0");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    /* ─────────────── Withdraw-All Logic ─────────────── */

    function withdrawAll() external nonReentrant {
        require(msg.sender == owner, "Not owner");
        uint256 amount = address(this).balance;
        require(amount > 0, "No balance");

        uint256 ownerAmount;
        uint256 penalty;

        if (block.timestamp >= endTime) {
            ownerAmount = amount;
        } else {
            uint256 claimable = _claimableNow();
            if (amount <= claimable) ownerAmount = amount;
            else {
                ownerAmount = claimable;
                penalty = amount - claimable;
            }
        }

        if (ownerAmount > 0) withdrawnByOwner += ownerAmount;

        if (ownerAmount > 0) {
            (bool okOwner, ) = payable(owner).call{value: ownerAmount}("");
            require(okOwner, "Owner transfer failed");
        }
        if (penalty > 0) {
            (bool okCreator, ) = payable(creator).call{value: penalty}("");
            require(okCreator, "Creator transfer failed");
        }

        emit WithdrawAll(owner, ownerAmount, penalty);
    }

    /* ─────────────── Core math views ─────────────── */

    function _progressRay() internal view returns (uint256 p) {
        if (block.timestamp <= startTime) return 0;
        if (block.timestamp >= endTime) return 1e18;
        uint256 elapsed = block.timestamp - startTime;
        uint256 duration = endTime - startTime;
        return (elapsed * 1e18) / duration;
    }

    function _unlockedTotal() internal view returns (uint256) {
        uint256 p = _progressRay();
        return (totalReceived * p) / 1e18;
    }

    function _claimableNow() internal view returns (uint256) {
        uint256 u = _unlockedTotal();
        return u <= withdrawnByOwner ? 0 : u - withdrawnByOwner;
    }

    function _withdrawableNow(uint256 currentBalance) internal view returns (uint256) {
        if (block.timestamp >= endTime) return currentBalance;
        uint256 claimable = _claimableNow();
        return claimable < currentBalance ? claimable : currentBalance;
    }

    /* ─────────────── Public readable helpers ─────────────── */

    /// @notice Current on-chain balance in wei
    function balanceWei() external view returns (uint256) {
        return address(this).balance;
    }

    /// @notice Current on-chain balance formatted to 5 decimals in ETH
    function balanceEth() external view returns (string memory) {
        return _toEthFixed5(address(this).balance);
    }

    /// @notice Amount (wei) that owner could withdraw right now
    function withdrawableWei() external view returns (uint256) {
        return _withdrawableNow(address(this).balance);
    }

    /// @notice Amount (ETH string, 5 decimals) that owner could withdraw right now
    function withdrawableEth() external view returns (string memory) {
        return _toEthFixed5(_withdrawableNow(address(this).balance));
    }

    /// @notice Percentage (string, one decimal) of vault balance that owner could withdraw right now
    function withdrawablePercent() external view returns (string memory) {
        uint256 bal = address(this).balance;
        if (bal == 0) return "0.0%";
        uint256 ownerShare = _withdrawableNow(bal);
        uint256 tenths = _divRound(ownerShare * 1000, bal); // tenths of percent
        return string(
            abi.encodePacked(
                _toString(tenths / 10),
                ".",
                _toString(tenths % 10),
                "%"
            )
        );
    }

    /// @notice Days (rounded up) until full unlock
    function daysUntilFullUnlock() external view returns (uint256) {
        if (block.timestamp >= endTime) return 0;
        uint256 secs = endTime - block.timestamp;
        return (secs + 86399) / 86400;
    }

    /* ─────────────── Internal formatting helpers ─────────────── */

    function _toEthFixed5(uint256 weiAmount) internal pure returns (string memory) {
        uint256 intPart = weiAmount / 1e18;
        uint256 fracWei = weiAmount % 1e18;
        uint256 frac = (fracWei + 5e12) / 1e13; // round half up to 5 decimals
        if (frac == 100000) {
            intPart += 1;
            frac = 0;
        }
        string memory intStr = _toString(intPart);
        string memory fracStr = _leftPadZeros(_toString(frac), 5);
        return string(abi.encodePacked(intStr, ".", fracStr));
    }

    function _divRound(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0, "div by zero");
        return (a + b / 2) / b;
    }

    function _toString(uint256 v) internal pure returns (string memory) {
        if (v == 0) return "0";
        uint256 len;
        uint256 tmp = v;
        while (tmp != 0) { len++; tmp /= 10; }
        bytes memory buf = new bytes(len);
        while (v != 0) {
            len -= 1;
            buf[len] = bytes1(uint8(48 + uint256(v % 10)));
            v /= 10;
        }
        return string(buf);
    }

    function _leftPadZeros(string memory s, uint256 width) internal pure returns (string memory) {
        bytes memory b = bytes(s);
        if (b.length >= width) return s;
        bytes memory out = new bytes(width);
        uint256 pad = width - b.length;
        for (uint256 i = 0; i < pad; i++) out[i] = "0";
        for (uint256 j = 0; j < b.length; j++) out[pad + j] = b[j];
        return string(out);
    }
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": false,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "remappings": []
  }
}}

Tags:
Factory|addr:0x1d66373fc1f17a4296f36d49c7ee0a159e0c88e2|verified:true|block:23577080|tx:0x986d63a402daee3110384e1d698d98b61fe99c7fe32a52c71cca40213be8111a|first_check:1760462315

Submitted on: 2025-10-14 19:18:36

Comments

Log in to comment.

No comments yet.