Merge pull request #595 from cypherstack/custom_fee_selection

Custom fee selection
This commit is contained in:
Diego Salazar 2023-06-19 15:08:52 -06:00 committed by GitHub
commit 86630f2e85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 1573 additions and 311 deletions

View file

@ -160,7 +160,7 @@ class ElectrumX {
"JSONRPC response\n"
" command: $command\n"
" args: $args\n"
" error: $response.data",
" error: ${response.data}",
);
}

View file

@ -468,6 +468,31 @@ class _ConfirmTransactionViewState
],
),
),
if (transactionInfo["fee"] is int &&
transactionInfo["vSize"] is int)
const SizedBox(
height: 12,
),
if (transactionInfo["fee"] is int &&
transactionInfo["vSize"] is int)
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"sats/vByte",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
"~${(transactionInfo["fee"] / transactionInfo["vSize"]).toInt()}",
style: STextStyles.itemSubtitle12(context),
),
],
),
),
const SizedBox(
height: 12,
),
@ -906,6 +931,43 @@ class _ConfirmTransactionViewState
),
),
),
if (isDesktop &&
!widget.isPaynymTransaction &&
transactionInfo["fee"] is int &&
transactionInfo["vSize"] is int)
Padding(
padding: const EdgeInsets.only(
left: 32,
),
child: Text(
"sats/vByte",
style: STextStyles.desktopTextExtraExtraSmall(context),
),
),
if (isDesktop &&
!widget.isPaynymTransaction &&
transactionInfo["fee"] is int &&
transactionInfo["vSize"] is int)
Padding(
padding: const EdgeInsets.only(
top: 10,
left: 32,
right: 32,
),
child: RoundedContainer(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 18,
),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
child: Text(
"~${(transactionInfo["fee"] / transactionInfo["vSize"]).toInt()}",
style: STextStyles.itemSubtitle(context),
),
),
),
if (!isDesktop) const Spacer(),
SizedBox(
height: isDesktop ? 23 : 12,

View file

@ -56,6 +56,7 @@ import 'package:stackwallet/widgets/animated_text.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/fee_slider.dart';
import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
@ -284,6 +285,8 @@ class _SendViewState extends ConsumerState<SendView> {
case FeeRateType.slow:
feeRate = feeObject.slow;
break;
default:
feeRate = -1;
}
Amount fee;
@ -299,6 +302,8 @@ class _SendViewState extends ConsumerState<SendView> {
case FeeRateType.slow:
specialMoneroId = MoneroTransactionPriority.slow;
break;
default:
throw ArgumentError("custom fee not available for monero");
}
fee = await manager.estimateFeeFor(amount, specialMoneroId.raw!);
@ -494,6 +499,7 @@ class _SendViewState extends ConsumerState<SendView> {
isSegwit: widget.accountLite!.segwit,
amount: amount,
args: {
"satsPerVByte": isCustomFee ? customFeeRate : null,
"feeRate": feeRate,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
@ -508,7 +514,10 @@ class _SendViewState extends ConsumerState<SendView> {
txDataFuture = (manager.wallet as FiroWallet).prepareSendPublic(
address: _address!,
amount: amount,
args: {"feeRate": ref.read(feeRateTypeStateProvider)},
args: {
"feeRate": ref.read(feeRateTypeStateProvider),
"satsPerVByte": isCustomFee ? customFeeRate : null,
},
);
} else {
txDataFuture = manager.prepareSend(
@ -516,6 +525,7 @@ class _SendViewState extends ConsumerState<SendView> {
amount: amount,
args: {
"feeRate": ref.read(feeRateTypeStateProvider),
"satsPerVByte": isCustomFee ? customFeeRate : null,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
selectedUTXOs.isNotEmpty)
@ -593,6 +603,10 @@ class _SendViewState extends ConsumerState<SendView> {
bool get isPaynymSend => widget.accountLite != null;
bool isCustomFee = false;
int customFeeRate = 1;
@override
void initState() {
coin = widget.coin;
@ -1830,17 +1844,23 @@ class _SendViewState extends ConsumerState<SendView> {
const SizedBox(
height: 12,
),
if (coin != Coin.epicCash)
if (coin != Coin.epicCash &&
coin != Coin.nano &&
coin != Coin.banano)
Text(
"Transaction fee (estimated)",
style: STextStyles.smallMed12(context),
textAlign: TextAlign.left,
),
if (coin != Coin.epicCash)
if (coin != Coin.epicCash &&
coin != Coin.nano &&
coin != Coin.banano)
const SizedBox(
height: 8,
),
if (coin != Coin.epicCash)
if (coin != Coin.epicCash &&
coin != Coin.nano &&
coin != Coin.banano)
Stack(
children: [
TextField(
@ -1898,6 +1918,15 @@ class _SendViewState extends ConsumerState<SendView> {
fractionDigits: coin.decimals,
),
updateChosen: (String fee) {
if (fee == "custom") {
if (!isCustomFee) {
setState(() {
isCustomFee = true;
});
}
return;
}
_setCurrentFee(
fee,
true,
@ -1905,6 +1934,9 @@ class _SendViewState extends ConsumerState<SendView> {
setState(() {
_calculateFeesFuture =
Future(() => fee);
if (isCustomFee) {
isCustomFee = false;
}
});
},
),
@ -1928,11 +1960,11 @@ class _SendViewState extends ConsumerState<SendView> {
.done &&
snapshot.hasData) {
_setCurrentFee(
snapshot.data! as String,
snapshot.data!,
false,
);
return Text(
"~${snapshot.data! as String}",
"~${snapshot.data!}",
style: STextStyles
.itemSubtitle(
context),
@ -1984,12 +2016,13 @@ class _SendViewState extends ConsumerState<SendView> {
.done &&
snapshot.hasData) {
_setCurrentFee(
snapshot.data!
as String,
snapshot.data!,
false,
);
return Text(
"~${snapshot.data! as String}",
isCustomFee
? ""
: "~${snapshot.data!}",
style: STextStyles
.itemSubtitle(
context),
@ -2025,6 +2058,18 @@ class _SendViewState extends ConsumerState<SendView> {
)
],
),
if (isCustomFee)
Padding(
padding: const EdgeInsets.only(
bottom: 12,
top: 16,
),
child: FeeSlider(
onSatVByteChanged: (rate) {
customFeeRate = rate;
},
),
),
const Spacer(),
const SizedBox(
height: 12,

View file

@ -161,6 +161,9 @@ class _TransactionFeeSelectionSheetState
}
}
return ref.read(feeSheetSessionCacheProvider).slow[amount]!;
default:
return Amount.zero;
}
}
@ -567,8 +570,6 @@ class _TransactionFeeSelectionSheetState
.watch(feeRateTypeStateProvider.state)
.state,
onChanged: (x) {
//todo: check if print needed
// debugPrint(x.toString());
ref
.read(feeRateTypeStateProvider.state)
.state = FeeRateType.slow;
@ -672,6 +673,79 @@ class _TransactionFeeSelectionSheetState
const SizedBox(
height: 24,
),
if (manager.coin.isElectrumXCoin)
GestureDetector(
onTap: () {
final state =
ref.read(feeRateTypeStateProvider.state).state;
if (state != FeeRateType.custom) {
ref.read(feeRateTypeStateProvider.state).state =
FeeRateType.custom;
}
widget.updateChosen("custom");
Navigator.of(context).pop();
},
child: Container(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: FeeRateType.custom,
groupValue: ref
.watch(feeRateTypeStateProvider.state)
.state,
onChanged: (x) {
ref
.read(
feeRateTypeStateProvider.state)
.state = FeeRateType.custom;
Navigator.of(context).pop();
},
),
),
],
),
const SizedBox(
width: 12,
),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
FeeRateType.custom.prettyName,
style:
STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
const SizedBox(
height: 2,
),
],
),
),
],
),
),
),
if (manager.coin.isElectrumXCoin)
const SizedBox(
height: 24,
),
],
);
},
@ -714,6 +788,8 @@ class _TransactionFeeSelectionSheetState
);
}
return null;
case FeeRateType.custom:
return null;
}
} catch (e, s) {
Logging.instance.log("$e $s", level: LogLevel.Warning);

View file

@ -360,6 +360,8 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
case FeeRateType.slow:
feeRate = feeObject.slow;
break;
default:
feeRate = -1;
}
final Amount fee = wallet.estimateFeeFor(feeRate);

