Surface fallback-split rows more prominently in the settlements UI (0.00 EUR is alarming) #25

Closed
opened 2026-05-15 21:08:57 +00:00 by padreug · 1 comment
Owner

Symptom

A real cash-out from a Sintra ATM lands on the operator's wallet and the Settlements table shows:

Status Time Gross Net Commission Fiat Hash
processed 10:59 PM 31,684 30,175 1,509 0.00 EUR 31a12fe0…

The 0.00 EUR reads as a bug. The next to processed is the existing used_fallback_split=true indicator, but it's small, tooltip-only, and easy to miss. An operator looking at this row thinks satmachineadmin failed to record the fiat amount — when in fact bitSpire never sent it.

Root cause

Not in this repo. aiolabs/lamassu-next#44 (filed, still open as of 2026-05-15) tracks the bitSpire-side Payment.extra enrichment. Until that ships:

  • bitSpire sends wallet_fiat_* (operator-reporting, USD) — not the customer-paid amount/currency.
  • is_bitspire_payment(extra) returns False (no source: "bitspire" marker), parse_settlement falls into _parse_fallback, fiat_amount is hardcoded to 0.0, exchange_rate to 0.0.
  • The dca distribution leg skips with "no exchange_rate on settlement (bitSpire fallback path; see aiolabs/lamassu-next#44)".

See the comment thread on aiolabs/lamassu-next#44 for the live evidence from the 2026-05-15 test.

The UX gap

Right now the operator has to:

  1. Notice the icon.
  2. Know it means used_fallback_split=true.
  3. Open the row to see the skipped legs.
  4. Read the leg error message to find the aiolabs/lamassu-next#44 link.

That's four steps to learn "this is a known upstream gap, not a bug here."

Proposed UX

