Merchandising Revenue Contract
What it does:
Automatically collects revenue from merchandise sales and distributes earnings to creators, brands, designers, manufacturers, and partners based on predefined on-chain splits.
Why it matters:
Eliminates manual accounting, prevents disputes over revenue shares, ensures instant and fair payouts, and enables trustless collaboration across global teams.
How it works:
-
Merch collaborators register payout addresses and revenue percentages
-
Each merch item or campaign is linked to a revenue split configuration
-
Sales payments (ETH or tokens) are sent directly to the smart contract
-
Smart contract calculates and allocates earnings to each stakeholder
-
Funds accumulate in on-chain balances per collaborator
-
Collaborators withdraw earnings independently at any time
-
All sales, splits, and payouts are transparent and auditable
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title MerchandisingRevenue
* @author Nam
* @notice Collects merch sales revenue and splits it automatically on-chain
*/
contract MerchandisingRevenue is Ownable {
struct Collaborator {
address payable wallet;
uint256 share; // percentage (out of 10000 = 100%)
}
struct MerchCampaign {
bool active;
Collaborator[] collaborators;
mapping(address => uint256) pendingETH;
mapping(address => mapping(address => uint256)) pendingToken;
// token => collaborator => amount
}
uint256 public campaignCount;
mapping(uint256 => MerchCampaign) private campaigns;
// -------------------- EVENTS --------------------
event CampaignCreated(uint256 indexed campaignId);
event SaleRecorded(uint256 indexed campaignId, address indexed buyer, uint256 amount);
event Withdrawal(address indexed collaborator, address token, uint256 amount);
// -------------------- CAMPAIGN SETUP --------------------
function createCampaign(
address[] calldata _wallets,
uint256[] calldata _shares
) external onlyOwner {
require(_wallets.length == _shares.length, "Length mismatch");
require(_wallets.length > 0, "No collaborators");
uint256 totalShare;
campaignCount++;
MerchCampaign storage c = campaigns[campaignCount];
c.active = true;
for (uint256 i = 0; i 0, "No payment");
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(msg.sender, address(0), amount);
}
function withdrawToken(uint256 _campaignId, address _token) external {
MerchCampaign storage c = campaigns[_campaignId];
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(msg.sender, _token, amount);
}
// -------------------- ADMIN --------------------
function setCampaignActive(uint256 _campaignId, bool _active) external onlyOwner {
campaigns[_campaignId].active = _active;
}
}