Contract · Payments
Payment Splitter
Split incoming ETH or ERC20 payments across N payees weighted by shares.
Payments
~1.2M gas
Ethereum
Post-deploy checklist
- Verify constructor payees and shares match your term sheet
- Send a small test payment and confirm balances
- Share the release() flow with each payee
- Document total shares so future readers can audit percentages
Configure & deploy
One address per payee, comma-separated. Order must align with shares.
Relative weights, not percentages. 50,30,20 means 50% / 30% / 20% of total 100.
Use cases
- Split royalties across a team
- Distribute revenue to token holders
- Fund multiple grant recipients
PaymentSplitter.solsolidity
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0 (Wizard / Contracts MCP patterns)
// Compiled with Solidity ^0.8.24 — Ethereum Toolset browser solc
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/// @notice Share-weighted ETH/ERC20 splitter (OZ PaymentSplitter pattern for Contracts v5).
contract PaymentSplitter {
using Address for address payable;
using SafeERC20 for IERC20;
event PayeeAdded(address account, uint256 shares);
event PaymentReleased(address to, uint256 amount);
event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);
event PaymentReceived(address from, uint256 amount);
uint256 private _totalShares;
uint256 private _totalReleased;
mapping(address => uint256) private _shares;
mapping(address => uint256) private _released;
mapping(IERC20 => uint256) private _erc20TotalReleased;
mapping(IERC20 => mapping(address => uint256)) private _erc20Released;
address[] private _payees;
constructor(address[] memory payees, uint256[] memory shares_) payable {
require(payees.length == shares_.length, "payees/shares length mismatch");
require(payees.length > 0, "no payees");
for (uint256 i = 0; i < payees.length; i++) {
_addPayee(payees[i], shares_[i]);
}
}
receive() external payable {
emit PaymentReceived(msg.sender, msg.value);
}
function totalShares() public view returns (uint256) { return _totalShares; }
function totalReleased() public view returns (uint256) { return _totalReleased; }
function shares(address account) public view returns (uint256) { return _shares[account]; }
function released(address account) public view returns (uint256) { return _released[account]; }
function payee(uint256 index) public view returns (address) { return _payees[index]; }
function releasable(address account) public view returns (uint256) {
uint256 totalReceived = address(this).balance + _totalReleased;
return _pendingPayment(account, totalReceived, _released[account]);
}
function release(address payable account) public {
require(_shares[account] > 0, "account has no shares");
uint256 payment = releasable(account);
require(payment != 0, "account is not due payment");
_released[account] += payment;
_totalReleased += payment;
account.sendValue(payment);
emit PaymentReleased(account, payment);
}
function release(IERC20 token, address account) public {
require(_shares[account] > 0, "account has no shares");
uint256 totalReceived = token.balanceOf(address(this)) + _erc20TotalReleased[token];
uint256 payment = _pendingPayment(account, totalReceived, _erc20Released[token][account]);
require(payment != 0, "account is not due payment");
_erc20Released[token][account] += payment;
_erc20TotalReleased[token] += payment;
token.safeTransfer(account, payment);
emit ERC20PaymentReleased(token, account, payment);
}
function _pendingPayment(address account, uint256 totalReceived, uint256 alreadyReleased) private view returns (uint256) {
return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;
}
function _addPayee(address account, uint256 shares_) private {
require(account != address(0), "zero address");
require(shares_ > 0, "shares are 0");
require(_shares[account] == 0, "account already has shares");
_payees.push(account);
_shares[account] = shares_;
_totalShares += shares_;
emit PayeeAdded(account, shares_);
}
}
