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": []
}
}}
Submitted on: 2025-10-14 19:18:36
Comments
Log in to comment.
No comments yet.