// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; interface IERC20 { function balanceOf(address) external view returns (uint256); function transfer(address, uint256) external returns (bool); } /// @notice Fixed recipients and basis-point shares. No owner or upgrade path. contract FissaraSplit { string public name; address[] public payees; uint256[] public shares; mapping(address => uint256) public credit; uint256 public reserved; uint256 private locked; event Received(address indexed from, uint256 amount); event Paid(address indexed asset, address indexed payee, uint256 amount); event Deferred(address indexed payee, uint256 amount); modifier nonReentrant() { require(locked == 0, "Reentrant"); locked = 1; _; locked = 0; } constructor(string memory label, address[] memory recipients, uint256[] memory weights) { require(bytes(label).length <= 64, "Name too long"); require(recipients.length >= 2 && recipients.length <= 100 && recipients.length == weights.length, "Invalid payees"); uint256 sum; for (uint256 i; i < recipients.length; i++) { require(recipients[i] != address(0) && recipients[i] != address(this) && weights[i] > 0, "Invalid payee"); for (uint256 j; j < i; j++) require(recipients[i] != recipients[j], "Duplicate payee"); sum += weights[i]; } require(sum == 10000, "Shares must total 10000"); name = label; payees = recipients; shares = weights; } receive() external payable { emit Received(msg.sender, msg.value); } function configuration() external view returns (address[] memory, uint256[] memory) { return (payees, shares); } function distribute() external nonReentrant { uint256 amount = address(this).balance - reserved; require(amount > 0, "Nothing to distribute"); uint256 allocated; for (uint256 i; i < payees.length; i++) { uint256 part = i == payees.length - 1 ? amount - allocated : amount / 10000 * shares[i] + amount % 10000 * shares[i] / 10000; allocated += part; if (part == 0) continue; (bool ok,) = payable(payees[i]).call{value: part, gas: 50000}(""); if (ok) emit Paid(address(0), payees[i], part); else { credit[payees[i]] += part; reserved += part; emit Deferred(payees[i], part); } } } function withdraw(address payable destination) external nonReentrant { require(destination != address(0), "Zero destination"); uint256 amount = credit[msg.sender]; require(amount > 0, "No credit"); credit[msg.sender] = 0; reserved -= amount; (bool ok,) = destination.call{value: amount}(""); require(ok, "Transfer failed"); emit Paid(address(0), msg.sender, amount); } function distributeToken(address token) external nonReentrant { require(token.code.length > 0, "Not a token"); uint256 amount = IERC20(token).balanceOf(address(this)); require(amount > 0, "No balance"); uint256 allocated; for (uint256 i; i < payees.length; i++) { uint256 part = i == payees.length - 1 ? amount - allocated : amount / 10000 * shares[i] + amount % 10000 * shares[i] / 10000; allocated += part; if (part == 0) continue; (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.transfer, (payees[i], part))); require(ok && (data.length == 0 || abi.decode(data, (bool))), "Token transfer failed"); emit Paid(token, payees[i], part); } } } /// @notice One atomic ETH payout. No retained funds: failed recipients revert the transaction. contract FissaraBeam { event Paid(address indexed payee, uint256 amount); constructor(address[] memory recipients, uint256[] memory amounts) payable { require(recipients.length > 0 && recipients.length <= 100 && recipients.length == amounts.length, "Invalid recipients"); uint256 sum; for (uint256 i; i < recipients.length; i++) { require(recipients[i] != address(0) && amounts[i] > 0, "Invalid payout"); sum += amounts[i]; } require(sum == msg.value, "Wrong value"); for (uint256 i; i < recipients.length; i++) { (bool ok,) = payable(recipients[i]).call{value: amounts[i], gas: 50000}(""); require(ok, "Recipient rejected ETH"); emit Paid(recipients[i], amounts[i]); } } }