Contract Name:
DixoPodcastEngagement
Contract Source Code:
File 1 of 1 : DixoPodcastEngagement
// 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);
}
contract DixoPodcastEngagement {
IERC20 public dixoToken;
address public owner;
struct UserEngagement {
mapping(uint256 => bool) hasRatedEpisode;
mapping(uint256 => bool) hasGivenFeedback;
mapping(uint256 => bool) hasSharedEpisode;
mapping(bytes32 => uint256) lastEngagementByType;
uint256 totalRewardsEarned;
uint256 episodesEngaged;
uint256 lastEngagementTime;
}
struct BasicAnalytics {
uint256 totalFeedbacks;
uint256 totalRatings;
uint256 totalShares;
uint256 averageRating;
uint256 totalRewardsDistributed;
uint256 uniqueEngagers;
uint256 publishDate;
uint256 firstEngagementTime;
uint256 lastEngagementTime;
}
struct RewardType {
uint256 amount;
bool isActive;
uint256 cooldownPeriod;
string description;
}
mapping(bytes32 => RewardType) public rewardTypes;
bytes32[] public allRewardTypes;
mapping(address => UserEngagement) private userEngagements;
mapping(uint256 => string[]) public episodeFeedbacks;
mapping(uint256 => uint256) public episodeRatingsSum;
mapping(uint256 => uint256) public episodeRatingsCount;
mapping(uint256 => string) public episodeSpotifyIds;
mapping(uint256 => string) public episodeTitles;
mapping(uint256 => BasicAnalytics) public episodeAnalytics;
uint256 public episodeCount;
event FeedbackSubmitted(address indexed user, uint256 indexed episodeId, string feedback);
event RatingSubmitted(address indexed user, uint256 indexed episodeId, uint256 rating);
event RewardDistributed(address indexed user, uint256 amount, string rewardType);
event EpisodeAdded(uint256 indexed episodeId, string spotifyId, string title);
event RewardTypeAdded(bytes32 indexed rewardTypeId, uint256 amount, uint256 cooldownPeriod);
event RewardTypeUpdated(bytes32 indexed rewardTypeId, uint256 newAmount, uint256 newCooldownPeriod);
event EngagementRecorded(address indexed user, uint256 indexed episodeId, bytes32 indexed rewardTypeId);
constructor(address _dixoToken) {
dixoToken = IERC20(_dixoToken);
owner = msg.sender;
_addRewardType("FEEDBACK", 3 * 10**18, 0, "Episode feedback submission");
_addRewardType("RATING", 3 * 10**18, 0, "Episode rating submission");
_addRewardType("SOCIAL_SHARE", 2 * 10**18, 1 days, "Social media share");
}
modifier validEpisodeId(uint256 episodeId) {
require(episodeId > 0 && episodeId <= episodeCount, "Invalid episode ID");
require(bytes(episodeSpotifyIds[episodeId]).length > 0, "Episode not found");
_;
}
modifier validRating(uint256 rating) {
require(rating >= 1 && rating <= 5, "Rating must be between 1 and 5");
_;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
function _addRewardType(
string memory name,
uint256 amount,
uint256 cooldownPeriod,
string memory description
) private {
bytes32 rewardTypeId = keccak256(abi.encodePacked(name));
rewardTypes[rewardTypeId] = RewardType({
amount: amount,
isActive: true,
cooldownPeriod: cooldownPeriod,
description: description
});
allRewardTypes.push(rewardTypeId);
emit RewardTypeAdded(rewardTypeId, amount, cooldownPeriod);
}
function addRewardType(
string calldata name,
uint256 amount,
uint256 cooldownPeriod,
string calldata description
) external onlyOwner {
_addRewardType(name, amount, cooldownPeriod, description);
}
function updateRewardType(
bytes32 rewardTypeId,
uint256 newAmount,
uint256 newCooldownPeriod,
bool isActive
) external onlyOwner {
require(rewardTypes[rewardTypeId].amount > 0, "Reward type does not exist");
rewardTypes[rewardTypeId].amount = newAmount;
rewardTypes[rewardTypeId].cooldownPeriod = newCooldownPeriod;
rewardTypes[rewardTypeId].isActive = isActive;
emit RewardTypeUpdated(rewardTypeId, newAmount, newCooldownPeriod);
}
function recordEngagement(
address user,
uint256 episodeId,
bytes32 rewardTypeId
) external onlyOwner returns (bool) {
RewardType storage rewardType = rewardTypes[rewardTypeId];
require(rewardType.isActive, "Reward type not active");
UserEngagement storage userEng = userEngagements[user];
if (rewardType.cooldownPeriod > 0) {
require(
block.timestamp >= userEng.lastEngagementByType[rewardTypeId] + rewardType.cooldownPeriod,
"Cooldown period not elapsed"
);
}
userEng.lastEngagementByType[rewardTypeId] = block.timestamp;
userEng.totalRewardsEarned += rewardType.amount;
userEng.lastEngagementTime = block.timestamp;
require(dixoToken.transfer(user, rewardType.amount), "Reward transfer failed");
emit EngagementRecorded(user, episodeId, rewardTypeId);
emit RewardDistributed(user, rewardType.amount, rewardTypes[rewardTypeId].description);
return true;
}
function hasGivenFeedback(address user, uint256 episodeId) public view returns (bool) {
require(episodeId > 0 && episodeId <= episodeCount, "Invalid episode ID");
return userEngagements[user].hasGivenFeedback[episodeId];
}
function hasRatedEpisode(address user, uint256 episodeId) public view returns (bool) {
require(episodeId > 0 && episodeId <= episodeCount, "Invalid episode ID");
return userEngagements[user].hasRatedEpisode[episodeId];
}
function submitFeedback(uint256 episodeId, string calldata feedback)
external
validEpisodeId(episodeId)
{
require(bytes(feedback).length >= 50, "Feedback must be at least 50 characters");
require(!userEngagements[msg.sender].hasGivenFeedback[episodeId], "Already gave feedback");
UserEngagement storage userEng = userEngagements[msg.sender];
BasicAnalytics storage analytics = episodeAnalytics[episodeId];
userEng.hasGivenFeedback[episodeId] = true;
userEng.episodesEngaged += 1;
userEng.lastEngagementTime = block.timestamp;
userEng.totalRewardsEarned += rewardTypes[keccak256(abi.encodePacked("FEEDBACK"))].amount;
episodeFeedbacks[episodeId].push(feedback);
analytics.totalFeedbacks += 1;
analytics.totalRewardsDistributed += rewardTypes[keccak256(abi.encodePacked("FEEDBACK"))].amount;
analytics.uniqueEngagers += 1;
analytics.lastEngagementTime = block.timestamp;
if (analytics.firstEngagementTime == 0) {
analytics.firstEngagementTime = block.timestamp;
}
require(dixoToken.transfer(msg.sender, rewardTypes[keccak256(abi.encodePacked("FEEDBACK"))].amount), "Reward transfer failed");
emit FeedbackSubmitted(msg.sender, episodeId, feedback);
emit RewardDistributed(msg.sender, rewardTypes[keccak256(abi.encodePacked("FEEDBACK"))].amount, "FEEDBACK");
}
function submitRating(uint256 episodeId, uint256 rating)
external
validEpisodeId(episodeId)
validRating(rating)
{
require(!userEngagements[msg.sender].hasRatedEpisode[episodeId], "Already rated");
UserEngagement storage userEng = userEngagements[msg.sender];
BasicAnalytics storage analytics = episodeAnalytics[episodeId];
userEng.hasRatedEpisode[episodeId] = true;
userEng.episodesEngaged += 1;
userEng.lastEngagementTime = block.timestamp;
userEng.totalRewardsEarned += rewardTypes[keccak256(abi.encodePacked("RATING"))].amount;
episodeRatingsSum[episodeId] += rating;
episodeRatingsCount[episodeId] += 1;
analytics.totalRatings += 1;
analytics.averageRating = episodeRatingsSum[episodeId] / episodeRatingsCount[episodeId];
analytics.totalRewardsDistributed += rewardTypes[keccak256(abi.encodePacked("RATING"))].amount;
analytics.uniqueEngagers += 1;
analytics.lastEngagementTime = block.timestamp;
if (analytics.firstEngagementTime == 0) {
analytics.firstEngagementTime = block.timestamp;
}
require(dixoToken.transfer(msg.sender, rewardTypes[keccak256(abi.encodePacked("RATING"))].amount), "Reward transfer failed");
emit RatingSubmitted(msg.sender, episodeId, rating);
emit RewardDistributed(msg.sender, rewardTypes[keccak256(abi.encodePacked("RATING"))].amount, "RATING");
}
function addEpisode(string calldata spotifyId, string calldata title)
external
onlyOwner
{
episodeCount += 1;
episodeSpotifyIds[episodeCount] = spotifyId;
episodeTitles[episodeCount] = title;
BasicAnalytics storage analytics = episodeAnalytics[episodeCount];
analytics.publishDate = block.timestamp;
emit EpisodeAdded(episodeCount, spotifyId, title);
}
function getEpisodeStats(uint256 episodeId)
external
view
validEpisodeId(episodeId)
returns (
string memory spotifyId,
string memory title,
uint256 totalFeedbacks,
uint256 totalRatings,
uint256 averageRating,
uint256 totalRewards,
uint256 uniqueEngagers
)
{
BasicAnalytics storage analytics = episodeAnalytics[episodeId];
return (
episodeSpotifyIds[episodeId],
episodeTitles[episodeId],
analytics.totalFeedbacks,
analytics.totalRatings,
analytics.averageRating,
analytics.totalRewardsDistributed,
analytics.uniqueEngagers
);
}
function getUserStats(address user)
external
view
returns (
uint256 totalRewards,
uint256 episodesEngaged,
uint256 lastEngagement
)
{
UserEngagement storage engagement = userEngagements[user];
return (
engagement.totalRewardsEarned,
engagement.episodesEngaged,
engagement.lastEngagementTime
);
}
function getEpisodeFeedbacks(uint256 episodeId)
external
view
validEpisodeId(episodeId)
returns (string[] memory)
{
return episodeFeedbacks[episodeId];
}
function getAllEpisodes()
external
view
returns (uint256[] memory ids, string[] memory titles)
{
ids = new uint256[](episodeCount);
titles = new string[](episodeCount);
for (uint256 i = 1; i <= episodeCount; i++) {
ids[i-1] = i;
titles[i-1] = episodeTitles[i];
}
return (ids, titles);
}
function getRewardTypeInfo(bytes32 rewardTypeId)
external
view
returns (
uint256 amount,
bool isActive,
uint256 cooldownPeriod,
string memory description
)
{
RewardType storage reward = rewardTypes[rewardTypeId];
return (reward.amount, reward.isActive, reward.cooldownPeriod, reward.description);
}
function getAllRewardTypes()
external
view
returns (
bytes32[] memory ids,
uint256[] memory amounts,
bool[] memory activeStates,
uint256[] memory cooldowns,
string[] memory descriptions
)
{
uint256 length = allRewardTypes.length;
ids = new bytes32[](length);
amounts = new uint256[](length);
activeStates = new bool[](length);
cooldowns = new uint256[](length);
descriptions = new string[](length);
for (uint256 i = 0; i < length; i++) {
bytes32 rewardTypeId = allRewardTypes[i];
RewardType storage reward = rewardTypes[rewardTypeId];
ids[i] = rewardTypeId;
amounts[i] = reward.amount;
activeStates[i] = reward.isActive;
cooldowns[i] = reward.cooldownPeriod;
descriptions[i] = reward.description;
}
}
function updateRewardToken(address newToken) external onlyOwner {
dixoToken = IERC20(newToken);
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "Invalid address");
owner = newOwner;
}
}