Learn-to-Earn Smart Contract

What it does:
Rewards learners with tokens or funds for completing educational tasks, courses, or achieving skill milestones on-chain.

Why it matters:
Incentivizes learning through financial rewards, verifies completion transparently, and creates a verifiable skill-based economy.

How it works:

  • Learners enroll in courses or learning modules

  • Educators define learning milestones and associated rewards

  • Completion is verified on-chain via proof-of-completion or educator approval

  • Smart contract releases token/fund rewards automatically upon milestone completion

  • Learners can claim accumulated rewards

  • All activities, completions, and reward distributions are auditable

  • Incomplete or disputed tasks are handled per predefined rules

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

/**
 * @title LearnToEarn
 * @author Nam
 * @notice Reward learners for completing tasks or courses on-chain
 */
contract LearnToEarn {

    // -------------------- ROLES --------------------

    address public admin;

    // -------------------- STRUCTS --------------------

    struct Course {
        string metadataHash; // IPFS course info
        uint256 rewardAmount; // wei per milestone
        address educator;
        bool active;
    }

    struct Enrollment {
        uint256 courseId;
        address learner;
        uint256 completedMilestones;
        uint256 totalRewardClaimed;
    }

    uint256 public courseCount;
    uint256 public enrollmentCount;

    mapping(uint256 => Course) public courses;
    mapping(uint256 => Enrollment) public enrollments;

    // -------------------- EVENTS --------------------

    event CourseCreated(uint256 indexed courseId, address indexed educator);
    event LearnerEnrolled(uint256 indexed enrollmentId, uint256 courseId, address learner);
    event MilestoneCompleted(uint256 indexed enrollmentId, uint256 rewardAmount);
    event RewardClaimed(uint256 indexed enrollmentId, address learner, uint256 amount);

    // -------------------- MODIFIERS --------------------

    modifier onlyAdmin() {
        require(msg.sender == admin, "Not admin");
        _;
    }

    modifier onlyEducator(uint256 _courseId) {
        require(courses[_courseId].educator == msg.sender, "Not course educator");
        _;
    }

    // -------------------- CONSTRUCTOR --------------------

    constructor() {
        admin = msg.sender;
    }

    // -------------------- COURSE MANAGEMENT --------------------

    function createCourse(string calldata _metadataHash, uint256 _rewardAmount) external {
        require(_rewardAmount > 0, "Invalid reward");

        courseCount += 1;
        courses[courseCount] = Course({
            metadataHash: _metadataHash,
            rewardAmount: _rewardAmount,
            educator: msg.sender,
            active: true
        });

        emit CourseCreated(courseCount, msg.sender);
    }

    // -------------------- ENROLLMENT --------------------

    function enroll(uint256 _courseId) external {
        require(courses[_courseId].active, "Course inactive");

        enrollmentCount += 1;
        enrollments[enrollmentCount] = Enrollment({
            courseId: _courseId,
            learner: msg.sender,
            completedMilestones: 0,
            totalRewardClaimed: 0
        });

        emit LearnerEnrolled(enrollmentCount, _courseId, msg.sender);
    }

    // -------------------- MILESTONE COMPLETION --------------------

    function completeMilestone(uint256 _enrollmentId) external {
        Enrollment storage e = enrollments[_enrollmentId];
        require(msg.sender == e.learner, "Not enrolled learner");

        e.completedMilestones += 1;

        emit MilestoneCompleted(_enrollmentId, courses[e.courseId].rewardAmount);
    }

    // -------------------- REWARD CLAIM --------------------

    function claimReward(uint256 _enrollmentId) external {
        Enrollment storage e = enrollments[_enrollmentId];
        require(msg.sender == e.learner, "Not learner");

        uint256 claimable = e.completedMilestones * courses[e.courseId].rewardAmount - e.totalRewardClaimed;
        require(claimable > 0, "No reward to claim");
        require(address(this).balance >= claimable, "Insufficient balance");

        e.totalRewardClaimed += claimable;
        payable(msg.sender).transfer(claimable);

        emit RewardClaimed(_enrollmentId, msg.sender, claimable);
    }

    // -------------------- FUNDING --------------------

    receive() external payable {}
}