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
);
}
}