-
Notifications
You must be signed in to change notification settings - Fork 7
in the Payment details section the user should see the fees #244
Description
Description
When viewing the details of a completed payment, the transaction fee (routing fee for Lightning, network fee for on-chain) is not shown. Users have no way to know how much they paid in fees for a given transaction.
Current State
The activities table has no fee_sats column. The Wallet.sendPayment interface returns only a string (preimage or payment ID). Fee data is either discarded at the point of execution or never retrieved, so it is never stored or displayed.
The activity detail screen (app/activity/[id]/index.tsx) renders amount and converted amount, but has no fee row.
Fee Data Availability by Wallet Type
Breez (Lightning / Spark)
Fee information is already available in BreezService.sendPayment — it is just discarded today.
prepareSendPayment is called before every payment and its response (prepareResponse) contains the fee breakdown on the paymentMethod field:
// services/BreezService.ts — inside sendPayment()
const prepareResponse = await this.client.prepareSendPayment({ ... });
// For Bolt11 invoices (already instanceof-checked):
if (prepareResponse.paymentMethod instanceof SendPaymentMethod.Bolt11Invoice) {
// lightningFeeSats — routing fee paid over Lightning (bigint)
// sparkTransferFeeSats — fee if paid via Spark transfer instead (bigint | undefined)
}Both lightningFeeSats and sparkTransferFeeSats are known before the payment is sent (prepare step), so no extra SDK call is needed. The actual method used is determined at send time; the correct fee is whichever path the SDK took.
Additionally, getPaymentById is already implemented in BreezService and could be used post-payment to retrieve the final confirmed fee from the Payment object if needed.
NWC (Nostr Wallet Connect)
NIP-47 defines a fees_paid field (in millisats) on the pay_invoice result object:
{
"result_type": "pay_invoice",
"result": {
"preimage": "...",
"fees_paid": 1000
}
}NwcService.sendPayment currently calls this.client.payInvoice(paymentRequest) and returns only the preimage as a string. Whether fees_paid is surfaced depends on the NWC client library in use — this needs to be verified. If not directly available, a follow-up getPayment by preimage/hash may be required.
Note: for NWC, fee availability depends entirely on the connected wallet's implementation. Some wallets omit fees_paid even if the field is in the spec. A null/undefined fee should be treated gracefully (display "N/A" rather than crashing or showing 0).
Implementation Plan
1. DB Migration — add fee_sats column
ALTER TABLE activities ADD COLUMN fee_sats INTEGER;Nullable — existing rows and wallets that can't return fees will be NULL.
2. Wallet interface — extend sendPayment return type
// models/WalletType.ts
sendPayment: (paymentRequest: string, amountSats: bigint) => Promise<{ preimage: string; feeSats?: number }>;3. BreezService.sendPayment — capture fee from prepare response
// Extract fee from prepareResponse before calling sendPayment
let feeSats: number | undefined;
if (prepareResponse.paymentMethod instanceof SendPaymentMethod.Bolt11Invoice) {
feeSats = Number(prepareResponse.paymentMethod.lightningFeeSats);
}
const response = await this.client.sendPayment({ prepareResponse, options: sendOptions });
return { preimage: response.payment.id, feeSats };4. NwcService.sendPayment — extract fees_paid if available
const result = await this.client.payInvoice(paymentRequest);
// result may be a string (preimage) or object depending on library version
const preimage = typeof result === 'string' ? result : result.preimage;
const feeSats = typeof result === 'object' && result.fees_paid != null
? Math.ceil(result.fees_paid / 1000) // msats → sats
: undefined;
return { preimage, feeSats };5. PayInvoiceTask — propagate fee through task chain
Update return type and pass feeSats up to StartPaymentTask.
6. SaveActivityArgs / SaveActivityTask / DatabaseService
Add optional fee_sats?: number | null field to SaveActivityArgs and persist it.
7. Activity detail UI
Add a new ActivityDetailRow for the fee in the "Transaction Details" section, shown only for payment activities and only when fee_sats is non-null:
{isPayment && activity.fee_sats != null && (
<ActivityDetailRow
icon={<Percent size={18} color={secondaryTextColor} />}
label="Network Fee"
value={formatActivityAmount(activity.fee_sats, 'SATS')}
isLast={...}
/>
)}Scope Notes
- Fees are only relevant for outgoing payments (
ActivityType.Pay). Incoming Lightning payments have no fee for the recipient. This feature can be scoped totype = 'pay'only. - For Breez, the fee shown is the pre-payment estimate from the prepare step. The actual fee may differ slightly in edge cases (e.g. if the route changes). A
≈prefix can be added if desired, though in practice for Lightning the prepared fee is what gets charged. - For NWC, a
nullfee should be shown as "N/A" — do not display 0 when data is unavailable, as that would be misleading.