Skip to content

Commit f8ba09e

Browse files
Merge pull request #470 from m1sterc001guy/toggle_ln_fee
feat: toggle if fees should be added to Lightning invoice
2 parents 460a681 + 865ff3f commit f8ba09e

9 files changed

Lines changed: 187 additions & 30 deletions

File tree

lib/frb_generated.dart

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ abstract class RustLibApi extends BaseApi {
357357
required SafeUrl gatewayUrl,
358358
required bool isLnv2,
359359
required Amount amount,
360+
required bool includeFees,
360361
});
361362

362363
Future<bool> crateMultimintMultimintContainsClient({
@@ -921,6 +922,7 @@ abstract class RustLibApi extends BaseApi {
921922
required String gatewayUrl,
922923
required bool isLnv2,
923924
required BigInt amountMsats,
925+
required bool includeFees,
924926
});
925927

926928
Future<Connector> crateDbConnectorDefault();
@@ -3551,6 +3553,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
35513553
required SafeUrl gatewayUrl,
35523554
required bool isLnv2,
35533555
required Amount amount,
3556+
required bool includeFees,
35543557
}) {
35553558
return handler.executeNormal(
35563559
NormalTask(
@@ -3573,6 +3576,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
35733576
amount,
35743577
serializer,
35753578
);
3579+
sse_encode_bool(includeFees, serializer);
35763580
pdeCallFfi(
35773581
generalizedFrbRustBinding,
35783582
serializer,
@@ -3586,7 +3590,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
35863590
),
35873591
constMeta:
35883592
kCrateMultimintMultimintComputeReceiveAmountWithFeesConstMeta,
3589-
argValues: [that, federationId, gatewayUrl, isLnv2, amount],
3593+
argValues: [
3594+
that,
3595+
federationId,
3596+
gatewayUrl,
3597+
isLnv2,
3598+
amount,
3599+
includeFees,
3600+
],
35903601
apiImpl: this,
35913602
),
35923603
);
@@ -3596,7 +3607,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
35963607
get kCrateMultimintMultimintComputeReceiveAmountWithFeesConstMeta =>
35973608
const TaskConstMeta(
35983609
debugName: "Multimint_compute_receive_amount_with_fees",
3599-
argNames: ["that", "federationId", "gatewayUrl", "isLnv2", "amount"],
3610+
argNames: [
3611+
"that",
3612+
"federationId",
3613+
"gatewayUrl",
3614+
"isLnv2",
3615+
"amount",
3616+
"includeFees",
3617+
],
36003618
);
36013619

