Ad Revenue Distribution Contract

What it does:
Automatically collects advertising revenue and distributes it on-chain to publishers, creators, platforms, and referrers based on predefined allocation rules.

Why it matters:
Removes opaque ad networks, prevents revenue manipulation, enables instant payouts, and gives publishers full transparency into how ad money is calculated and distributed.

How it works:

  • Advertisers deposit campaign funds directly into the smart contract

  • Each ad impression, click, or conversion triggers revenue allocation logic

  • Revenue is split among stakeholders (publisher, creator, platform, referrer) by fixed percentages

  • Funds are accumulated in on-chain balances per stakeholder

  • Stakeholders can withdraw earnings at any time without waiting for monthly settlements

  • All revenue flows, events, and payouts are fully transparent and auditable on-chain

  • Integrates with websites, apps, games, and content platforms via oracles or signed reports

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

/**
 * @title AdRevenueDistribution
 * @author Nam
 * @notice Distributes advertising revenue to multiple stakeholders transparently on-chain
 */
contract AdRevenueDistribution {

    address public admin;
    uint256 public campaignCount;

    constructor() {
        admin = msg.sender;
    }

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

    struct Stakeholder {
        address account;
        uint256 share; // out of 10000 (basis points)
    }

    struct Campaign {
        address advertiser;
        uint256 budget;
        uint256 spent;
        Stakeholder[] stakeholders;
        mapping(address => uint256) balances;
        bool active;
    }

    mapping(uint256 => Campaign) public campaigns;

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

    event CampaignCreated(uint256 indexed campaignId, address indexed advertiser, uint256 budget);
    event RevenueDistributed(uint256 indexed campaignId, uint256 amount);
    event StakeholderWithdrawal(uint256 indexed campaignId, address indexed stakeholder, uint256 amount);
    event CampaignPaused(uint256 indexed campaignId);
    event CampaignResumed(uint256 indexed campaignId);

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

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

    modifier onlyAdvertiser(uint256 _campaignId) {
        require(msg.sender == campaigns[_campaignId].advertiser, "Not advertiser");
        _;
    }

    modifier campaignActive(uint256 _campaignId) {
        require(campaigns[_campaignId].active, "Campaign not active");
        _;
    }

    // -------------------- CAMPAIGN MANAGEMENT --------------------

    function createCampaign(
        Stakeholder[] calldata _stakeholders
    ) external payable returns (uint256) {
        require(msg.value > 0, "Budget required");
        require(_stakeholders.length > 0, "No stakeholders");

        uint256 totalShares;
        for (uint256 i = 0; i < _stakeholders.length; i++) {
            totalShares += _stakeholders[i].share;
        }
        require(totalShares == 10000, "Shares must total 100%");

        campaignCount += 1;
        Campaign storage c = campaigns[campaignCount];
        c.advertiser = msg.sender;
        c.budget = msg.value;
        c.active = true;

        for (uint256 i = 0; i  0, "Invalid amount");
        require(c.spent + _amount <= c.budget, "Budget exceeded");

        c.spent += _amount;

        for (uint256 i = 0; i  0, "No balance");

        c.balances[msg.sender] = 0;
        payable(msg.sender).transfer(amount);

        emit StakeholderWithdrawal(_campaignId, msg.sender, amount);
    }

    function balanceOf(uint256 _campaignId, address _stakeholder) external view returns (uint256) {
        return campaigns[_campaignId].balances[_stakeholder];
    }

    // -------------------- UNUSED BUDGET REFUND --------------------

    function refundRemainingBudget(uint256 _campaignId)
        external
        onlyAdvertiser(_campaignId)
    {
        Campaign storage c = campaigns[_campaignId];
        require(!c.active, "Pause campaign first");

        uint256 remaining = c.budget - c.spent;
        require(remaining > 0, "No remaining budget");

        c.budget = c.spent;
        payable(c.advertiser).transfer(remaining);
    }
}