Automated Savings Vault

What it does:
Users deposit funds into a smart contract vault that automatically enforces savings rules. Withdrawals are locked until predefined conditions are met (time, goals, milestones), encouraging disciplined and consistent saving.

Why it matters:
Many people struggle to save because of impulsive spending. By removing emotional decision-making and enforcing rules on-chain, this contract helps users build strong financial habits and long-term wealth.

How it works:

  • Users deposit ETH into a personal savings vault.

  • A lock period or target goal is defined at creation.

  • Funds cannot be withdrawn until conditions are satisfied.

  • Optional penalties apply for early withdrawal attempts.

  • Progress and savings history are transparently stored on-chain.

      // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title AutomatedSavingsVault
 * @author Nam
 * @notice A smart contract that enforces disciplined saving by locking funds
 *         until a predefined condition is met.
 */
contract AutomatedSavingsVault {

    struct Vault {
        uint256 balance;
        uint256 unlockTime;
        uint256 targetAmount;
        bool targetBased;
        bool withdrawn;
    }

    mapping(address => Vault) public vaults;

    event VaultCreated(
        address indexed user,
        uint256 unlockTime,
        uint256 targetAmount,
        bool targetBased
    );

    event Deposit(
        address indexed user,
        uint256 amount,
        uint256 totalBalance
    );

    event Withdraw(
        address indexed user,
        uint256 amount
    );

    event EarlyWithdrawPenalty(
        address indexed user,
        uint256 penaltyAmount
    );

    /**
     * @notice Create a new savings vault
     * @param _unlockTime Timestamp when funds can be withdrawn
     * @param _targetAmount Target savings goal (0 if time-based only)
     */
    function createVault(
        uint256 _unlockTime,
        uint256 _targetAmount
    ) external payable {
        require(vaults[msg.sender].balance == 0, "Vault already exists");
        require(msg.value > 0, "Initial deposit required");

        bool targetBased = _targetAmount > 0;

        vaults[msg.sender] = Vault({
            balance: msg.value,
            unlockTime: _unlockTime,
            targetAmount: _targetAmount,
            targetBased: targetBased,
            withdrawn: false
        });

        emit VaultCreated(
            msg.sender,
            _unlockTime,
            _targetAmount,
            targetBased
        );
    }

    /**
     * @notice Deposit additional funds into an existing vault
     */
    function deposit() external payable {
        Vault storage vault = vaults[msg.sender];
        require(vault.balance > 0, "Vault does not exist");
        require(!vault.withdrawn, "Vault already withdrawn");
        require(msg.value > 0, "Deposit must be greater than zero");

        vault.balance += msg.value;

        emit Deposit(msg.sender, msg.value, vault.balance);
    }

    /**
     * @notice Withdraw funds if conditions are met
     */
    function withdraw() external {
        Vault storage vault = vaults[msg.sender];
        require(vault.balance > 0, "Vault does not exist");
        require(!vault.withdrawn, "Already withdrawn");

        bool timeConditionMet = block.timestamp >= vault.unlockTime;
        bool targetConditionMet = !vault.targetBased || vault.balance >= vault.targetAmount;

        require(timeConditionMet || targetConditionMet, "Withdrawal conditions not met");

        uint256 amount = vault.balance;
        vault.withdrawn = true;
        vault.balance = 0;

        payable(msg.sender).transfer(amount);

        emit Withdraw(msg.sender, amount);
    }

    /**
     * @notice Emergency early withdrawal with penalty (10%)
     */
    function emergencyWithdraw() external {
        Vault storage vault = vaults[msg.sender];
        require(vault.balance > 0, "Vault does not exist");
        require(!vault.withdrawn, "Already withdrawn");

        uint256 penalty = (vault.balance * 10) / 100;
        uint256 payout = vault.balance - penalty;

        vault.withdrawn = true;
        vault.balance = 0;

        payable(msg.sender).transfer(payout);

        emit EarlyWithdrawPenalty(msg.sender, penalty);
        emit Withdraw(msg.sender, payout);
    }

    /**
     * @notice View vault details
     */
    function getVault(address _user)
        external
        view
        returns (
            uint256 balance,
            uint256 unlockTime,
            uint256 targetAmount,
            bool targetBased,
            bool withdrawn
        )
    {
        Vault memory vault = vaults[_user];
        return (
            vault.balance,
            vault.unlockTime,
            vault.targetAmount,
            vault.targetBased,
            vault.withdrawn
        );
    }
}