fee_msat read in core/market/events — the LNbits field is fee #170

Closed
opened 2026-09-22 21:18:29 +00:00 by padreug · 0 comments
Owner

Same bug as the one fixed in the wallet by #161, still present in three other places. Found by grepping the merged tree after that batch landed.

LNbits payment payloads carry fee (signed millisats, like amount). There is no fee_msat field. Reading it yields undefined.

Verified live against POST /api/v1/payments with out: true, which is exactly what PaymentService.payInvoice returns:

has fee_msat: False   -> PaymentResult.fee_msat reads None
has fee     : True    -> actual value 0 msat
amount      : -55000 msat | status: success

Sites

File Line Effect
src/core/services/PaymentService.ts 8 PaymentResult.fee_msat: number — the type asserts a field that never arrives, so TS trusts it downstream
src/core/services/PaymentService.ts 269 debug log prints undefined
src/modules/market/composables/useLightningPayment.ts 45, 91 paid orders get feeMsat: undefined persisted
src/modules/events/services/LnbitsPaymentProvider.ts 87 ?? 0 masks it — every payment records a zero fee

Severity

Low. Nothing computes a balance or total from these, so no money is miscounted; the fee is simply recorded as missing or zero. The lying type on PaymentResult is the part most worth fixing, since it hides the problem from the compiler.

Fix

Rename to fee at the read sites, keeping the internal feeMsat property names if preferred, and take Math.abs() since the value is signed for outgoing payments. PaymentResult should also stop declaring a field the API does not return.

See the field contract for why this class of bug is silent: LNbits pins pydantic 1.x, so @property members like pending are never serialized, and fee is stored under its own name.

Same bug as the one fixed in the wallet by #161, still present in three other places. Found by grepping the merged tree after that batch landed. LNbits payment payloads carry **`fee`** (signed millisats, like `amount`). There is no `fee_msat` field. Reading it yields `undefined`. Verified live against `POST /api/v1/payments` with `out: true`, which is exactly what `PaymentService.payInvoice` returns: ``` has fee_msat: False -> PaymentResult.fee_msat reads None has fee : True -> actual value 0 msat amount : -55000 msat | status: success ``` ## Sites | File | Line | Effect | |---|---|---| | `src/core/services/PaymentService.ts` | 8 | `PaymentResult.fee_msat: number` — the type asserts a field that never arrives, so TS trusts it downstream | | `src/core/services/PaymentService.ts` | 269 | debug log prints `undefined` | | `src/modules/market/composables/useLightningPayment.ts` | 45, 91 | paid orders get `feeMsat: undefined` persisted | | `src/modules/events/services/LnbitsPaymentProvider.ts` | 87 | `?? 0` masks it — every payment records a zero fee | ## Severity Low. Nothing computes a balance or total from these, so no money is miscounted; the fee is simply recorded as missing or zero. The lying type on `PaymentResult` is the part most worth fixing, since it hides the problem from the compiler. ## Fix Rename to `fee` at the read sites, keeping the internal `feeMsat` property names if preferred, and take `Math.abs()` since the value is signed for outgoing payments. `PaymentResult` should also stop declaring a field the API does not return. See [the field contract](https://git.atitlan.io/aiolabs/webapp/pulls/161) for why this class of bug is silent: LNbits pins pydantic 1.x, so `@property` members like `pending` are never serialized, and `fee` is stored under its own name.
Sign in to join this conversation.
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/webapp#170
No description provided.