// SPDX-License-Identifier: MIT pragma solidity 0.8.26; /// @notice Unanimously accepted native-BNB net settlements. No external price feed or credit issuance. contract Owe { uint256 public constant FEE_BPS = 25; uint256 public constant MAX_PARTICIPANTS = 24; address public immutable treasury; uint256 public nextId = 1; uint256 public totalEscrow; uint256 public totalCredits; uint256 private entered = 1; enum Status { Missing, Open, Settled, Cancelled } struct Settlement { address creator; uint64 deadline; uint32 count; uint32 acknowledgements; uint32 debtors; uint32 fundedDebtors; uint256 principal; uint256 deposited; bytes32 memoHash; Status status; } mapping(uint256 => Settlement) public settlements; mapping(uint256 => address[]) private members; mapping(uint256 => mapping(address => int256)) public netBalance; mapping(uint256 => mapping(address => bool)) public isMember; mapping(uint256 => mapping(address => bool)) public acknowledged; mapping(uint256 => mapping(address => uint256)) public funded; mapping(address => uint256) public credit; event Created(uint256 indexed id, address indexed creator, uint64 deadline, bytes32 memoHash); event Acknowledged(uint256 indexed id, address indexed participant); event Funded(uint256 indexed id, address indexed participant, uint256 amount); event Settled(uint256 indexed id, uint256 principal, uint256 fee); event Cancelled(uint256 indexed id, address indexed by); event Refunded(uint256 indexed id, address indexed participant, uint256 amount); event Withdrawn(address indexed owner, address indexed destination, uint256 amount); modifier guarded() { require(entered == 1, "REENTRANCY"); entered = 2; _; entered = 1; } constructor(address recipient) { require(recipient != address(0) && recipient != address(this), "TREASURY"); treasury = recipient; } function feeFor(uint256 principal) public pure returns (uint256) { return principal * FEE_BPS / 10000; } function participants(uint256 id) external view returns (address[] memory) { return members[id]; } function create(address[] calldata people, int256[] calldata balances, uint64 deadline, bytes32 memoHash) external guarded returns (uint256 id) { uint256 n = people.length; require(n >= 2 && n <= MAX_PARTICIPANTS && n == balances.length, "PARTICIPANTS"); require(deadline >= block.timestamp + 60 && deadline <= block.timestamp + 90 days, "DEADLINE"); int256 sum; uint256 principal; uint32 debtors; bool creatorIncluded; id = nextId++; for (uint256 i; i < n; ++i) { address who = people[i]; int256 amount = balances[i]; require(who != address(0) && who != address(this) && !isMember[id][who], "MEMBER"); require(amount != 0 && amount >= -int256(1e30) && amount <= int256(1e30), "BALANCE"); isMember[id][who] = true; netBalance[id][who] = amount; members[id].push(who); if (who == msg.sender) creatorIncluded = true; sum += amount; if (amount < 0) { principal += uint256(-amount); ++debtors; } } require(sum == 0 && principal > 0 && creatorIncluded, "NET_SUM_OR_CREATOR"); settlements[id] = Settlement(msg.sender, deadline, uint32(n), 0, debtors, 0, principal, 0, memoHash, Status.Open); emit Created(id, msg.sender, deadline, memoHash); } function acknowledge(uint256 id) external guarded { Settlement storage s = settlements[id]; require(s.status == Status.Open && block.timestamp < s.deadline, "NOT_OPEN"); require(isMember[id][msg.sender] && !acknowledged[id][msg.sender], "ACKNOWLEDGEMENT"); acknowledged[id][msg.sender] = true; ++s.acknowledgements; emit Acknowledged(id, msg.sender); } function deposit(uint256 id) external payable guarded { Settlement storage s = settlements[id]; int256 net = netBalance[id][msg.sender]; require(s.status == Status.Open && block.timestamp < s.deadline, "NOT_OPEN"); require(net < 0 && acknowledged[id][msg.sender] && funded[id][msg.sender] == 0, "NOT_DEBTOR_OR_FUNDED"); uint256 due = uint256(-net); uint256 total = due + feeFor(due); require(msg.value == total, "EXACT_FUNDING"); funded[id][msg.sender] = total; s.deposited += total; totalEscrow += total; ++s.fundedDebtors; emit Funded(id, msg.sender, total); } function settle(uint256 id) external guarded { Settlement storage s = settlements[id]; require(s.status == Status.Open && block.timestamp < s.deadline, "NOT_OPEN"); require(s.acknowledgements == s.count && s.fundedDebtors == s.debtors, "NOT_READY"); s.status = Status.Settled; totalEscrow -= s.deposited; uint256 fee = s.deposited - s.principal; for (uint256 i; i < members[id].length; ++i) { address who = members[id][i]; int256 net = netBalance[id][who]; if (net > 0) credit[who] += uint256(net); } credit[treasury] += fee; totalCredits += s.deposited; emit Settled(id, s.principal, fee); } function cancel(uint256 id) external guarded { Settlement storage s = settlements[id]; require(s.status == Status.Open, "NOT_OPEN"); require(block.timestamp >= s.deadline || isMember[id][msg.sender], "NOT_PARTICIPANT"); s.status = Status.Cancelled; emit Cancelled(id, msg.sender); } function refund(uint256 id) external guarded { require(settlements[id].status == Status.Cancelled, "NOT_CANCELLED"); uint256 amount = funded[id][msg.sender]; require(amount > 0, "NO_REFUND"); funded[id][msg.sender] = 0; totalEscrow -= amount; credit[msg.sender] += amount; totalCredits += amount; emit Refunded(id, msg.sender, amount); } function withdraw(address payable destination) external guarded { require(destination != address(0) && destination != address(this), "DESTINATION"); uint256 amount = credit[msg.sender]; require(amount > 0, "NO_CREDIT"); credit[msg.sender] = 0; totalCredits -= amount; (bool ok,) = destination.call{value: amount}(""); require(ok, "TRANSFER_FAILED"); emit Withdrawn(msg.sender, destination, amount); } }