36023620
@override
@@ -8158,6 +8176,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
81588176
required String gatewayUrl,
81598177
required bool isLnv2,
81608178
required BigInt amountMsats,
8179+
required bool includeFees,
81618180
}) {
81628181
return handler.executeNormal(
81638182
NormalTask(
@@ -8170,6 +8189,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
81708189
sse_encode_String(gatewayUrl, serializer);
81718190
sse_encode_bool(isLnv2, serializer);
81728191
sse_encode_u_64(amountMsats, serializer);
8192+
sse_encode_bool(includeFees, serializer);
81738193
pdeCallFfi(
81748194
generalizedFrbRustBinding,
81758195
serializer,
@@ -8182,7 +8202,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
81828202
decodeErrorData: sse_decode_AnyhowException,
81838203
),
81848204
constMeta: kCrateComputeReceiveAmountWithFeesConstMeta,
8185-
argValues: [federationId, gatewayUrl, isLnv2, amountMsats],
8205+
argValues: [federationId, gatewayUrl, isLnv2, amountMsats, includeFees],
81868206
apiImpl: this,
81878207
),
81888208
);
@@ -8191,7 +8211,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
81918211
TaskConstMeta get kCrateComputeReceiveAmountWithFeesConstMeta =>
81928212
const TaskConstMeta(
81938213
debugName: "compute_receive_amount_with_fees",
8194-
argNames: ["federationId", "gatewayUrl", "isLnv2", "amountMsats"],
8214+
argNames: [
8215+
"federationId",
8216+
"gatewayUrl",
8217+
"isLnv2",
8218+
"amountMsats",
8219+
"includeFees",
8220+
],
81958221
);
81968222

81978223
@override
@@ -19723,18 +19749,29 @@ class MultimintImpl extends RustOpaque implements Multimint {
1972319749
bolt11: bolt11,
1972419750
);
1972519751

19752+
/// Computes the invoice amount and fee breakdown for a receive.
19753+
///
19754+
/// When `include_fees` is true the fees are added on top of `amount`, so the
19755+
/// sender covers them and exactly `amount` is credited to the receiver. When
19756+
/// it is false the invoice equals `amount` exactly — the sender pays only what
19757+
/// was requested and the receiver absorbs the fees, netting `amount` minus the
19758+
/// fees. In both cases the returned `invoice_msats` is the invoice face value
19759+
/// and the receiver nets `invoice_msats - federation_fee_msats -
19760+
/// gateway_fee_msats`.
1972619761
Future<ReceiveAmount> computeReceiveAmountWithFees({
1972719762
required FederationId federationId,
1972819763
required SafeUrl gatewayUrl,
1972919764
required bool isLnv2,
1973019765
required Amount amount,
19766+
required bool includeFees,
1973119767
}) =>
1973219768
RustLib.instance.api.crateMultimintMultimintComputeReceiveAmountWithFees(
1973319769
that: this,
1973419770
federationId: federationId,
1973519771
gatewayUrl: gatewayUrl,
1973619772
isLnv2: isLnv2,
1973719773
amount: amount,
19774+
includeFees: includeFees,
1973819775
);
1973919776

1974019777
Future<bool> containsClient({required FederationId federationId}) =>

lib/l10n/app_en.arb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,12 @@
445445
"showMillisatoshisDescription": "Display fractional satoshis with 3 decimal places",
446446
"@showMillisatoshisDescription": {},
447447

448+
"addFeesToInvoice": "Add fees to invoice",
449+
"@addFeesToInvoice": {},
450+
451+
"addFeesToInvoiceDescription": "When on, the sender pays the fees and you receive the full amount. When off, the invoice is for the exact amount and the fees are taken from what you receive.",
452+
"@addFeesToInvoiceDescription": {},
453+
448454
"txDetailAmount": "Amount",
449455
"@txDetailAmount": {},
450456

lib/l10n/app_es.arb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@
121121
"displaySettingsUpdated": "¡Configuración de pantalla actualizada!",
122122
"showMillisatoshis": "Mostrar milisatoshis",
123123
"showMillisatoshisDescription": "Mostrar satoshis fraccionarios con 3 decimales",
124+
"addFeesToInvoice": "Agregar comisiones a la factura",
125+
"addFeesToInvoiceDescription": "Cuando está activado, el remitente paga las comisiones y tú recibes el monto completo. Cuando está desactivado, la factura es por el monto exacto y las comisiones se descuentan de lo que recibes.",
124126
"txDetailAmount": "Monto",
125127
"txDetailTotalAmount": "Monto Total",
126128
"txDetailReceivedAmount": "Monto Recibido",

lib/lib.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,13 @@ Future<ReceiveAmount> computeReceiveAmountWithFees({
9494
required String gatewayUrl,
9595
required bool isLnv2,
9696
required BigInt amountMsats,
97+
required bool includeFees,
9798
}) => RustLib.instance.api.crateComputeReceiveAmountWithFees(
9899
federationId: federationId,
99100
gatewayUrl: gatewayUrl,
100101
isLnv2: isLnv2,
101102
amountMsats: amountMsats,
103+
includeFees: includeFees,
102104
);
103105

104106
Future<String> getInvoiceFromLnaddressOrLnurl({

lib/multimint.dart

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,21 @@ abstract class Multimint implements RustOpaqueInterface {
166166
required Bolt11Invoice bolt11,
167167
});
168168

169+
/// Computes the invoice amount and fee breakdown for a receive.
170+
///
171+
/// When `include_fees` is true the fees are added on top of `amount`, so the
172+
/// sender covers them and exactly `amount` is credited to the receiver. When
173+
/// it is false the invoice equals `amount` exactly — the sender pays only what
174+
/// was requested and the receiver absorbs the fees, netting `amount` minus the
175+
/// fees. In both cases the returned `invoice_msats` is the invoice face value
176+
/// and the receiver nets `invoice_msats - federation_fee_msats -
177+
/// gateway_fee_msats`.
169178
Future<ReceiveAmount> computeReceiveAmountWithFees({
170179
required FederationId federationId,
171180
required SafeUrl gatewayUrl,
172181
required bool isLnv2,
173182
required Amount amount,
183+
required bool includeFees,
174184
});
175185

176186
Future<bool> containsClient({required FederationId federationId});
@@ -852,11 +862,12 @@ class PeerStatus {
852862

853863
/// Result of pricing a Lightning receive. `invoice_msats` is the invoice's face
854864
/// value (what the payer pays). The fee is broken out into its two sources:
855-
/// `federation_fee_msats` (on-federation: lightning input fee + mint output fees
856-
/// + dust) and `gateway_fee_msats` (the gateway's off-chain routing fee, always
857-
/// 0 for LNv1). The receiver is credited `invoice_msats - federation_fee_msats -
858-
/// gateway_fee_msats`. Both are quoted at `invoice_msats`, not the requested
859-
/// amount, so they match what is really deducted.
865+
/// `federation_fee_msats` (on-federation: lightning input fee plus mint output
866+
/// fees plus dust) and `gateway_fee_msats` (the gateway's off-chain routing fee,
867+
/// always 0 for LNv1). The receiver is credited
868+
/// `invoice_msats - federation_fee_msats - gateway_fee_msats`. Both are quoted at
869+
/// `invoice_msats`, not the requested amount, so they match what is really
870+
/// deducted.
860871
class ReceiveAmount {
861872
final BigInt invoiceMsats;
862873
final BigInt federationFeeMsats;

lib/number_pad.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ class _NumberPadState extends State<NumberPad> {
6666
String? _selectedGatewayEndpoint;
6767
bool? _selectedGatewayIsLnv2;
6868

69+
// When true, fees are added on top of the invoice so the sender pays them and
70+
// the receiver gets the full requested amount. When false, the invoice is for
71+
// the exact requested amount and the receiver absorbs the fees. Resets to true
72+
// each time the screen opens. Only relevant for Lightning receive.
73+
bool _includeFees = true;
74+
75+
// The toggle only makes sense when generating a Lightning invoice to receive,
76+
// not when paying a Lightning Address / LNURL or for ecash / on-chain.
77+
bool get _isLightningReceive =>
78+
widget.paymentType == PaymentType.lightning &&
79+
widget.lightningAddressOrLnurl == null;
80+
6981
@override
7082
void initState() {
7183
super.initState();
@@ -330,6 +342,7 @@ class _NumberPadState extends State<NumberPad> {
330342
gatewayUrl: selected.endpoint,
331343
isLnv2: selected.isLnv2,
332344
amountMsats: requestedAmountMsats,
345+
includeFees: _includeFees,
333346
);
334347

335348
// Create the invoice
@@ -990,6 +1003,40 @@ class _NumberPadState extends State<NumberPad> {
9901003
),
9911004
),
9921005
),
1006+
if (_isLightningReceive)
1007+
Padding(
1008+
padding: const EdgeInsets.fromLTRB(16, 0, 8, 8),
1009+
child: Row(
1010+
children: [
1011+
Expanded(
1012+
child: Text(
1013+
context.l10n.addFeesToInvoice,
1014+
style: Theme.of(context).textTheme.bodyMedium,
1015+
),
1016+
),
1017+
Tooltip(
1018+
message: context.l10n.addFeesToInvoiceDescription,
1019+
triggerMode: TooltipTriggerMode.tap,
1020+
showDuration: const Duration(seconds: 4),
1021+
child: Padding(
1022+
padding: const EdgeInsets.only(right: 4),
1023+
child: Icon(
1024+
Icons.info_outline,
1025+
size: 18,
1026+
color: Theme.of(context).colorScheme.onSurfaceVariant,
1027+
),
1028+
),
1029+
),
1030+
Switch(
1031+
value: _includeFees,
1032+
activeThumbColor: Theme.of(context).colorScheme.primary,
1033+
onChanged: (value) {
1034+
setState(() => _includeFees = value);
1035+
},
1036+
),
1037+
],
1038+
),
1039+
),
9931040
Padding(
9941041
padding: const EdgeInsets.symmetric(horizontal: 16.0),
9951042
child: SizedBox(

rust/ecashapp/src/frb_generated.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3171,6 +3171,7 @@ fn wire__crate__multimint__Multimint_compute_receive_amount_with_fees_impl(
31713171
let api_gateway_url = <SafeUrl>::sse_decode(&mut deserializer);
31723172
let api_is_lnv2 = <bool>::sse_decode(&mut deserializer);
31733173
let api_amount = <Amount>::sse_decode(&mut deserializer);
3174+
let api_include_fees = <bool>::sse_decode(&mut deserializer);
31743175
deserializer.end();
31753176
move |context| async move {
31763177
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
@@ -3212,6 +3213,7 @@ fn wire__crate__multimint__Multimint_compute_receive_amount_with_fees_impl(
32123213
api_gateway_url,
32133214
api_is_lnv2,
32143215
api_amount,
3216+
api_include_fees,
32153217
)
32163218
.await?;
32173219
Ok(output_ok)
@@ -10017,6 +10019,7 @@ fn wire__crate__compute_receive_amount_with_fees_impl(
1001710019
let api_gateway_url = <String>::sse_decode(&mut deserializer);
1001810020
let api_is_lnv2 = <bool>::sse_decode(&mut deserializer);
1001910021
let api_amount_msats = <u64>::sse_decode(&mut deserializer);
10022+
let api_include_fees = <bool>::sse_decode(&mut deserializer);
1002010023
deserializer.end();
1002110024
move |context| async move {
1002210025
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
@@ -10045,6 +10048,7 @@ fn wire__crate__compute_receive_amount_with_fees_impl(
1004510048
api_gateway_url,
1004610049
api_is_lnv2,
1004710050
api_amount_msats,
10051+
api_include_fees,
1004810052
)
1004910053
.await?;
1005010054
Ok(output_ok)

rust/ecashapp/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,13 @@ pub async fn compute_receive_amount_with_fees(
282282
gateway_url: String,
283283
is_lnv2: bool,
284284
amount_msats: u64,
285+
include_fees: bool,
285286
) -> anyhow::Result<ReceiveAmount> {
286287
let gateway_url = SafeUrl::parse(&gateway_url)?;
287288
let amount = Amount::from_msats(amount_msats);
288289
let multimint = get_multimint();
289290
multimint
290-
.compute_receive_amount_with_fees(federation_id, gateway_url, is_lnv2, amount)
291+
.compute_receive_amount_with_fees(federation_id, gateway_url, is_lnv2, amount, include_fees)
291292
.await
292293
}
293294

0 commit comments

Comments
 (0)