View file

@ -179,6 +179,8 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.slow[amount]!;
default:
return Amount.zero;
}
}

View file

@ -12,13 +12,14 @@ import 'dart:async';
import 'dart:math';
import 'package:bip47/bip47.dart';
import 'package:cw_core/monero_transaction_priority.dart';
import 'package:decimal/decimal.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/models/contact_address_entry.dart';
import 'package:stackwallet/models/isar/models/contact_entry.dart';
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
@ -46,16 +47,20 @@ import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/animated_text.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_fee_dialog.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/fee_slider.dart';
import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
@ -116,6 +121,17 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
bool get isPaynymSend => widget.accountLite != null;
bool isCustomFee = false;
int customFeeRate = 1;
(FeeRateType, String?, String?)? feeSelectionResult;
final stringsToLoopThrough = [
"Calculating",
"Calculating.",
"Calculating..",
"Calculating...",
];
Future<void> previewSend() async {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
@ -284,6 +300,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
isSegwit: widget.accountLite!.segwit,
amount: amount,
args: {
"satsPerVByte": isCustomFee ? customFeeRate : null,
"feeRate": feeRate,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
@ -300,6 +317,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
amount: amount,
args: {
"feeRate": ref.read(feeRateTypeStateProvider),
"satsPerVByte": isCustomFee ? customFeeRate : null,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
ref.read(desktopUseUTXOs).isNotEmpty)
@ -313,6 +331,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
amount: amount,
args: {
"feeRate": ref.read(feeRateTypeStateProvider),
"satsPerVByte": isCustomFee ? customFeeRate : null,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
ref.read(desktopUseUTXOs).isNotEmpty)
@ -562,12 +581,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
);
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading balance",
"Loading balance.",
"Loading balance..",
"Loading balance...",
],
stringsToLoopThrough: stringsToLoopThrough,
style: STextStyles.itemSubtitle(context),
);
}
@ -1369,94 +1383,178 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
}
},
),
// const SizedBox(
// height: 20,
// ),
// Text(
// "Note (optional)",
// style: STextStyles.desktopTextExtraSmall(context).copyWith(
// color: Theme.of(context)
// .extension<StackColors>()!
// .textFieldActiveSearchIconRight,
// ),
// textAlign: TextAlign.left,
// ),
// const SizedBox(
// height: 10,
// ),
// ClipRRect(
// borderRadius: BorderRadius.circular(
// Constants.size.circularBorderRadius,
// ),
// child: TextField(
// minLines: 1,
// maxLines: 5,
// autocorrect: Util.isDesktop ? false : true,
// enableSuggestions: Util.isDesktop ? false : true,
// controller: noteController,
// focusNode: _noteFocusNode,
// style: STextStyles.desktopTextExtraSmall(context).copyWith(
// color: Theme.of(context)
// .extension<StackColors>()!
// .textFieldActiveText,
// height: 1.8,
// ),
// onChanged: (_) => setState(() {}),
// decoration: standardInputDecoration(
// "Type something...",
// _noteFocusNode,
// context,
// desktopMed: true,
// ).copyWith(
// contentPadding: const EdgeInsets.only(
// left: 16,
// top: 11,
// bottom: 12,
// right: 5,
// ),
// suffixIcon: noteController.text.isNotEmpty
// ? Padding(
// padding: const EdgeInsets.only(right: 0),
// child: UnconstrainedBox(
// child: Row(
// children: [
// TextFieldIconButton(
// child: const XIcon(),
// onTap: () async {
// setState(() {
// noteController.text = "";
// });
// },
// ),
// ],
// ),
// ),
// )
// : null,
// ),
// ),
// ),
if (!isPaynymSend)
const SizedBox(
height: 20,
),
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
Text(
"Transaction fee (${coin == Coin.ethereum ? "max" : "estimated"})",
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
ConditionalParent(
condition: coin.isElectrumXCoin,
builder: (child) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
child,
CustomTextButton(
text: "Edit",
onTap: () async {
feeSelectionResult = await showDialog<
(
FeeRateType,
String?,
String?,
)?>(
context: context,
builder: (_) => DesktopFeeDialog(
walletId: walletId,
),
);
if (feeSelectionResult != null) {
if (isCustomFee &&
feeSelectionResult!.$1 != FeeRateType.custom) {
isCustomFee = false;
} else if (!isCustomFee &&
feeSelectionResult!.$1 == FeeRateType.custom) {
isCustomFee = true;
}
}
setState(() {});
},
),
],
),
child: Text(
"Transaction fee"
"${isCustomFee ? "" : " (${coin == Coin.ethereum ? "max" : "estimated"})"}",
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
textAlign: TextAlign.left,
),
textAlign: TextAlign.left,
),
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
const SizedBox(
height: 10,
),
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
DesktopFeeDropDown(
walletId: walletId,
if (!isCustomFee)
(feeSelectionResult?.$2 == null)
? FutureBuilder(
future: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value.getManager(walletId).fees,
),
),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return DesktopFeeItem(
feeObject: snapshot.data,
feeRateType: FeeRateType.average,
walletId: walletId,
feeFor: ({
required Amount amount,
required FeeRateType feeRateType,
required int feeRate,
required Coin coin,
}) async {
if (ref
.read(feeSheetSessionCacheProvider)
.average[amount] ==
null) {
final manager = ref
.read(walletsChangeNotifierProvider)
.getManager(walletId);
if (coin == Coin.monero || coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(amount,
MoneroTransactionPriority.regular.raw!);
ref
.read(feeSheetSessionCacheProvider)
.average[amount] = fee;
} else if ((coin == Coin.firo ||
coin == Coin.firoTestNet) &&
ref
.read(
publicPrivateBalanceStateProvider
.state)
.state !=
"Private") {
ref
.read(feeSheetSessionCacheProvider)
.average[amount] =
await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate);
} else {
ref
.read(feeSheetSessionCacheProvider)
.average[amount] =
await manager.estimateFeeFor(
amount, feeRate);
}
}
return ref
.read(feeSheetSessionCacheProvider)
.average[amount]!;
},
isSelected: true,
);
} else {
return Row(
children: [
AnimatedText(
stringsToLoopThrough: stringsToLoopThrough,
style: STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
),
],
);
}
},
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
feeSelectionResult?.$2 ?? "",
style: STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
textAlign: TextAlign.left,
),
Text(
feeSelectionResult?.$3 ?? "",
style: STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
),
],
),
if (isCustomFee)
Padding(
padding: const EdgeInsets.only(
bottom: 12,
top: 16,
),
child: FeeSlider(
onSatVByteChanged: (rate) {
customFeeRate = rate;
},
),
),
const SizedBox(
height: 36,

View file

@ -14,7 +14,7 @@ import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/contact_address_entry.dart';
import 'package:stackwallet/models/isar/models/contact_entry.dart';
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';

View file

@ -287,8 +287,8 @@ class BitcoinWallet extends CoinServiceAPI
@override
Future<int> get maxFee async {
final fee = (await fees).fast as String;
final satsFee =
Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
final satsFee = Decimal.parse(fee) *
Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
return satsFee.floor().toBigInt().toInt();
}
@ -1070,9 +1070,60 @@ class BitcoinWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -1087,6 +1138,8 @@ class BitcoinWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2201,6 +2254,7 @@ class BitcoinWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2308,18 +2362,22 @@ class BitcoinWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
selectedTxFeeRate,
).raw.toInt();
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
if (satsPerVByte == null) {
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
selectedTxFeeRate,
).raw.toInt();
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
}
}
final int amount = satoshiAmountToSend - feeForOneOutput;
@ -2373,15 +2431,19 @@ class BitcoinWallet extends CoinServiceAPI
}
// Assume 1 output, only for recipient and no change
final feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
final feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
final feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
Logging.instance
.log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info);
@ -2576,6 +2638,7 @@ class BitcoinWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,

