Creator Subscription Wallet

What it does:
Manages on-chain subscription payments that allow fans to support creators with recurring deposits while giving creators programmable control over access, payouts, and cancellations.

Why it matters:
Eliminates platform fees, removes payment censorship risk, enables global subscriptions without banks, and gives creators full custody and transparency over recurring revenue.

How it works:

  • Creators deploy or register a subscription wallet with pricing and billing interval

  • Fans subscribe by depositing funds into the contract

  • Subscription validity is tracked on-chain by time and balance

  • Creators can withdraw earned subscription revenue at any time

  • Subscribers can cancel to stop future charges without trust assumptions

  • Expired or underfunded subscriptions automatically lose access

  • Integrates with gated content, communities, APIs, newsletters, or live streams

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

/**
 * @title CreatorSubscriptionWallet
 * @author Nam
 * @notice On-chain recurring subscription wallet for creators
 */
contract CreatorSubscriptionWallet {

    address public creator;
    uint256 public pricePerInterval;     // wei
    uint256 public billingInterval;       // seconds

    uint256 public subscriberCount;

    constructor(uint256 _pricePerInterval, uint256 _billingInterval) {
        require(_pricePerInterval > 0, "Invalid price");
        require(_billingInterval > 0, "Invalid interval");
        creator = msg.sender;
        pricePerInterval = _pricePerInterval;
        billingInterval = _billingInterval;
    }

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

    struct Subscription {
        uint256 balance;
        uint256 lastCharged;
        bool active;
    }

    mapping(address => Subscription) public subscriptions;

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

    event Subscribed(address indexed subscriber, uint256 amount);
    event Charged(address indexed subscriber, uint256 amount, uint256 nextChargeTime);
    event Cancelled(address indexed subscriber);
    event CreatorWithdraw(uint256 amount);

    // -------------------- SUBSCRIPTION MANAGEMENT --------------------

    function subscribe() external payable {
        require(msg.value >= pricePerInterval, "Insufficient deposit");

        Subscription storage s = subscriptions[msg.sender];

        if (!s.active) {
            subscriberCount += 1;
            s.lastCharged = block.timestamp;
            s.active = true;
        }

        s.balance += msg.value;
        emit Subscribed(msg.sender, msg.value);
    }

    function cancelSubscription() external {
        Subscription storage s = subscriptions[msg.sender];
        require(s.active, "Not active");

        s.active = false;
        emit Cancelled(msg.sender);
    }

    // -------------------- BILLING LOGIC --------------------

    function charge(address _subscriber) public {
        Subscription storage s = subscriptions[_subscriber];
        require(s.active, "Inactive subscription");

        uint256 elapsed = block.timestamp - s.lastCharged;
        uint256 intervals = elapsed / billingInterval;
        require(intervals > 0, "Nothing to charge");

        uint256 amount = intervals * pricePerInterval;
        require(s.balance >= amount, "Insufficient balance");

        s.balance -= amount;
        s.lastCharged += intervals * billingInterval;

        emit Charged(
            _subscriber,
            amount,
            s.lastCharged + billingInterval
        );
    }

    function batchCharge(address[] calldata _subscribers) external {
        for (uint256 i = 0; i < _subscribers.length; i++) {
            if (subscriptions[_subscribers[i]].active) {
                charge(_subscribers[i]);
            }
        }
    }

    // -------------------- CREATOR WITHDRAW --------------------

    function withdraw(uint256 _amount) external {
        require(msg.sender == creator, "Not creator");
        require(_amount = pricePerInterval;
    }

    function nextChargeTime(address _subscriber) external view returns (uint256) {
        return subscriptions[_subscriber].lastCharged + billingInterval;
    }
}