Setoff

Design

Solver

The solver picks which pending bills go into the next cycle. It has no special power: the contract re-checks everything.

The problem it solves

If every net debtor has deposited enough, every pending IOU can settle. Usually some haven't. Then the solver must choose a subset of IOUs such that every party's net position within that subset is covered by its deposit, while clearing as much face value as possible. That is a knapsack-style problem, so the solver uses a fast greedy repair that is close to optimal.

Algorithm

How close to perfect

On small random pools, where every possible combination can be checked, the solver clears about 93% of the best possible value and finds the exact best answer most of the time. Every cycle it proposes is fully funded, and the contract checks that again anyway.

Settling

The solver simulates first, then sends Memo.memo(Setoff, settle(ids, parties), keccak("setoff:cycle:N"), summary) from its wallet. The cycle summary, for example "Setoff cycle 3: 13 IOUs, 11.96 USDC cleared with 4.1 USDC", is indexed onchain by Arc's Memo contract. It reads the head block uncached, so bills posted seconds earlier are included.

bash
cd solver
SETOFF_NETWORK=mainnet SOLVER_PK=0x... npm run solve

Anyone can run one

settle is permissionless and the pool is public, so if the hosted solver stops, anyone can run the command above and settle the cycle. A dishonest solver can only leave bills out, and those wait for the next cycle.