mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 09:47:37 +00:00
feat: custom fee for sends on mobile for supported coins
This commit is contained in:
parent
5880e0c5fa
commit
0778deb6d3
17 changed files with 808 additions and 166 deletions
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -179,6 +179,8 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
|
|||
? tokenFeeSessionCacheProvider
|
||||
: feeSheetSessionCacheProvider)
|
||||
.slow[amount]!;
|
||||
default:
|
||||
return Amount.zero;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue