Skip to content
Splitflow

The life of a payment

What happens between money arriving and everyone being paid.

From one payment to many

PayerSplit contractApprovals · freezeRecipients
  1. 1

    Someone pays the split

    Tokens arrive at its address, like any wallet.

  2. 2

    The rule divides it

    The contract computes every share; nobody types amounts.

  3. 3

    Checks run

    Large payouts wait for signatures; frozen ones wait too.

  4. 4

    Everyone is paid

    One transaction pays all and logs a receipt.

The three rules, worked out

Say 2,500 tUSDC arrives.

Fixed percentages

Shares add up to exactly 100%.

RecipientShareReceives
Events & venues40%1,000 tUSDC
Workshop instructors30%750 tUSDC
Hackathon prize pool20%500 tUSDC
Club treasury10%250 tUSDC

Contribution points

Each share is points ÷ total points.

RecipientShareReceives
Léa Tremblay34 / 97876.288661 tUSDC
Omar Haddad28 / 97721.649484 tUSDC
Priya Nair22 / 97567.010309 tUSDC
Jonas Weber13 / 97335.051546 tUSDC

Cover a cost first

A fixed amount first, then the rule.

RecipientShareReceives
Shared rent—350 tUSDC
Growers' collective45%967.5 tUSDC
Rise & Crumb bakery25%537.5 tUSDC
Northside coffee roaster20%430 tUSDC
Community fund10%215 tUSDC

Rounding dust

Shares round down; the few leftover units go to the largest share, shown on the receipt.

Approvals and freezing

Approvals for large payouts

Above a threshold, a payout waits for enough signers, e.g. 2 of 3 from 2,000 tUSDC.

Emergency freeze

The owner or an approver can stop payouts. Payments are held until unfrozen.

The audit trail

Every event, with time, actor, amount and transaction hash. Exports to CSV.

For developers

The demo runs in your browser; its data layer mirrors the contract, ready for wagmi and viem.

Show the contract interface

Contract interface (sketch)

interface ISplit {
  // Rule: fixed basis points (sum 10_000) or points
  function recipients() external view returns (address[] memory, uint256[] memory);
  function coverFirst() external view returns (address, uint256);

  // Anyone can pay the split like any wallet
  receive() external payable;

  // Pays every recipient their share of what is waiting.
  // Rounds down; the dust goes to the largest share.
  function distribute(uint256 amount) external;

  // Payouts >= threshold need `required` approvals
  function propose(uint256 amount) external returns (uint256 id);
  function approve(uint256 id) external; // executes on the last approval

  function freeze() external;   // owner or approver
  function unfreeze() external;

  event PaymentReceived(address indexed from, uint256 amount);
  event Distributed(uint256 amount, address[] to, uint256[] amounts);
  event Frozen(address by);
}

The demo's data layer

  • src/lib/demo/types.tssplits, rules and audit entries, in base units
  • src/lib/demo/allocate.tsthe exact division, dust included
  • src/lib/demo/ops.tspayment, payout, approval, freeze
  • src/lib/demo/chain.tssimulated wallet, blocks and failures
Read the source on GitHub

See it move

Launch the demo