View file

@ -955,9 +955,60 @@ class BitcoinCashWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -972,6 +1023,8 @@ class BitcoinCashWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2187,6 +2240,7 @@ class BitcoinCashWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2303,10 +2357,12 @@ class BitcoinCashWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
if (feeForOneOutput < (vSizeForOneOutput + 1)) {
feeForOneOutput = (vSizeForOneOutput + 1);
}
@ -2350,20 +2406,21 @@ class BitcoinCashWallet extends CoinServiceAPI
satoshisBeingUsed - satoshiAmountToSend - 1,
], // dust limit is the minimum amount a change output should be
))["vSize"] as int;
//todo: check if print needed
// debugPrint("vSizeForOneOutput $vSizeForOneOutput");
// debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts");
// Assume 1 output, only for recipient and no change
var feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
var feeForTwoOutputs = estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
int feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
Logging.instance
.log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info);
@ -2575,6 +2632,7 @@ class BitcoinCashWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,

View file

@ -941,9 +941,60 @@ class DogecoinWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -958,6 +1009,8 @@ class DogecoinWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2092,6 +2145,7 @@ class DogecoinWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2199,10 +2253,12 @@ class DogecoinWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) {
feeForOneOutput = (vSizeForOneOutput + 1) * 1000;
}
@ -2248,15 +2304,19 @@ class DogecoinWallet extends CoinServiceAPI
debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts");
// Assume 1 output, only for recipient and no change
var feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
var feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
var feeForTwoOutputs = estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
var feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
Logging.instance
.log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info);
@ -2463,6 +2523,7 @@ class DogecoinWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,

