APE Price: $1.03 (-4.14%)

Contract Diff Checker

Contract Name:
EnhancedPodcastRewards

Contract Source Code:

File 1 of 1 : EnhancedPodcastRewards

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

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

interface IAnalytics {
    function getEpisodeStats(uint256 episodeId) external view returns (
        uint256 totalListens,
        uint256 uniqueListeners,
        uint256 lastListenTime,
        uint256 averageCompletionRate
    );
}

contract EnhancedPodcastRewards {
    address public owner;
    IERC20 public rewardToken;
    IAnalytics public analyticsContract;
    
    // Enhanced reward parameters
    uint256 public constant MIN_COMPLETION_RATE = 80;
    uint256 public baseReward = 1e18;                 // 1 DIXO base reward
    uint256 public streakBonus = 2e17;               // 0.2 DIXO per day streak
    uint256 public maxStreakBonus = 5;               // Max 5 days streak
    uint256 public episodeBonus = 5e17;              // 0.5 DIXO per 5 episodes
    uint256 public monthlyListenTarget = 20;         // Monthly target
    uint256 public monthlyBonus = 5e18;              // 5 DIXO monthly bonus
    
    // Advanced bonus features
    uint256 public genreExplorationBonus = 3e17;     // 0.3 DIXO for new genre
    uint256 public bingeBonusThreshold = 3;          // Episodes for binge bonus
    uint256 public bingeBonusAmount = 4e17;          // 0.4 DIXO for binge
    uint256 public primeTimeBonus = 2e17;            // 0.2 DIXO for prime time
    uint256 public earlyListenerBonus = 1e18;        // 1 DIXO for early listening
    
    struct UserRewards {
        uint256 totalEarned;
        uint256 currentStreak;
        uint256 lastRewardTime;
        uint256 monthlyListenCount;
        uint256 lastMonthRewarded;
        uint256 episodesCompleted;
        uint256 lastBingeCount;
        uint256 lastBingeTimestamp;
        mapping(uint256 => bool) episodeRewarded;
        mapping(string => bool) genresExplored;
        uint256 genreCount;
    }
    
    // Time windows
    uint256 public constant PRIME_TIME_START = 18 hours; // 6 PM
    uint256 public constant PRIME_TIME_END = 23 hours;   // 11 PM
    uint256 public constant EARLY_LISTENER_THRESHOLD = 24 hours;
    
    mapping(address => UserRewards) public userRewards;
    mapping(uint256 => uint256) public episodeReleaseTime;
    mapping(uint256 => string) public episodeGenre;
    
    event RewardDistributed(
        address indexed listener,
        uint256 amount,
        string rewardType,
        uint256 timestamp
    );
    
    event BonusParametersUpdated(
        uint256 baseReward,
        uint256 streakBonus,
        uint256 genreBonus,
        uint256 bingeBonus,
        uint256 primeTimeBonus,
        uint256 earlyListenerBonus
    );
    
    constructor(address _rewardToken, address _analyticsContract) {
        rewardToken = IERC20(_rewardToken);
        analyticsContract = IAnalytics(_analyticsContract);
        owner = msg.sender;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    // Helper function to replace Math.min
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }
    
    function distributeReward(
        address listener,
        uint256 episodeId,
        uint256 completionRate
    ) external returns (uint256 totalReward) {
        require(msg.sender == address(analyticsContract), "Only analytics contract");
        require(completionRate >= MIN_COMPLETION_RATE, "Completion rate too low");
        
        UserRewards storage rewards = userRewards[listener];
        require(!rewards.episodeRewarded[episodeId], "Already rewarded");
        
        // Base reward calculation
        totalReward = (baseReward * completionRate) / 100;
        emit RewardDistributed(listener, totalReward, "BASE_REWARD", block.timestamp);
        
        // Streak bonus
        if (block.timestamp - rewards.lastRewardTime <= 1 days) {
            rewards.currentStreak = min(rewards.currentStreak + 1, maxStreakBonus); // Fixed Math.min
            uint256 streakBonusAmount = streakBonus * (rewards.currentStreak - 1);
            totalReward += streakBonusAmount;
            emit RewardDistributed(listener, streakBonusAmount, "STREAK_BONUS", block.timestamp);
        } else {
            rewards.currentStreak = 1;
        }
        
        // Genre exploration bonus
        string memory currentGenre = episodeGenre[episodeId];
        if (bytes(currentGenre).length > 0 && !rewards.genresExplored[currentGenre]) {
            rewards.genresExplored[currentGenre] = true;
            rewards.genreCount++;
            if (rewards.genreCount > 1) {
                totalReward += genreExplorationBonus;
                emit RewardDistributed(listener, genreExplorationBonus, "GENRE_BONUS", block.timestamp);
            }
        }
        
        // Binge listening bonus
        if (block.timestamp - rewards.lastBingeTimestamp <= 24 hours) {
            rewards.lastBingeCount++;
            if (rewards.lastBingeCount >= bingeBonusThreshold) {
                totalReward += bingeBonusAmount;
                rewards.lastBingeCount = 0;
                emit RewardDistributed(listener, bingeBonusAmount, "BINGE_BONUS", block.timestamp);
            }
        } else {
            rewards.lastBingeCount = 1;
        }
        rewards.lastBingeTimestamp = block.timestamp;
        
        // Prime time bonus
        uint256 timeOfDay = (block.timestamp % 86400);
        if (timeOfDay >= PRIME_TIME_START && timeOfDay <= PRIME_TIME_END) {
            totalReward += primeTimeBonus;
            emit RewardDistributed(listener, primeTimeBonus, "PRIME_TIME_BONUS", block.timestamp);
        }
        
        // Early listener bonus
        if (episodeReleaseTime[episodeId] > 0 && 
            block.timestamp - episodeReleaseTime[episodeId] <= EARLY_LISTENER_THRESHOLD) {
            totalReward += earlyListenerBonus;
            emit RewardDistributed(listener, earlyListenerBonus, "EARLY_LISTENER_BONUS", block.timestamp);
        }
        
        // Update state
        rewards.episodeRewarded[episodeId] = true;
        rewards.lastRewardTime = block.timestamp;
        rewards.totalEarned += totalReward;
        rewards.episodesCompleted++;
        
        require(rewardToken.transfer(listener, totalReward), "Reward transfer failed");
        
        return totalReward;
    }
    
    // Admin functions
    function setEpisodeMetadata(
        uint256 episodeId,
        uint256 releaseTime,
        string calldata genre
    ) external onlyOwner {
        episodeReleaseTime[episodeId] = releaseTime;
        episodeGenre[episodeId] = genre;
    }
    
    function updateBonusParameters(
        uint256 _baseReward,
        uint256 _streakBonus,
        uint256 _genreBonus,
        uint256 _bingeBonus,
        uint256 _primeTimeBonus,
        uint256 _earlyListenerBonus
    ) external onlyOwner {
        baseReward = _baseReward;
        streakBonus = _streakBonus;
        genreExplorationBonus = _genreBonus;
        bingeBonusAmount = _bingeBonus;
        primeTimeBonus = _primeTimeBonus;
        earlyListenerBonus = _earlyListenerBonus;
        
        emit BonusParametersUpdated(
            _baseReward,
            _streakBonus,
            _genreBonus,
            _bingeBonus,
            _primeTimeBonus,
            _earlyListenerBonus
        );
    }
    
    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "Invalid address");
        owner = newOwner;
    }
    
    // View functions
    function getUserStats(address user) external view returns (
        uint256 totalEarned,
        uint256 currentStreak,
        uint256 genreCount,
        uint256 lastBingeCount,
        uint256 episodesCompleted
    ) {
        UserRewards storage rewards = userRewards[user];
        return (
            rewards.totalEarned,
            rewards.currentStreak,
            rewards.genreCount,
            rewards.lastBingeCount,
            rewards.episodesCompleted
        );
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):