feat: custom fee for sends on mobile for supported coins

This commit is contained in:
julian 2023-06-17 10:42:23 -06:00
parent 5880e0c5fa
commit 0778deb6d3
17 changed files with 808 additions and 166 deletions

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,
),

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';
@ -300,6 +301,8 @@ class _SendViewState extends ConsumerState<SendView> {
case FeeRateType.slow:
feeRate = feeObject.slow;
break;
default:
feeRate = -1;
}
Amount fee;
@ -315,6 +318,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!);
@ -510,6 +515,7 @@ class _SendViewState extends ConsumerState<SendView> {
isSegwit: widget.accountLite!.segwit,
amount: amount,
args: {
"satsPerVByte": isCustomFee ? customFeeRate : null,
"feeRate": feeRate,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
@ -524,7 +530,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(
@ -532,6 +541,7 @@ class _SendViewState extends ConsumerState<SendView> {
amount: amount,
args: {
"feeRate": ref.read(feeRateTypeStateProvider),
"satsPerVByte": isCustomFee ? customFeeRate : null,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
selectedUTXOs.isNotEmpty)
@ -609,6 +619,10 @@ class _SendViewState extends ConsumerState<SendView> {
bool get isPaynymSend => widget.accountLite != null;
bool isCustomFee = false;
int customFeeRate = 1;
@override
void initState() {
coin = widget.coin;
@ -1913,6 +1927,15 @@ class _SendViewState extends ConsumerState<SendView> {
fractionDigits: coin.decimals,
),
updateChosen: (String fee) {
if (fee == "custom") {
if (!isCustomFee) {
setState(() {
isCustomFee = true;
});
}
return;
}
_setCurrentFee(
fee,
true,
@ -1920,6 +1943,9 @@ class _SendViewState extends ConsumerState<SendView> {
setState(() {
_calculateFeesFuture =
Future(() => fee);
if (isCustomFee) {
isCustomFee = false;
}
});
},
),
@ -1999,12 +2025,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),
@ -2040,6 +2067,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

@ -374,6 +374,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

@ -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

@ -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

@ -17,7 +17,7 @@ class FeeSlider extends StatefulWidget {
class _FeeSliderState extends State<FeeSlider> {
static const int min = 1;
static const int max = 10;
static const int max = 4;
double sliderValue = 0;