View file

@ -1409,6 +1409,7 @@ class ECashWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -1517,18 +1518,22 @@ class ECashWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
selectedTxFeeRate,
).raw.toInt();
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
if (satsPerVByte == null) {
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
selectedTxFeeRate,
).raw.toInt();
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
}
}
final int amount = satoshiAmountToSend - feeForOneOutput;
@ -1586,15 +1591,19 @@ class ECashWallet extends CoinServiceAPI
}
// Assume 1 output, only for recipient and no change
final feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
final feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
final feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
Logging.instance
.log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info);
@ -1795,6 +1804,7 @@ class ECashWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,
@ -1977,8 +1987,6 @@ class ECashWallet extends CoinServiceAPI
final tx = builder.build();
final txHex = tx.toHex();
final vSize = tx.virtualSize();
//todo: check if print needed
Logger.print("ecash raw hex: $txHex");
return {"hex": txHex, "vSize": vSize};
}
@ -2549,9 +2557,60 @@ class ECashWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -2566,6 +2625,9 @@ class ECashWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {

View file

@ -1029,8 +1029,55 @@ class FiroWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final result = await coinSelection(
amount.raw.toInt(),
-1,
address,
isSendAll,
satsPerVByte: customSatsPerVByte,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -1045,6 +1092,8 @@ class FiroWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -1296,6 +1345,7 @@ class FiroWallet extends CoinServiceAPI
int selectedTxFeeRate,
String _recipientAddress,
bool isSendAll, {
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -1385,10 +1435,12 @@ class FiroWallet extends CoinServiceAPI
recipients: [_recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
if (feeForOneOutput < vSizeForOneOutput + 1) {
feeForOneOutput = vSizeForOneOutput + 1;
@ -1429,20 +1481,21 @@ class FiroWallet extends CoinServiceAPI
satoshisBeingUsed - satoshiAmountToSend - 1,
], // dust limit is the minimum amount a change output should be
))["vSize"] as int;
//todo: check if print needed
debugPrint("vSizeForOneOutput $vSizeForOneOutput");
debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts");
// Assume 1 output, only for recipient and no change
var feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
var feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
var feeForTwoOutputs = estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
var feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
Logging.instance
.log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info);
@ -1641,9 +1694,15 @@ class FiroWallet extends CoinServiceAPI
level: LogLevel.Warning);
// try adding more outputs
if (spendableOutputs.length > inputsBeingConsumed) {
return coinSelection(satoshiAmountToSend, selectedTxFeeRate,
_recipientAddress, isSendAll,
additionalOutputs: additionalOutputs + 1, utxos: utxos);
return coinSelection(
satoshiAmountToSend,
selectedTxFeeRate,
_recipientAddress,
isSendAll,
additionalOutputs: additionalOutputs + 1,
satsPerVByte: satsPerVByte,
utxos: utxos,
);
}
return 2;
}

View file

@ -232,8 +232,8 @@ class LitecoinWallet extends CoinServiceAPI
@override
Future<int> get maxFee async {
final fee = (await fees).fast as String;
final satsFee =
Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
final satsFee = Decimal.parse(fee) *
Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
return satsFee.floor().toBigInt().toInt();
}
@ -1058,9 +1058,60 @@ class LitecoinWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -1075,6 +1126,8 @@ class LitecoinWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2298,6 +2351,7 @@ class LitecoinWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2403,18 +2457,22 @@ class LitecoinWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
selectedTxFeeRate,
).raw.toInt();
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
if (satsPerVByte == null) {
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
selectedTxFeeRate,
).raw.toInt();
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
}
}
final int amount = satoshiAmountToSend - feeForOneOutput;
@ -2455,15 +2513,19 @@ class LitecoinWallet extends CoinServiceAPI
))["vSize"] as int;
// Assume 1 output, only for recipient and no change
final feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
final feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
final feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
Logging.instance
.log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info);
@ -2659,6 +2721,7 @@ class LitecoinWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,

View file

@ -458,6 +458,8 @@ class MoneroWallet extends CoinServiceAPI with WalletCache, WalletDB {
case FeeRateType.slow:
feePriority = MoneroTransactionPriority.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
Future<PendingTransaction>? awaitPendingTransaction;

View file

@ -224,8 +224,8 @@ class NamecoinWallet extends CoinServiceAPI
@override
Future<int> get maxFee async {
final fee = (await fees).fast as String;
final satsFee =
Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
final satsFee = Decimal.parse(fee) *
Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
return satsFee.floor().toBigInt().toInt();
}
@ -1048,9 +1048,60 @@ class NamecoinWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -1065,6 +1116,8 @@ class NamecoinWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2274,6 +2327,7 @@ class NamecoinWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2379,18 +2433,22 @@ class NamecoinWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
selectedTxFeeRate,
).raw.toInt();
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
if (satsPerVByte == null) {
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
selectedTxFeeRate,
).raw.toInt();
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
}
}
final int amount = satoshiAmountToSend - feeForOneOutput;
@ -2431,15 +2489,19 @@ class NamecoinWallet extends CoinServiceAPI
))["vSize"] as int;
// Assume 1 output, only for recipient and no change
final feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
final feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
final feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
Logging.instance
.log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info);
@ -2635,6 +2697,7 @@ class NamecoinWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,

