fix(settlements): process cash-in (outbound) payments, not just cash-out #30

Merged
padreug merged 1 commit from fix/cash-in-settlement into main 2026-06-22 10:19:45 +00:00
Owner

What

The _handle_payment cash-in branch existed but had never run end-to-end — bitSpire cash-in payouts only started reaching it once aiolabs/withdraw#3 let an LNURL-withdraw payout carry source=bitspire. With that merged, the first real cash-in surfaced two bugs:

  1. payment.sat is signed by direction — negative for an outbound (cash-in) payout. It was passed straight to parse_settlement as wire_sats, which enforces wire_sats >= 0, so every cash-in was rejected (wire_sats must be >= 0, got -75795). A settlement's wire_sats is a magnitude (direction lives in tx_type) → pass abs(payment.sat). Same in _record_rejected.

  2. _record_rejected hard-coded tx_type="cash_out", so a rejected cash-in showed the wrong direction in the dashboard. The parsed tx_type isn't available there, but the authenticated protocol direction is — derive it: outbound → cash_in, inbound → cash_out.

Verification (dev stack)

A stamped cash-in now lands a cash_in settlement and pays the platform:

cash_in  wire=75795  principal=82386  fee=6591  (super_fee=2472  operator_fee=4119)  → processed
super wallet credited 2472 sats (3% of principal)
dca leg skipped — principal stays in operator wallet as cash-in liquidity

black + ruff clean on the changed file (the two remaining E501s are pre-existing, outside this diff).

Depends on / context

  • aiolabs/withdraw#3 (merged) — the extra passthrough that lets a cash-in payout be stamped.
  • bitSpire (ATM) must create the cash-in link for the NET amount with extra={source, type:cash_in, principal_sats, fee_sats, nostr attribution} — spec'd to bitspire separately. This PR is the operator-side half; it's a no-op safety improvement until bitSpire stamps cash-ins.

Post-merge

Tag v0.1.1 and bump the aiolabs/lnbits-extensions catalog entry.

🤖 Generated with Claude Code

## What The `_handle_payment` cash-in branch existed but had **never run end-to-end** — bitSpire cash-in payouts only started reaching it once `aiolabs/withdraw#3` let an LNURL-withdraw payout carry `source=bitspire`. With that merged, the first real cash-in surfaced two bugs: 1. **`payment.sat` is signed by direction** — negative for an outbound (cash-in) payout. It was passed straight to `parse_settlement` as `wire_sats`, which enforces `wire_sats >= 0`, so every cash-in was **rejected** (`wire_sats must be >= 0, got -75795`). A settlement's `wire_sats` is a magnitude (direction lives in `tx_type`) → pass `abs(payment.sat)`. Same in `_record_rejected`. 2. **`_record_rejected` hard-coded `tx_type="cash_out"`**, so a rejected cash-in showed the wrong direction in the dashboard. The parsed tx_type isn't available there, but the authenticated protocol direction is — derive it: outbound → `cash_in`, inbound → `cash_out`. ## Verification (dev stack) A stamped cash-in now lands a `cash_in` settlement and pays the platform: ``` cash_in wire=75795 principal=82386 fee=6591 (super_fee=2472 operator_fee=4119) → processed super wallet credited 2472 sats (3% of principal) dca leg skipped — principal stays in operator wallet as cash-in liquidity ``` `black` + `ruff` clean on the changed file (the two remaining E501s are pre-existing, outside this diff). ## Depends on / context - `aiolabs/withdraw#3` (merged) — the `extra` passthrough that lets a cash-in payout be stamped. - bitSpire (ATM) must create the cash-in link for the **NET** amount with `extra={source, type:cash_in, principal_sats, fee_sats, nostr attribution}` — spec'd to bitspire separately. This PR is the operator-side half; it's a no-op safety improvement until bitSpire stamps cash-ins. ## Post-merge Tag `v0.1.1` and bump the `aiolabs/lnbits-extensions` catalog entry. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(settlements): process cash-in (outbound) payments, not just cash-out
Some checks failed
ci.yml / fix(settlements): process cash-in (outbound) payments, not just cash-out (pull_request) Failing after 0s
7b55dc152b
The `_handle_payment` cash-in branch existed but had never been exercised
end-to-end — bitSpire cash-in payouts only started reaching it once the
withdraw extension learned to stamp `source=bitspire` on an LNURL-withdraw
payout (aiolabs/withdraw#3). With that wired up, the first real cash-in
exposed two bugs:

1. `payment.sat` is signed by protocol direction — negative for an
   outbound (cash-in) payout. It was passed straight to `parse_settlement`
   as `wire_sats`, which enforces `wire_sats >= 0`, so every cash-in was
   rejected ("wire_sats must be >= 0, got -75795"). A settlement's
   `wire_sats` is a magnitude (direction lives in `tx_type`); pass
   `abs(payment.sat)`. Same in `_record_rejected`.

2. `_record_rejected` hard-coded `tx_type="cash_out"`, so a rejected
   cash-in showed the wrong direction in the operator dashboard. The
   parsed tx_type isn't available on the rejection path, but the
   authenticated protocol direction is — derive it: outbound → cash_in,
   inbound → cash_out.

Verified on the dev stack: a stamped cash-in now lands a `cash_in`
settlement (net 75795, principal 82386, fee 6591), pays the super its 3%
(2472 sats), and correctly skips the DCA leg (principal stays in the
operator's wallet as liquidity from the cash-in customer).
padreug deleted branch fix/cash-in-settlement 2026-06-22 10:19:46 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/spirekeeper!30
No description provided.