Until lamassu-next#44 lands and the fallback path goes cold, fallback-split rows should be visually distinct without the operator hunting:

  • Fiat column: instead of rendering 0.00 EUR on used_fallback_split=true rows, render or pending with a tooltip explaining that bitSpire didn't supply the split metadata yet (links to lamassu-next#44).

  • Status chip: keep the green processed (the Lightning side really did settle) but pair it with a deep-orange fallback chip — small, in-line, distinct from the icon — so the row reads as "settled but distribution-blocked" at a glance.

  • Settlements page banner (when any visible row has used_fallback_split=true): a one-liner explaining the upstream issue once at the top, not on every row. Auto-disappears when no fallback rows are showing.

  • Worklist consideration: should fallback-split rows surface in the worklist? Arguments both ways:

    • Yes: they need operator attention (distribution didn't happen).
    • No: the operator can't fix them — the fix is upstream. Cluttering the worklist with un-actionable rows defeats its purpose.

    Recommendation: add a fifth worklist bucket awaiting_upstream that lists used_fallback_split=true rows, with no action buttons — purely informational. Distinct from rejected (security) and errored (operational, retry-able) and stuck_* (recoverable via force-reset).

Acceptance criteria

  • used_fallback_split=true rows render (or similar) in the Fiat column, not 0.00 EUR.
  • The icon gains a paired fallback chip on the status cell for visual scannability.
  • Tooltip on either element points to aiolabs/lamassu-next#44.
  • Settlements page shows a single dismissible banner when one or more fallback rows are visible.
  • Worklist gains a fifth awaiting_upstream bucket (informational, no actions). Total worklist count includes it.
  • No backend changes required — all data already on the row (used_fallback_split, fiat_amount, error_message on the skipped dca leg).

Out of scope

  • Reading bitSpire's wallet_fiat_* fields as a stopgap. Confusing — those are operator-side USD reporting, not customer-paid fiat. Once lamassu-next#44 ships, the right fields will be present; don't bake in a stopgap that conflicts.
  • Any change to the distribution skip behaviour. Skipping the dca leg without an exchange rate is correct.

References

  • Upstream gap: aiolabs/lamassu-next#44 (2026-05-15 comment has end-to-end evidence)
  • Code refs: bitspire.py:_parse_fallback (hardcodes 0.0); bitspire.py:is_bitspire_payment (the source check)
  • UI: static/js/index.js worklist buckets; templates/satmachineadmin/index.html settlements table
## Symptom A real cash-out from a Sintra ATM lands on the operator's wallet and the Settlements table shows: | Status | Time | Gross | Net | Commission | Fiat | Hash | |---|---|---|---|---|---|---| | `processed` ⚠ | 10:59 PM | 31,684 | 30,175 | 1,509 | **0.00 EUR** | 31a12fe0… | The `0.00 EUR` reads as a bug. The `⚠` next to `processed` is the existing `used_fallback_split=true` indicator, but it's small, tooltip-only, and easy to miss. An operator looking at this row thinks satmachineadmin failed to record the fiat amount — when in fact bitSpire never sent it. ## Root cause Not in this repo. `aiolabs/lamassu-next#44` (filed, still open as of 2026-05-15) tracks the bitSpire-side `Payment.extra` enrichment. Until that ships: - bitSpire sends `wallet_fiat_*` (operator-reporting, USD) — *not* the customer-paid amount/currency. - `is_bitspire_payment(extra)` returns False (no `source: "bitspire"` marker), `parse_settlement` falls into `_parse_fallback`, `fiat_amount` is hardcoded to `0.0`, `exchange_rate` to `0.0`. - The `dca` distribution leg skips with `"no exchange_rate on settlement (bitSpire fallback path; see aiolabs/lamassu-next#44)"`. See the comment thread on `aiolabs/lamassu-next#44` for the live evidence from the 2026-05-15 test. ## The UX gap Right now the operator has to: 1. Notice the `⚠` icon. 2. Know it means `used_fallback_split=true`. 3. Open the row to see the skipped legs. 4. Read the leg error message to find the `aiolabs/lamassu-next#44` link. That's four steps to learn "this is a known upstream gap, not a bug here." ## Proposed UX Until `lamassu-next#44` lands and the fallback path goes cold, fallback-split rows should be visually distinct without the operator hunting: - **Fiat column**: instead of rendering `0.00 EUR` on `used_fallback_split=true` rows, render `—` or `pending` with a tooltip explaining that bitSpire didn't supply the split metadata yet (links to `lamassu-next#44`). - **Status chip**: keep the green `processed` (the Lightning side really did settle) but pair it with a deep-orange `fallback` chip — small, in-line, distinct from the `⚠` icon — so the row reads as "settled but distribution-blocked" at a glance. - **Settlements page banner** (when any visible row has `used_fallback_split=true`): a one-liner explaining the upstream issue once at the top, not on every row. Auto-disappears when no fallback rows are showing. - **Worklist consideration**: should fallback-split rows surface in the worklist? Arguments both ways: - Yes: they need operator attention (distribution didn't happen). - No: the operator can't fix them — the fix is upstream. Cluttering the worklist with un-actionable rows defeats its purpose. Recommendation: add a fifth worklist bucket `awaiting_upstream` that lists `used_fallback_split=true` rows, with no action buttons — purely informational. Distinct from `rejected` (security) and `errored` (operational, retry-able) and `stuck_*` (recoverable via force-reset). ## Acceptance criteria - [ ] `used_fallback_split=true` rows render `—` (or similar) in the Fiat column, not `0.00 EUR`. - [ ] The `⚠` icon gains a paired `fallback` chip on the status cell for visual scannability. - [ ] Tooltip on either element points to `aiolabs/lamassu-next#44`. - [ ] Settlements page shows a single dismissible banner when one or more fallback rows are visible. - [ ] Worklist gains a fifth `awaiting_upstream` bucket (informational, no actions). Total worklist count includes it. - [ ] No backend changes required — all data already on the row (`used_fallback_split`, `fiat_amount`, `error_message` on the skipped `dca` leg). ## Out of scope - Reading bitSpire's `wallet_fiat_*` fields as a stopgap. Confusing — those are operator-side USD reporting, not customer-paid fiat. Once `lamassu-next#44` ships, the right fields will be present; don't bake in a stopgap that conflicts. - Any change to the distribution skip behaviour. Skipping the `dca` leg without an exchange rate is correct. ## References - Upstream gap: `aiolabs/lamassu-next#44` (2026-05-15 comment has end-to-end evidence) - Code refs: `bitspire.py:_parse_fallback` (hardcodes 0.0); `bitspire.py:is_bitspire_payment` (the `source` check) - UI: `static/js/index.js` worklist buckets; `templates/satmachineadmin/index.html` settlements table
Author
Owner

➡️ Migrated to aiolabs/spirekeeper#14 (aiolabs/spirekeeper#14).

The v2-bitspire line of this extension now lives in its own repo, aiolabs/spirekeeper. Tracking for this issue continues there; closing here. (Issue numbers were reassigned in the new repo.)

➡️ **Migrated to https://git.atitlan.io/aiolabs/spirekeeper/issues/14 (aiolabs/spirekeeper#14).** The v2-bitspire line of this extension now lives in its own repo, `aiolabs/spirekeeper`. Tracking for this issue continues there; closing here. (Issue numbers were reassigned in the new repo.)
Sign in to join this conversation.
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/satmachineadmin#25
No description provided.