Decentralized Allowance for Kids

What it does: Parents allocate weekly funds with rules (spending categories, savings goals, charity).

Why it matters: Teaches financial literacy transparently.

How it works:

  • Allowance released on schedule.

  • Smart rules enforce saving percentages.

  • Parents can set spending constraints.

  • Reports show habits over time.

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

/**
 * @title Decentralized Allowance for Kids
 *
 * Model:
 * - Parent funds the contract
 * - Parent assigns one or more children
 * - Parent configures weekly/monthly allowances
 * - Funds release only on schedule
 * - Optional chores approval gate
 * - Kids can withdraw only what is unlocked
 * - Parent can pause or update limits
 */

contract DecentralizedAllowance {
    struct AllowancePlan {
        uint256 amountPerPeriod;      // Allowance per cycle
        uint256 periodSeconds;        // 7 days, 30 days, etc.
        uint256 nextRelease;          // Timestamp when next funds unlock
        uint256 unlockedBalance;      // Accumulated amount ready to withdraw
        bool choresRequired;          // Whether parent needs to approve chores
        bool choresApproved;          // Reset after each release
        bool active;
    }

    address public parent;

    mapping(address => AllowancePlan) public plans;

    event PlanCreated(
        address indexed child,
        uint256 amountPerPeriod,
        uint256 periodSeconds,
        bool choresRequired
    );

    event PlanUpdated(
        address indexed child,
        uint256 amountPerPeriod,
        uint256 periodSeconds,
        bool choresRequired
    );

    event AllowanceUnlocked(
        address indexed child,
        uint256 amount
    );

    event Withdrawn(
        address indexed child,
        uint256 amount
    );

    event ChoresApproved(address indexed child);
    event PlanPaused(address indexed child);
    event PlanResumed(address indexed child);
    event ParentFunded(uint256 amount);
    event FundsRecovered(uint256 amount);

    modifier onlyParent() {
        require(msg.sender == parent, "Not parent");
        _;
    }

    modifier onlyChild(address child) {
        require(msg.sender == child, "Not authorized");
        _;
    }

    constructor() {
        parent = msg.sender;
    }

    // -----------------------------------
    // PARENT: FUND CONTRACT
    // -----------------------------------

    function fund() external payable onlyParent {
        require(msg.value > 0, "No value sent");
        emit ParentFunded(msg.value);
    }

    // -----------------------------------
    // PARENT: CREATE / UPDATE PLAN
    // -----------------------------------

    function createPlan(
        address child,
        uint256 amountPerPeriod,
        uint256 periodSeconds,
        bool choresRequired
    ) external onlyParent {
        require(child != address(0), "Invalid child");
        require(amountPerPeriod > 0, "Allowance must be > 0");
        require(periodSeconds >= 1 days, "Period too short");

        AllowancePlan storage plan = plans[child];

        plan.amountPerPeriod = amountPerPeriod;
        plan.periodSeconds = periodSeconds;
        plan.nextRelease = block.timestamp + periodSeconds;
        plan.unlockedBalance = 0;
        plan.choresRequired = choresRequired;
        plan.choresApproved = !choresRequired;
        plan.active = true;

        emit PlanCreated(child, amountPerPeriod, periodSeconds, choresRequired);
    }

    function updatePlan(
        address child,
        uint256 amountPerPeriod,
        uint256 periodSeconds,
        bool choresRequired
    ) external onlyParent {
        AllowancePlan storage plan = plans[child];
        require(plan.active, "Plan not active");

        plan.amountPerPeriod = amountPerPeriod;
        plan.periodSeconds = periodSeconds;
        plan.choresRequired = choresRequired;

        // Reset chores approval if chores required now
        if (choresRequired) {
            plan.choresApproved = false;
        }

        emit PlanUpdated(child, amountPerPeriod, periodSeconds, choresRequired);
    }

    // -----------------------------------
    // PARENT: CHORE APPROVAL
    // -----------------------------------

    function approveChores(address child) external onlyParent {
        AllowancePlan storage plan = plans[child];
        require(plan.active, "Plan not active");
        require(plan.choresRequired, "Chores not required");

        plan.choresApproved = true;

        emit ChoresApproved(child);
    }

    // -----------------------------------
    // AUTOMATIC UNLOCK
    // -----------------------------------

    function unlock(address child) public {
        AllowancePlan storage plan = plans[child];
        require(plan.active, "Plan not active");
        require(block.timestamp >= plan.nextRelease, "Too early");

        if (plan.choresRequired) {
            require(plan.choresApproved, "Chores not approved");
        }

        plan.unlockedBalance += plan.amountPerPeriod;
        plan.nextRelease = block.timestamp + plan.periodSeconds;

        // Reset chores approval for next period
        if (plan.choresRequired) {
            plan.choresApproved = false;
        }

        emit AllowanceUnlocked(child, plan.amountPerPeriod);
    }

    // -----------------------------------
    // CHILD: WITHDRAW UNLOCKED FUNDS
    // -----------------------------------

    function withdraw() external {
        AllowancePlan storage plan = plans[msg.sender];
        require(plan.active, "Plan not active");

        // Unlock automatically if due
        if (block.timestamp >= plan.nextRelease) {
            unlock(msg.sender);
        }

        uint256 amount = plan.unlockedBalance;
        require(amount > 0, "Nothing to withdraw");
        require(address(this).balance >= amount, "Insufficient contract balance");

        plan.unlockedBalance = 0;
        payable(msg.sender).transfer(amount);

        emit Withdrawn(msg.sender, amount);
    }

    // -----------------------------------
    // PARENT: SAFETY CONTROLS
    // -----------------------------------

    function pausePlan(address child) external onlyParent {
        AllowancePlan storage plan = plans[child];
        require(plan.active, "Already paused");

        plan.active = false;
        emit PlanPaused(child);
    }

    function resumePlan(address child) external onlyParent {
        AllowancePlan storage plan = plans[child];
        require(!plan.active, "Already active");

        plan.active = true;
        emit PlanResumed(child);
    }

    // Parent can recover leftover funds if plans are paused
    function recoverFunds(uint256 amount) external onlyParent {
        require(amount <= address(this).balance, "Too much");
        payable(parent).transfer(amount);

        emit FundsRecovered(amount);
    }

    // -----------------------------------
    // VIEW HELPERS
    // -----------------------------------

    function getPlan(address child)
        external
        view
        returns (
            uint256 amountPerPeriod,
            uint256 periodSeconds,
            uint256 nextRelease,
            uint256 unlockedBalance,
            bool choresRequired,
            bool choresApproved,
            bool active
        )
    {
        AllowancePlan memory p = plans[child];
        return (
            p.amountPerPeriod,
            p.periodSeconds,
            p.nextRelease,
            p.unlockedBalance,
            p.choresRequired,
            p.choresApproved,
            p.active
        );
    }
}