AI-Generated Content Ownership

What it does:
Defines, records, and enforces ownership and attribution rules for AI-generated content, including splits between prompt creators, model providers, data licensors, and publishers.

Why it matters:
Clarifies ambiguous IP ownership in AI content creation, prevents disputes, enables fair revenue sharing, and creates legally auditable provenance for AI-assisted works.

How it works:

  • Content is registered with a hash representing the final AI-generated output

  • Contributors (prompt author, model owner, dataset licensor, editor) are listed with fixed ownership shares

  • Smart contract stores immutable attribution and percentage splits

  • Monetization revenue is sent directly to the contract

  • Earnings are automatically allocated to contributors based on ownership shares

  • Contributors withdraw funds independently without trust assumptions

  • Ownership records can be referenced by marketplaces, publishers, and licensing systems

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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title AIGeneratedContentOwnership
 * @author Nam
 * @notice Manages attribution and revenue distribution for AI-generated content
 */
contract AIGeneratedContentOwnership is Ownable {

    struct Contributor {
        address payable wallet;
        uint256 share; // out of 10000 (100%)
        string role;   // prompt, model, dataset, editor, publisher
    }

    struct Content {
        bytes32 contentHash;
        string metadataURI;
        Contributor[] contributors;
        mapping(address => uint256) pendingETH;
        mapping(address => mapping(address => uint256)) pendingToken;
        bool exists;
    }

    uint256 public contentCount;
    mapping(uint256 => Content) private contents;

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

    event ContentRegistered(uint256 indexed contentId, bytes32 contentHash);
    event RevenueReceived(uint256 indexed contentId, address token, uint256 amount);
    event Withdrawal(uint256 indexed contentId, address indexed contributor, address token, uint256 amount);

    // -------------------- CONTENT REGISTRATION --------------------

    function registerContent(
        bytes32 _contentHash,
        string calldata _metadataURI,
        address[] calldata _wallets,
        uint256[] calldata _shares,
        string[] calldata _roles
    ) external onlyOwner {
        require(_wallets.length == _shares.length, "Length mismatch");
        require(_wallets.length == _roles.length, "Role mismatch");
        require(_wallets.length > 0, "No contributors");

        uint256 totalShare;
        contentCount++;
        Content storage c = contents[contentCount];

        c.contentHash = _contentHash;
        c.metadataURI = _metadataURI;
        c.exists = true;

        for (uint256 i = 0; i  0, "No ETH sent");

        for (uint256 i = 0; i  0, "Invalid amount");

        IERC20(_token).transferFrom(msg.sender, address(this), _amount);

        for (uint256 i = 0; i  0, "Nothing to withdraw");

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

        emit Withdrawal(_contentId, msg.sender, address(0), amount);
    }

    function withdrawToken(uint256 _contentId, address _token) external {
        Content storage c = contents[_contentId];
        uint256 amount = c.pendingToken[_token][msg.sender];
        require(amount > 0, "Nothing to withdraw");

        c.pendingToken[_token][msg.sender] = 0;
        IERC20(_token).transfer(msg.sender, amount);

        emit Withdrawal(_contentId, msg.sender, _token, amount);
    }
}