@@ -3,29 +3,97 @@ import 'package:freezed_annotation/freezed_annotation.dart';
33
44part 'fees_entity.freezed.dart' ;
55
6+ /// A fee paid for a Bitcoin or Liquid transaction.
7+ ///
8+ /// Two variants exist because the user can input either:
9+ /// - an absolute amount of satoshis they want to pay (regardless of tx size),
10+ /// - or a fee rate in sat/vByte (which the wallet multiplies by the actual
11+ /// tx vsize when building the transaction).
12+ ///
13+ /// At the SDK boundary:
14+ /// - BDK's `TxBuilder` accepts both via `feeAbsolute()` and `feeRate()` .
15+ /// - BDK's `BumpFeeTxBuilder` (RBF) accepts ONLY a fee rate.
16+ /// - LWK's `buildLbtcTx` accepts ONLY a fee rate.
17+ ///
18+ /// For the rate variant we store the value in BDK's native unit — sat per
19+ /// kilo-weight-unit (kwu) — which lets us hit the SDK with zero conversion
20+ /// and supports fee rates well below 1 sat/vByte without precision loss.
21+ /// 1 vByte = 4 weight units, so 1 sat/vByte = 250 sat/kwu.
622@freezed
723sealed class NetworkFee with _$NetworkFee {
824 const NetworkFee ._();
925
10- const factory NetworkFee .absolute (int value) = AbsoluteFee ;
11- const factory NetworkFee .relative (double value) = RelativeFee ;
26+ /// An absolute fee in satoshis.
27+ const factory NetworkFee .absolute (int sats) = AbsoluteFee ;
28+
29+ /// A relative fee stored in BDK's native unit (sat per kilo-weight-unit).
30+ ///
31+ /// Prefer [NetworkFee.relativeFromSatPerVbyte] for code that reasons in
32+ /// the user-facing sat/vByte unit. This constructor exists to keep
33+ /// well-known defaults `const` -able (e.g. `0.1 sat/vByte == 25 sat/kwu` ).
34+ const factory NetworkFee .relativeSatPerKwu (int satPerKwu) = RelativeFee ;
35+
36+ /// Build a [RelativeFee] from a sat/vByte value as typed by the user
37+ /// or returned by the mempool API.
38+ ///
39+ /// Returns [RelativeFee] (not [NetworkFee] ) so callers don't need to cast
40+ /// — sealed-union factories would static-type to the parent.
41+ ///
42+ /// The conversion rounds to the nearest sat/kwu: precision loss is bounded
43+ /// by 0.5 / 250 ≈ 0.002 sat/vByte, well below any meaningful UX threshold.
44+ static RelativeFee relativeFromSatPerVbyte (double satPerVbyte) =>
45+ RelativeFee ((satPerVbyte * 250 ).round ());
46+
47+ /// Build a [RelativeFee] from an absolute fee target plus an estimated
48+ /// tx vsize.
49+ ///
50+ /// Used when the user enters an absolute amount on a network whose SDK
51+ /// only accepts a rate (Liquid send, RBF bump). The dance costs at most
52+ /// ±1 sat in the final fee (BDK/LWK may rebuild with a slightly different
53+ /// vsize than the estimate) — that's the structural cost of the SDK's
54+ /// rate-only contract, not something we can eliminate.
55+ static RelativeFee relativeFromAbsoluteAndVsize ({
56+ required int absoluteSats,
57+ required int vsize,
58+ }) {
59+ assert (vsize > 0 , 'vsize must be positive' );
60+ // sat/kwu = (absoluteSats / vsize) * 250
61+ // = (absoluteSats * 250) / vsize, rounded half-up via integer math.
62+ return RelativeFee ((absoluteSats * 250 + vsize ~ / 2 ) ~ / vsize);
63+ }
1264
1365 bool get isAbsolute => this is AbsoluteFee ;
1466 bool get isRelative => this is RelativeFee ;
1567
16- @override
68+ /// Numeric value kept for display-layer compatibility.
69+ ///
70+ /// - [AbsoluteFee] returns its amount in sats (`int` ).
71+ /// - [RelativeFee] returns its rate in sat/vByte (`double` ).
1772 num get value => switch (this ) {
18- AbsoluteFee (: final value ) => value ,
19- RelativeFee (: final value ) => value ,
73+ AbsoluteFee (: final sats ) => sats ,
74+ RelativeFee (: final satPerKwu ) => satPerKwu / 250.0 ,
2075 };
2176
22- // add to absolute fee
23- NetworkFee toAbsolute (int size) {
24- return switch (this ) {
25- AbsoluteFee (: final value) => NetworkFee .absolute (value),
26- RelativeFee (: final value) => NetworkFee .absolute ((value * size).round ()),
27- };
28- }
77+ /// Convert this fee to its absolute form given a tx vsize (in vbytes).
78+ ///
79+ /// Identity for [AbsoluteFee] ; computes `(satPerKwu * vsize) / 250`
80+ /// rounded half-up for [RelativeFee] .
81+ NetworkFee toAbsolute (int vsize) => switch (this ) {
82+ AbsoluteFee () => this ,
83+ RelativeFee (: final satPerKwu) =>
84+ NetworkFee .absolute ((satPerKwu * vsize + 125 ) ~ / 250 ),
85+ };
86+ }
87+
88+ /// UI-facing accessors on [RelativeFee] — these are display conversions only,
89+ /// never round-tripped back into storage.
90+ extension RelativeFeeDisplay on RelativeFee {
91+ /// Display in sat/vByte (the unit users type).
92+ double get satPerVbyte => satPerKwu / 250.0 ;
93+
94+ /// Display in sat per 1000 vBytes — LWK's preferred unit.
95+ /// 1 kvB = 4 kwu, so 1 sat/kwu = 4 sat/kvByte.
96+ double get satPerKvbyte => satPerKwu * 4.0 ;
2997}
3098
3199@freezed
@@ -37,43 +105,25 @@ abstract class FeeOptions with _$FeeOptions {
37105 }) = _FeeOptions ;
38106 const FeeOptions ._();
39107
40- FeeOptions toAbsolute (int size) {
41- return FeeOptions (
42- fastest: switch (fastest) {
43- AbsoluteFee (: final value) => NetworkFee .absolute (value),
44- RelativeFee (: final value) => NetworkFee .absolute (
45- (value * size).round (),
46- ),
47- },
48- economic: switch (economic) {
49- AbsoluteFee (: final value) => NetworkFee .absolute (value),
50- RelativeFee (: final value) => NetworkFee .absolute (
51- (value * size).round (),
52- ),
53- },
54- slow: switch (slow) {
55- AbsoluteFee (: final value) => NetworkFee .absolute (value),
56- RelativeFee (: final value) => NetworkFee .absolute (
57- (value * size).round (),
58- ),
59- },
60- );
61- }
108+ FeeOptions toAbsolute (int vsize) => FeeOptions (
109+ fastest: fastest.toAbsolute (vsize),
110+ economic: economic.toAbsolute (vsize),
111+ slow: slow.toAbsolute (vsize),
112+ );
62113
63- FeeOptions toRelative (int size) {
114+ FeeOptions toRelative (int vsize) {
115+ NetworkFee asRelative (NetworkFee fee) => switch (fee) {
116+ AbsoluteFee (: final sats) =>
117+ NetworkFee .relativeFromAbsoluteAndVsize (
118+ absoluteSats: sats,
119+ vsize: vsize,
120+ ),
121+ RelativeFee () => fee,
122+ };
64123 return FeeOptions (
65- fastest: switch (fastest) {
66- AbsoluteFee (: final value) => NetworkFee .relative (value / size),
67- RelativeFee (: final value) => NetworkFee .relative (value),
68- },
69- economic: switch (economic) {
70- AbsoluteFee (: final value) => NetworkFee .relative (value / size),
71- RelativeFee (: final value) => NetworkFee .relative (value),
72- },
73- slow: switch (slow) {
74- AbsoluteFee (: final value) => NetworkFee .relative (value / size),
75- RelativeFee (: final value) => NetworkFee .relative (value),
76- },
124+ fastest: asRelative (fastest),
125+ economic: asRelative (economic),
126+ slow: asRelative (slow),
77127 );
78128 }
79129}
0 commit comments