View file

@ -219,8 +219,8 @@ class ParticlWallet extends CoinServiceAPI
@override
Future<int> get maxFee async {
final fee = (await fees).fast as String;
final satsFee =
Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
final satsFee = Decimal.parse(fee) *
Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
return satsFee.floor().toBigInt().toInt();
}
@ -975,9 +975,60 @@ class ParticlWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -992,6 +1043,8 @@ class ParticlWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2441,6 +2494,7 @@ class ParticlWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2546,18 +2600,22 @@ class ParticlWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
selectedTxFeeRate,
).raw.toInt();
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
if (satsPerVByte == null) {
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
selectedTxFeeRate,
).raw.toInt();
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
}
}
final int amount = satoshiAmountToSend - feeForOneOutput;
@ -2598,15 +2656,19 @@ class ParticlWallet extends CoinServiceAPI
))["vSize"] as int;
// Assume 1 output, only for recipient and no change
final feeForOneOutput = estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
final feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
final feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
Logging.instance
.log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info);
@ -2803,6 +2865,7 @@ class ParticlWallet extends CoinServiceAPI
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
recipientAddress: recipientAddress,
satsPerVByte: satsPerVByte,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,
utxos: utxos,

View file

@ -486,6 +486,8 @@ class WowneroWallet extends CoinServiceAPI with WalletCache, WalletDB {
case FeeRateType.slow:
feePriority = MoneroTransactionPriority.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
Future<PendingTransaction>? awaitPendingTransaction;

View file

@ -84,17 +84,12 @@ class Wallets extends ChangeNotifier {
}
final List<Tuple2<Coin, List<ChangeNotifierProvider<Manager>>>> result = [];
for (final coin in map.keys) {
result.add(Tuple2(coin, map[coin]!));
for (final coin in Coin.values) {
if (map[coin] != null) {
result.add(Tuple2(coin, map[coin]!));
}
}
// result.sort((a, b) => a.item1.prettyName.compareTo(b.item1.prettyName));
result.sort((a, b) => a.item1.prettyName == "Bitcoin"
? -1
: b.item1.prettyName == "Monero"
? 1
: a.item1.prettyName.compareTo(b.item1.prettyName));
return result;
}

View file

@ -8,7 +8,7 @@
*
*/
enum FeeRateType { fast, average, slow }
enum FeeRateType { fast, average, slow, custom }
extension FeeRateTypeExt on FeeRateType {
String get prettyName {
@ -19,6 +19,8 @@ extension FeeRateTypeExt on FeeRateType {
return "Average";
case FeeRateType.slow:
return "Slow";
case FeeRateType.custom:
return "Custom";
}
}
}

View file

@ -0,0 +1,412 @@
import 'package:cw_core/monero_transaction_priority.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/animated_text.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
class DesktopFeeDialog extends ConsumerStatefulWidget {
const DesktopFeeDialog({
Key? key,
required this.walletId,
this.isToken = false,
}) : super(key: key);
final String walletId;
final bool isToken;
@override
ConsumerState<DesktopFeeDialog> createState() => _DesktopFeeDialogState();
}
class _DesktopFeeDialogState extends ConsumerState<DesktopFeeDialog> {
late final String walletId;
FeeObject? feeObject;
FeeRateType feeRateType = FeeRateType.average;
Future<Amount> feeFor({
required Amount amount,
required FeeRateType feeRateType,
required int feeRate,
required Coin coin,
}) async {
switch (feeRateType) {
case FeeRateType.fast:
if (ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.fast[amount] ==
null) {
if (widget.isToken == false) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if (coin == Coin.monero || coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(
amount, MoneroTransactionPriority.fast.raw!);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).fast[amount] =
await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate);
} else {
ref.read(feeSheetSessionCacheProvider).fast[amount] =
await manager.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
ref.read(tokenFeeSessionCacheProvider).fast[amount] = fee;
}
}
return ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.fast[amount]!;
case FeeRateType.average:
if (ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.average[amount] ==
null) {
if (widget.isToken == false) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if (coin == Coin.monero || coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(
amount, MoneroTransactionPriority.regular.raw!);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).average[amount] =
await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate);
} else {
ref.read(feeSheetSessionCacheProvider).average[amount] =
await manager.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
ref.read(tokenFeeSessionCacheProvider).average[amount] = fee;
}
}
return ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.average[amount]!;
case FeeRateType.slow:
if (ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.slow[amount] ==
null) {
if (widget.isToken == false) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if (coin == Coin.monero || coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(
amount, MoneroTransactionPriority.slow.raw!);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).slow[amount] =
await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate);
} else {
ref.read(feeSheetSessionCacheProvider).slow[amount] =
await manager.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
ref.read(tokenFeeSessionCacheProvider).slow[amount] = fee;
}
}
return ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.slow[amount]!;
default:
return Amount.zero;
}
}
@override
void initState() {
walletId = widget.walletId;
super.initState();
}
@override
Widget build(BuildContext context) {
return DesktopDialog(
maxWidth: 450,
maxHeight: double.infinity,
child: FutureBuilder(
future: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value.getManager(walletId).fees,
),
),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
feeObject = snapshot.data!;
}
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 32),
child: Text(
"Choose fee",
style: STextStyles.desktopH3(context),
),
),
const DesktopDialogCloseButton(),
],
),
...FeeRateType.values.map(
(e) => Padding(
padding: const EdgeInsets.only(
left: 32,
right: 32,
bottom: 16,
),
child: DesktopFeeItem(
feeObject: feeObject,
feeRateType: e,
walletId: walletId,
feeFor: feeFor,
isSelected: false,
),
),
),
const SizedBox(
height: 16,
),
],
);
},
),
);
}
}
class DesktopFeeItem extends ConsumerStatefulWidget {
const DesktopFeeItem({
Key? key,
required this.feeObject,
required this.feeRateType,
required this.walletId,
required this.feeFor,
required this.isSelected,
}) : super(key: key);
final FeeObject? feeObject;
final FeeRateType feeRateType;
final String walletId;
final Future<Amount> Function({
required Amount amount,
required FeeRateType feeRateType,
required int feeRate,
required Coin coin,
}) feeFor;
final bool isSelected;
@override
ConsumerState<DesktopFeeItem> createState() => _DesktopFeeItemState();
}
class _DesktopFeeItemState extends ConsumerState<DesktopFeeItem> {
String? feeString;
String? timeString;
static const stringsToLoopThrough = [
"Calculating",
"Calculating.",
"Calculating..",
"Calculating...",
];
String estimatedTimeToBeIncludedInNextBlock(
int targetBlockTime, int estimatedNumberOfBlocks) {
int time = targetBlockTime * estimatedNumberOfBlocks;
int hours = (time / 3600).floor();
if (hours > 1) {
return "~$hours hours";
} else if (hours == 1) {
return "~$hours hour";
}
// less than an hour
final string = (time / 60).toStringAsFixed(1);
if (string == "1.0") {
return "~1 minute";
} else {
if (string.endsWith(".0")) {
return "~${(time / 60).floor()} minutes";
}
return "~$string minutes";
}
}
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType : ${widget.feeRateType}");
return MaterialButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onPressed: () {
Navigator.of(context).pop(
(
widget.feeRateType,
feeString,
timeString,
),
);
},
child: Builder(
builder: (_) {
if (widget.feeRateType == FeeRateType.custom) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.feeRateType.prettyName,
style:
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
textAlign: TextAlign.left,
),
],
);
}
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(widget.walletId)));
if (widget.feeObject == null) {
return AnimatedText(
stringsToLoopThrough: stringsToLoopThrough,
style: STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
);
} else {
return FutureBuilder(
future: widget.feeFor(
coin: manager.coin,
feeRateType: widget.feeRateType,
feeRate: widget.feeRateType == FeeRateType.fast
? widget.feeObject!.fast
: widget.feeRateType == FeeRateType.slow
? widget.feeObject!.slow
: widget.feeObject!.medium,
amount: ref.watch(sendAmountProvider.state).state,
),
builder: (_, AsyncSnapshot<Amount> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
feeString = "${widget.feeRateType.prettyName} "
"(~${ref.watch(pAmountFormatter(manager.coin)).format(
snapshot.data!,
indicatePrecisionLoss: false,
)})";
timeString = manager.coin == Coin.ethereum
? ""
: estimatedTimeToBeIncludedInNextBlock(
Constants.targetBlockTimeInSeconds(manager.coin),
widget.feeRateType == FeeRateType.fast
? widget.feeObject!.numberOfBlocksFast
: widget.feeRateType == FeeRateType.slow
? widget.feeObject!.numberOfBlocksSlow
: widget.feeObject!.numberOfBlocksAverage,
);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
feeString!,
style: STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
textAlign: TextAlign.left,
),
if (widget.feeObject != null)
Text(
timeString!,
style: STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
),
],
);
} else {
return AnimatedText(
stringsToLoopThrough: stringsToLoopThrough,
style: STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
);
}
},
);
}
},
),
);
}
}

