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
- Start with every pending IOU that isn't about to expire (60 s margin), most urgent first, capped at 256.
- Repair: while some net debtor is short, try dropping each of that debtor's IOUs and keep the drop that leaves the least total shortfall across everyone. Ties go to the smaller IOU.
- Refill: retry every dropped IOU, largest first, keeping any that still fit.
- FX (opt-in): pair parties who opted in and have opposite USDC/EURC leftovers, at a reference rate both minimums accept. Re-select with those swaps, and keep them only if the cycle is still fully funded and clears at least as much.
- Credit: for each bill still left out, check whether the debtor has credit lines whose lenders have room and spare deposit. If together they cover the gap, add the bill and plan the draws (one per lender).
- Partial: finally, pay part of each remaining bill, up to what its debtor can still cover. The rest stays open.
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.
cd solver
SETOFF_NETWORK=mainnet SOLVER_PK=0x... npm run solveAnyone 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.