Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
in reviewhumanPR #136Identity

settlement-engineer: split_settlement — weighted multi-payee fan-out settlement with conservation-proof validators

The merged payments plugins each move value in one shape:

Author

KaranSinghBisht avatar

@KaranSinghBisht

github profile →
Status
In review
Opened on
Jul 9
Branch
hackathon/karansinghbisht-split-settlement

Description

The pitch.

## Motivation

The merged payments plugins each move value in one shape:

- `prepaid_credits` — one debit to **one** payee.
- `escrow` — one payee's payout, gated on delivery/arbitration.
- `empic_escrow` — pull/pubsub escrow for a provider/consumer pair.
- `streaming` — **one** payee metered one tick at a time.

None of them takes a single debit and fans it out to **N payees at once** by
declared shares.

**Scope anchor:** this PR is a self-scoped payments-layer problem, distinct
from problem 03 by that problem's own scope declaration — 03 (streaming
x402) explicitly lists multi-party streams (one payer, many payees) as out
of its scope — following the merged precedent for self-scoped payments work
(#38 arbitrated escrow, #90 EMPIC). Novelty is therefore claimed against
the fan-out settlement shape, which exists nowhere in the merged set. That is the missing primitive behind the most common real
settlement flows: marketplace revenue splits, royalty payouts, ad/network
rev-share, and contributor splits. Doing this naively is exactly where money
leaks — floor every share and the indivisible remainder ("dust") silently
vanishes, or a compromised splitter quietly reweights the payout in its own
favour. This PR adds `split_settlement`: an atomic, exact, weight-locked fan-out
settlement, plus two adversarial validators that make those two failure modes
un-shippable.

## Design

**Money & weights are integers, end to end.** Credits are `int` (matching the
`Money.amount: int` the rest of the layer uses); weights are **positive
integers** (ratios). An 80/20 split is `(80, 20)` or `(4, 1)` — never floats.
Nothing on the settlement path touches floating point, so no allocation can drift
by a rounding error.

**Largest-remainder (Hamilton) allocation.** For amount `A` and weights `wᵢ`
with total `W`:

1. Each payee first gets the floor of its exact quota: `fᵢ = A·wᵢ // W`.
2. The leftover `dust = A − Σfᵢ` is handed out one unit at a time to the payees
   with the largest remainders `rᵢ = A·wᵢ mod W`, ties broken by payee id, then
   original index.

*Conservation proof sketch.* `Σfᵢ = Σ(A·wᵢ − rᵢ)/W = A − (Σrᵢ)/W`. Each
`rᵢ ∈ [0, W)`, so `Σrᵢ < n·W` and `dust = (Σrᵢ)/W ∈ {0, …, n−1}` is a
non-negative integer strictly less than the payee count. Distributing exactly
`dust` unit increments restores the total to `A`, so `Σ(allocation) == A`
**exactly**, for every amount and every weight vector. The tie-break is a total
order on `(−remainder, id, index)`, so the result is deterministic regardless of
dict iteration or input ordering.

**Weights are locked at open.** `open_split(splits, ref)` records the weight
vector as an immutable tuple and moves no money; `settle_split(ref, amount)` is
the only thing that pays out, and it recomputes the allocation purely from the
locked weights. The weights a settlement honours cannot be renegotiated between
open and settle.

**Atomicity.** `settle_split` computes the full allocation, re-checks
conservation, and verifies the payer's balance *before* crediting anyone; then it
debits the payer once and credits every payee in a single pass. If the balance is
short, nothing moves. The two-phase `open`→`settle` form leaves a failed
settlement `OPEN` and retryable; the one-shot `split_pay` convenience is
all-or-nothing and rolls its own contract back on failure.

**Role binding & receipts.** On a shared ledger only the contract's payer may
settle or refund it. Every settlement returns one `Receipt` per payee and stores
a queryable settlement record (`settled_amount` + per-payee `allocations`).

**Registration.** Both `_BUILTINS` (`nest_core/plugins.py`) **and** a
`nest.plugins.payments` entry point in `packages/nest-plugins-reference/pyproject.toml`
(payments previously had no entry-point group at all).

## Tradeoffs

1. **Weights at open vs. weights at settle.** Locking weights at open (and
   letting the amount arrive at settle) makes the allocation a pure function of
   `(declared weights, amount)` that a val

…

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/136/head:pr-136
git checkout pr-136