Fan Voting on Content Direction

What it does:
Allows fans to vote on creative decisions such as story arcs, themes, formats, release schedules, or future content directions using on-chain voting.

Why it matters:
Deepens fan engagement, aligns creators with their audience, turns passive followers into stakeholders, and provides transparent, tamper-proof insight into audience preferences.

How it works:

  • Creators define content decisions as on-chain voting proposals

  • Fans gain voting power by holding tokens, NFTs, or active subscriptions

  • Each proposal has clear options, voting weights, and deadlines

  • Votes are recorded immutably on-chain and counted automatically

  • Results are visible in real time and cannot be manipulated

  • Creators can commit to executing winning outcomes publicly

  • Integrates with fan tokens, creator DAOs, and gated communities

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

/**
 * @title FanVotingOnContentDirection
 * @author Nam
 * @notice On-chain fan voting for creator content decisions
 */
contract FanVotingOnContentDirection {

    address public creator;
    uint256 public proposalCount;

    constructor() {
        creator = msg.sender;
    }

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

    struct Proposal {
        string description;
        string[] options;
        uint256 deadline;
        bool executed;
        mapping(uint256 => uint256) votes; // optionIndex => vote count
        mapping(address => bool) hasVoted;
    }

    mapping(uint256 => Proposal) private proposals;
    mapping(address => uint256) public votingPower; // simple off-chain assigned power

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

    event ProposalCreated(uint256 indexed proposalId, string description, uint256 deadline);
    event Voted(uint256 indexed proposalId, address indexed voter, uint256 optionIndex, uint256 weight);
    event ProposalExecuted(uint256 indexed proposalId, uint256 winningOption);

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

    modifier onlyCreator() {
        require(msg.sender == creator, "Not creator");
        _;
    }

    // -------------------- VOTING POWER MANAGEMENT --------------------
    // Can be wired to NFT / token balance in production

    function setVotingPower(address _fan, uint256 _power) external onlyCreator {
        votingPower[_fan] = _power;
    }

    // -------------------- PROPOSAL MANAGEMENT --------------------

    function createProposal(
        string calldata _description,
        string[] calldata _options,
        uint256 _votingDuration
    ) external onlyCreator {
        require(_options.length >= 2, "Need multiple options");

        proposalCount += 1;
        Proposal storage p = proposals[proposalCount];
        p.description = _description;
        p.options = _options;
        p.deadline = block.timestamp + _votingDuration;

        emit ProposalCreated(proposalCount, _description, p.deadline);
    }

    // -------------------- VOTING --------------------

    function vote(uint256 _proposalId, uint256 _optionIndex) external {
        Proposal storage p = proposals[_proposalId];
        require(block.timestamp < p.deadline, "Voting ended");
        require(!p.hasVoted[msg.sender], "Already voted");
        require(_optionIndex <p> 0, "No voting power");

        p.votes[_optionIndex] += weight;
        p.hasVoted[msg.sender] = true;

        emit Voted(_proposalId, msg.sender, _optionIndex, weight);
    }

    // -------------------- EXECUTION --------------------

    function executeProposal(uint256 _proposalId) external onlyCreator {
        Proposal storage p = proposals[_proposalId];
        require(block.timestamp &gt;= p.deadline, "Voting ongoing");
        require(!p.executed, "Already executed");

        uint256 winningOption;
        uint256 highestVotes;

        for (uint256 i = 0; i <p> highestVotes) {
                highestVotes = p.votes[i];
                winningOption = i;
            }
        }

        p.executed = true;
        emit ProposalExecuted(_proposalId, winningOption);
    }

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

    function getProposal(uint256 _proposalId)
        external
        view
        returns (
            string memory description,
            string[] memory options,
            uint256 deadline,
            bool executed
        )
    {
        Proposal storage p = proposals[_proposalId];
        return (p.description, p.options, p.deadline, p.executed);
    }

    function votesForOption(uint256 _proposalId, uint256 _optionIndex)
        external
        view
        returns (uint256)
    {
        return proposals[_proposalId].votes[_optionIndex];
    }
}