Decentralized Publishing Platform

What it does:
Allows writers and creators to publish articles or digital works in a decentralized manner, with on-chain ownership, monetization, and audience access control.

Why it matters:
Eliminates platform lock-in, censorship risk, and opaque revenue models while giving creators direct ownership of content, readers, and income.

How it works:

  • Creators publish content by registering metadata and ownership on-chain

  • Content itself is stored off-chain (IPFS / Arweave) with on-chain references

  • Access can be free, pay-per-article, subscription-based, or token-gated

  • Readers pay directly to the smart contract for access rights

  • Revenue is distributed automatically to authors and collaborators

  • Ownership and publishing history are immutable and auditable

  • Integrates with wallets, DAOs, NFTs, and external frontends

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

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

/**
 * @title DecentralizedPublishingPlatform
 * @author Nam
 * @notice On-chain publishing with paywalls and revenue distribution
 */
contract DecentralizedPublishingPlatform is Ownable {

    struct Contributor {
        address payable wallet;
        uint256 share; // out of 10000 (100%)
    }

    struct Article {
        string metadataURI;
        uint256 price; // ETH price for access
        bool active;
        Contributor[] contributors;
        mapping(address => bool) hasAccess;
        mapping(address => uint256) pendingETH;
        mapping(address => mapping(address => uint256)) pendingToken;
    }

    uint256 public articleCount;
    mapping(uint256 => Article) private articles;

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

    event ArticlePublished(uint256 indexed articleId, string metadataURI);
    event AccessPurchased(uint256 indexed articleId, address indexed reader);
    event Withdrawal(uint256 indexed articleId, address indexed contributor, address token, uint256 amount);

    // -------------------- PUBLISHING --------------------

    function publishArticle(
        string calldata _metadataURI,
        uint256 _price,
        address[] calldata _wallets,
        uint256[] calldata _shares
    ) external {
        require(_wallets.length == _shares.length, "Length mismatch");
        require(_wallets.length > 0, "No contributors");

        uint256 totalShare;
        articleCount++;
        Article storage a = articles[articleCount];
        a.metadataURI = _metadataURI;
        a.price = _price;
        a.active = true;

        for (uint256 i = 0; i < _wallets.length; i++) {
            totalShare += _shares[i];
            a.contributors.push(
                Contributor(payable(_wallets[i]), _shares[i])
            );
        }

        require(totalShare == 10000, "Shares must total 100%");
        emit ArticlePublished(articleCount, _metadataURI);
    }

    // -------------------- ACCESS --------------------

    function purchaseAccess(uint256 _articleId) external payable {
        Article storage a = articles[_articleId];
        require(a.active, "Article inactive");
        require(!a.hasAccess[msg.sender], "Already accessed");
        require(msg.value == a.price, "Incorrect payment");

        a.hasAccess[msg.sender] = true;

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

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

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