View file

@ -0,0 +1,56 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:stackwallet/utilities/text_styles.dart';
class FeeSlider extends StatefulWidget {
const FeeSlider({
super.key,
required this.onSatVByteChanged,
});
final void Function(int) onSatVByteChanged;
@override
State<FeeSlider> createState() => _FeeSliderState();
}
class _FeeSliderState extends State<FeeSlider> {
static const int min = 1;
static const int max = 4;
double sliderValue = 0;
int rate = min;
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"sat/vByte",
style: STextStyles.smallMed12(context),
),
Text(
"$rate",
style: STextStyles.smallMed12(context),
),
],
),
Slider(
value: sliderValue,
onChanged: (value) {
setState(() {
sliderValue = value;
rate = pow(sliderValue * (max - min) + min, 4).toInt();
});
widget.onSatVByteChanged(rate);
},
),
],
);
}
}

View file

@ -1761,7 +1761,7 @@ packages:
path: wakelock_windows
ref: "2a9bca63a540771f241d688562351482b2cf234c"
resolved-ref: "2a9bca63a540771f241d688562351482b2cf234c"
url: "https://github.com/timsneath/wakelock"
url: "https://github.com/diegotori/wakelock"
source: git
version: "0.2.2"
wallet:

View file

@ -182,7 +182,7 @@ dependency_overrides:
# required for dart 3, at least until a fix is merged upstream
wakelock_windows:
git:
url: https://github.com/timsneath/wakelock
url: https://github.com/diegotori/wakelock
ref: 2a9bca63a540771f241d688562351482b2cf234c
path: wakelock_windows

View file

@ -987,7 +987,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i27.BitcoinWallet {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
_i23.Future<List<_i18.UTXO>> get utxos => (super.noSuchMethod(
@ -1408,6 +1408,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i27.BitcoinWallet {
required String? recipientAddress,
required bool? coinControl,
required bool? isSendAll,
int? satsPerVByte,
int? additionalOutputs = 0,
List<_i18.UTXO>? utxos,
}) =>
@ -1420,6 +1421,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i27.BitcoinWallet {
#recipientAddress: recipientAddress,
#coinControl: coinControl,
#isSendAll: isSendAll,
#satsPerVByte: satsPerVByte,
#additionalOutputs: additionalOutputs,
#utxos: utxos,
},
@ -2837,7 +2839,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(
@ -3205,7 +3207,7 @@ class MockCoinServiceAPI extends _i1.Mock implements _i20.CoinServiceAPI {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -269,7 +269,7 @@ class MockManager extends _i1.Mock implements _i12.Manager {
@override
_i13.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i13.Coin.banano,
returnValue: _i13.Coin.bitcoin,
) as _i13.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -230,7 +230,7 @@ class MockManager extends _i1.Mock implements _i10.Manager {
@override
_i11.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i11.Coin.banano,
returnValue: _i11.Coin.bitcoin,
) as _i11.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -228,7 +228,7 @@ class MockManager extends _i1.Mock implements _i10.Manager {
@override
_i11.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i11.Coin.banano,
returnValue: _i11.Coin.bitcoin,
) as _i11.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -548,7 +548,7 @@ class MockManager extends _i1.Mock implements _i13.Manager {
@override
_i9.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i9.Coin.banano,
returnValue: _i9.Coin.bitcoin,
) as _i9.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -335,7 +335,7 @@ class MockManager extends _i1.Mock implements _i10.Manager {
@override
_i8.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i8.Coin.banano,
returnValue: _i8.Coin.bitcoin,
) as _i8.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -335,7 +335,7 @@ class MockManager extends _i1.Mock implements _i10.Manager {
@override
_i8.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i8.Coin.banano,
returnValue: _i8.Coin.bitcoin,
) as _i8.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -335,7 +335,7 @@ class MockManager extends _i1.Mock implements _i10.Manager {
@override
_i8.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i8.Coin.banano,
returnValue: _i8.Coin.bitcoin,
) as _i8.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -102,7 +102,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i7.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i7.Coin.banano,
returnValue: _i7.Coin.bitcoin,
) as _i7.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -333,7 +333,7 @@ class MockManager extends _i1.Mock implements _i10.Manager {
@override
_i8.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i8.Coin.banano,
returnValue: _i8.Coin.bitcoin,
) as _i8.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -548,7 +548,7 @@ class MockManager extends _i1.Mock implements _i13.Manager {
@override
_i9.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i9.Coin.banano,
returnValue: _i9.Coin.bitcoin,
) as _i9.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -389,7 +389,7 @@ class MockManager extends _i1.Mock implements _i13.Manager {
@override
_i11.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i11.Coin.banano,
returnValue: _i11.Coin.bitcoin,
) as _i11.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -102,7 +102,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i7.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i7.Coin.banano,
returnValue: _i7.Coin.bitcoin,
) as _i7.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -102,7 +102,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i7.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i7.Coin.banano,
returnValue: _i7.Coin.bitcoin,
) as _i7.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -317,7 +317,7 @@ class MockManager extends _i1.Mock implements _i12.Manager {
@override
_i10.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i10.Coin.banano,
returnValue: _i10.Coin.bitcoin,
) as _i10.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -317,7 +317,7 @@ class MockManager extends _i1.Mock implements _i12.Manager {
@override
_i10.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i10.Coin.banano,
returnValue: _i10.Coin.bitcoin,
) as _i10.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -102,7 +102,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i7.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i7.Coin.banano,
returnValue: _i7.Coin.bitcoin,
) as _i7.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -102,7 +102,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i7.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i7.Coin.banano,
returnValue: _i7.Coin.bitcoin,
) as _i7.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -333,7 +333,7 @@ class MockManager extends _i1.Mock implements _i10.Manager {
@override
_i8.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i8.Coin.banano,
returnValue: _i8.Coin.bitcoin,
) as _i8.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -569,7 +569,7 @@ class MockManager extends _i1.Mock implements _i15.Manager {
@override
_i9.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i9.Coin.banano,
returnValue: _i9.Coin.bitcoin,
) as _i9.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -333,7 +333,7 @@ class MockManager extends _i1.Mock implements _i10.Manager {
@override
_i8.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i8.Coin.banano,
returnValue: _i8.Coin.bitcoin,
) as _i8.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -104,7 +104,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i7.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i7.Coin.banano,
returnValue: _i7.Coin.bitcoin,
) as _i7.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -103,7 +103,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i7.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i7.Coin.banano,
returnValue: _i7.Coin.bitcoin,
) as _i7.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -102,7 +102,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i7.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i7.Coin.banano,
returnValue: _i7.Coin.bitcoin,
) as _i7.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -144,7 +144,7 @@ class MockManager extends _i1.Mock implements _i9.Manager {
@override
_i10.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i10.Coin.banano,
returnValue: _i10.Coin.bitcoin,
) as _i10.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -104,7 +104,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i7.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i7.Coin.banano,
returnValue: _i7.Coin.bitcoin,
) as _i7.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -217,7 +217,7 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet {
@override
_i12.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i12.Coin.banano,
returnValue: _i12.Coin.bitcoin,
) as _i12.Coin);
@override
_i11.Future<List<String>> get mnemonic => (super.noSuchMethod(
@ -487,6 +487,7 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet {
int? selectedTxFeeRate,
String? _recipientAddress,
bool? isSendAll, {
int? satsPerVByte,
int? additionalOutputs = 0,
List<_i13.UTXO>? utxos,
}) =>
@ -499,6 +500,7 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet {
isSendAll,
],
{
#satsPerVByte: satsPerVByte,
#additionalOutputs: additionalOutputs,
#utxos: utxos,
},

View file

@ -782,7 +782,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
_i23.Future<List<_i17.UTXO>> get utxos => (super.noSuchMethod(
@ -1202,6 +1202,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet {
required String? recipientAddress,
required bool? coinControl,
required bool? isSendAll,
int? satsPerVByte,
int? additionalOutputs = 0,
List<_i17.UTXO>? utxos,
}) =>
@ -1214,6 +1215,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet {
#recipientAddress: recipientAddress,
#coinControl: coinControl,
#isSendAll: isSendAll,
#satsPerVByte: satsPerVByte,
#additionalOutputs: additionalOutputs,
#utxos: utxos,
},
@ -2831,7 +2833,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(
@ -3199,7 +3201,7 @@ class MockCoinServiceAPI extends _i1.Mock implements _i20.CoinServiceAPI {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -866,7 +866,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i28.BitcoinWallet {
@override
_i21.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i21.Coin.banano,
returnValue: _i21.Coin.bitcoin,
) as _i21.Coin);
@override
_i22.Future<List<_i17.UTXO>> get utxos => (super.noSuchMethod(
@ -1287,6 +1287,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i28.BitcoinWallet {
required String? recipientAddress,
required bool? coinControl,
required bool? isSendAll,
int? satsPerVByte,
int? additionalOutputs = 0,
List<_i17.UTXO>? utxos,
}) =>
@ -1299,6 +1300,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i28.BitcoinWallet {
#recipientAddress: recipientAddress,
#coinControl: coinControl,
#isSendAll: isSendAll,
#satsPerVByte: satsPerVByte,
#additionalOutputs: additionalOutputs,
#utxos: utxos,
},
@ -2077,7 +2079,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i21.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i21.Coin.banano,
returnValue: _i21.Coin.bitcoin,
) as _i21.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(
@ -2445,7 +2447,7 @@ class MockCoinServiceAPI extends _i1.Mock implements _i19.CoinServiceAPI {
@override
_i21.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i21.Coin.banano,
returnValue: _i21.Coin.bitcoin,
) as _i21.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -466,7 +466,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i18.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i18.Coin.banano,
returnValue: _i18.Coin.bitcoin,
) as _i18.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(
@ -838,7 +838,7 @@ class MockCoinServiceAPI extends _i1.Mock implements _i7.CoinServiceAPI {
@override
_i18.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i18.Coin.banano,
returnValue: _i18.Coin.bitcoin,
) as _i18.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(
@ -1230,7 +1230,7 @@ class MockFiroWallet extends _i1.Mock implements _i23.FiroWallet {
@override
_i18.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i18.Coin.banano,
returnValue: _i18.Coin.bitcoin,
) as _i18.Coin);
@override
_i19.Future<List<String>> get mnemonic => (super.noSuchMethod(
@ -1500,6 +1500,7 @@ class MockFiroWallet extends _i1.Mock implements _i23.FiroWallet {
int? selectedTxFeeRate,
String? _recipientAddress,
bool? isSendAll, {
int? satsPerVByte,
int? additionalOutputs = 0,
List<_i22.UTXO>? utxos,
}) =>
@ -1512,6 +1513,7 @@ class MockFiroWallet extends _i1.Mock implements _i23.FiroWallet {
isSendAll,
],
{
#satsPerVByte: satsPerVByte,
#additionalOutputs: additionalOutputs,
#utxos: utxos,
},

View file

@ -521,7 +521,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i24.BitcoinWallet {
@override
_i20.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i20.Coin.banano,
returnValue: _i20.Coin.bitcoin,
) as _i20.Coin);
@override
_i21.Future<List<_i17.UTXO>> get utxos => (super.noSuchMethod(
@ -941,6 +941,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i24.BitcoinWallet {
required String? recipientAddress,
required bool? coinControl,
required bool? isSendAll,
int? satsPerVByte,
int? additionalOutputs = 0,
List<_i17.UTXO>? utxos,
}) =>
@ -953,6 +954,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i24.BitcoinWallet {
#recipientAddress: recipientAddress,
#coinControl: coinControl,
#isSendAll: isSendAll,
#satsPerVByte: satsPerVByte,
#additionalOutputs: additionalOutputs,
#utxos: utxos,
},

View file

@ -776,7 +776,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
_i23.Future<List<_i17.UTXO>> get utxos => (super.noSuchMethod(
@ -1196,6 +1196,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet {
required String? recipientAddress,
required bool? coinControl,
required bool? isSendAll,
int? satsPerVByte,
int? additionalOutputs = 0,
List<_i17.UTXO>? utxos,
}) =>
@ -1208,6 +1209,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet {
#recipientAddress: recipientAddress,
#coinControl: coinControl,
#isSendAll: isSendAll,
#satsPerVByte: satsPerVByte,
#additionalOutputs: additionalOutputs,
#utxos: utxos,
},
@ -2186,7 +2188,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(
@ -2554,7 +2556,7 @@ class MockCoinServiceAPI extends _i1.Mock implements _i20.CoinServiceAPI {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(

View file

@ -878,7 +878,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i29.BitcoinWallet {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
_i23.Future<List<_i17.UTXO>> get utxos => (super.noSuchMethod(
@ -1299,6 +1299,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i29.BitcoinWallet {
required String? recipientAddress,
required bool? coinControl,
required bool? isSendAll,
int? satsPerVByte,
int? additionalOutputs = 0,
List<_i17.UTXO>? utxos,
}) =>
@ -1311,6 +1312,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i29.BitcoinWallet {
#recipientAddress: recipientAddress,
#coinControl: coinControl,
#isSendAll: isSendAll,
#satsPerVByte: satsPerVByte,
#additionalOutputs: additionalOutputs,
#utxos: utxos,
},
@ -2289,7 +2291,7 @@ class MockManager extends _i1.Mock implements _i6.Manager {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(
@ -2657,7 +2659,7 @@ class MockCoinServiceAPI extends _i1.Mock implements _i20.CoinServiceAPI {
@override
_i22.Coin get coin => (super.noSuchMethod(
Invocation.getter(#coin),
returnValue: _i22.Coin.banano,
returnValue: _i22.Coin.bitcoin,
) as _i22.Coin);
@override
bool get isRefreshing => (super.noSuchMethod(