mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 17:27:39 +00:00
public firo fee estimate and firo fee ui updates
This commit is contained in:
parent
15c4d0e617
commit
88df57177b
3 changed files with 301 additions and 98 deletions
|
@ -167,13 +167,26 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
late Future<String> _calculateFeesFuture;
|
||||
|
||||
Map<int, String> cachedFees = {};
|
||||
Map<int, String> cachedFiroPrivateFees = {};
|
||||
Map<int, String> cachedFiroPublicFees = {};
|
||||
|
||||
Future<String> calculateFees(int amount) async {
|
||||
if (amount <= 0) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
if (cachedFees[amount] != null) {
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private") {
|
||||
if (cachedFiroPrivateFees[amount] != null) {
|
||||
return cachedFiroPrivateFees[amount]!;
|
||||
}
|
||||
} else {
|
||||
if (cachedFiroPublicFees[amount] != null) {
|
||||
return cachedFiroPublicFees[amount]!;
|
||||
}
|
||||
}
|
||||
} else if (cachedFees[amount] != null) {
|
||||
return cachedFees[amount]!;
|
||||
}
|
||||
|
||||
|
@ -195,13 +208,34 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
break;
|
||||
}
|
||||
|
||||
final fee = await manager.estimateFeeFor(amount, feeRate);
|
||||
int fee;
|
||||
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private") {
|
||||
fee = await manager.estimateFeeFor(amount, feeRate);
|
||||
|
||||
cachedFiroPrivateFees[amount] = Format.satoshisToAmount(fee)
|
||||
.toStringAsFixed(Constants.decimalPlaces);
|
||||
|
||||
return cachedFiroPrivateFees[amount]!;
|
||||
} else {
|
||||
fee = await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate);
|
||||
|
||||
cachedFiroPublicFees[amount] = Format.satoshisToAmount(fee)
|
||||
.toStringAsFixed(Constants.decimalPlaces);
|
||||
|
||||
return cachedFiroPublicFees[amount]!;
|
||||
}
|
||||
} else {
|
||||
fee = await manager.estimateFeeFor(amount, feeRate);
|
||||
cachedFees[amount] =
|
||||
Format.satoshisToAmount(fee).toStringAsFixed(Constants.decimalPlaces);
|
||||
|
||||
return cachedFees[amount]!;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _firoBalanceFuture(
|
||||
ChangeNotifierProvider<Manager> provider, String locale) async {
|
||||
|
@ -309,6 +343,22 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
.select((value) => value.getManagerProvider(walletId)));
|
||||
final String locale = ref.watch(
|
||||
localeServiceChangeNotifierProvider.select((value) => value.locale));
|
||||
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
ref.listen(publicPrivateBalanceStateProvider, (previous, next) {
|
||||
if (_amountToSend == null) {
|
||||
setState(() {
|
||||
_calculateFeesFuture = calculateFees(0);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_calculateFeesFuture =
|
||||
calculateFees(Format.decimalAmountToSatoshis(_amountToSend!));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: CFColors.almostWhite,
|
||||
appBar: AppBar(
|
||||
|
@ -1200,7 +1250,16 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
onPressed: (coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet) &&
|
||||
ref
|
||||
.watch(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Private"
|
||||
? null
|
||||
: () {
|
||||
showModalBottomSheet<dynamic>(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
|
@ -1213,28 +1272,22 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
TransactionFeeSelectionSheet(
|
||||
walletId: walletId,
|
||||
amount: Decimal.tryParse(
|
||||
cryptoAmountController.text) ??
|
||||
cryptoAmountController
|
||||
.text) ??
|
||||
Decimal.zero,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
child: ((coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet) &&
|
||||
ref
|
||||
.watch(feeRateTypeStateProvider
|
||||
.watch(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state
|
||||
.prettyName,
|
||||
style: STextStyles.itemSubtitle12,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
.state ==
|
||||
"Private")
|
||||
? Row(
|
||||
children: [
|
||||
FutureBuilder(
|
||||
future: _calculateFeesFuture,
|
||||
builder: (context, snapshot) {
|
||||
|
@ -1243,7 +1296,8 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
snapshot.hasData) {
|
||||
return Text(
|
||||
"~${snapshot.data! as String} ${coin.ticker}",
|
||||
style: STextStyles.itemSubtitle,
|
||||
style:
|
||||
STextStyles.itemSubtitle,
|
||||
);
|
||||
} else {
|
||||
return AnimatedText(
|
||||
|
@ -1253,7 +1307,55 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
"Calculating..",
|
||||
"Calculating...",
|
||||
],
|
||||
style: STextStyles.itemSubtitle,
|
||||
style:
|
||||
STextStyles.itemSubtitle,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
ref
|
||||
.watch(
|
||||
feeRateTypeStateProvider
|
||||
.state)
|
||||
.state
|
||||
.prettyName,
|
||||
style:
|
||||
STextStyles.itemSubtitle12,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _calculateFeesFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState
|
||||
.done &&
|
||||
snapshot.hasData) {
|
||||
return Text(
|
||||
"~${snapshot.data! as String} ${coin.ticker}",
|
||||
style: STextStyles
|
||||
.itemSubtitle,
|
||||
);
|
||||
} else {
|
||||
return AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Calculating",
|
||||
"Calculating.",
|
||||
"Calculating..",
|
||||
"Calculating...",
|
||||
],
|
||||
style: STextStyles
|
||||
.itemSubtitle,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -1335,21 +1437,25 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
_amountToSend!);
|
||||
int availableBalance;
|
||||
if ((coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet)
|
||||
) {
|
||||
coin == Coin.firoTestNet)) {
|
||||
if (ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Private") {
|
||||
availableBalance = Format.decimalAmountToSatoshis(
|
||||
await (manager.wallet as FiroWallet).availablePrivateBalance());
|
||||
availableBalance =
|
||||
Format.decimalAmountToSatoshis(
|
||||
await (manager.wallet
|
||||
as FiroWallet)
|
||||
.availablePrivateBalance());
|
||||
} else {
|
||||
availableBalance = Format.decimalAmountToSatoshis(
|
||||
await (manager.wallet as FiroWallet).availablePublicBalance());
|
||||
availableBalance =
|
||||
Format.decimalAmountToSatoshis(
|
||||
await (manager.wallet
|
||||
as FiroWallet)
|
||||
.availablePublicBalance());
|
||||
}
|
||||
|
||||
} else {
|
||||
availableBalance =
|
||||
Format.decimalAmountToSatoshis(
|
||||
|
|
|
@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/utilities/cfcolors.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -58,35 +60,63 @@ class _TransactionFeeSelectionSheetState
|
|||
required int amount,
|
||||
required FeeRateType feeRateType,
|
||||
required int feeRate,
|
||||
required Coin coin,
|
||||
}) async {
|
||||
switch (feeRateType) {
|
||||
case FeeRateType.fast:
|
||||
if (ref.read(feeSheetSessionCacheProvider).fast[amount] == null) {
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
ref.read(feeSheetSessionCacheProvider).fast[amount] =
|
||||
Format.satoshisToAmount(await ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.estimateFeeFor(amount, feeRate));
|
||||
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate));
|
||||
} else {
|
||||
ref.read(feeSheetSessionCacheProvider).fast[amount] =
|
||||
Format.satoshisToAmount(
|
||||
await manager.estimateFeeFor(amount, feeRate));
|
||||
}
|
||||
}
|
||||
return ref.read(feeSheetSessionCacheProvider).fast[amount]!;
|
||||
|
||||
case FeeRateType.average:
|
||||
if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) {
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
ref.read(feeSheetSessionCacheProvider).average[amount] =
|
||||
Format.satoshisToAmount(await ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.estimateFeeFor(amount, feeRate));
|
||||
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate));
|
||||
} else {
|
||||
ref.read(feeSheetSessionCacheProvider).average[amount] =
|
||||
Format.satoshisToAmount(
|
||||
await manager.estimateFeeFor(amount, feeRate));
|
||||
}
|
||||
}
|
||||
return ref.read(feeSheetSessionCacheProvider).average[amount]!;
|
||||
|
||||
case FeeRateType.slow:
|
||||
if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) {
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
ref.read(feeSheetSessionCacheProvider).slow[amount] =
|
||||
Format.satoshisToAmount(await ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.estimateFeeFor(amount, feeRate));
|
||||
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate));
|
||||
} else {
|
||||
ref.read(feeSheetSessionCacheProvider).slow[amount] =
|
||||
Format.satoshisToAmount(
|
||||
await manager.estimateFeeFor(amount, feeRate));
|
||||
}
|
||||
}
|
||||
return ref.read(feeSheetSessionCacheProvider).slow[amount]!;
|
||||
}
|
||||
|
@ -249,6 +279,7 @@ class _TransactionFeeSelectionSheetState
|
|||
if (feeObject != null)
|
||||
FutureBuilder(
|
||||
future: feeFor(
|
||||
coin: manager.coin,
|
||||
feeRateType: FeeRateType.fast,
|
||||
feeRate: feeObject!.fast,
|
||||
amount: Format
|
||||
|
@ -372,6 +403,7 @@ class _TransactionFeeSelectionSheetState
|
|||
if (feeObject != null)
|
||||
FutureBuilder(
|
||||
future: feeFor(
|
||||
coin: manager.coin,
|
||||
feeRateType: FeeRateType.fast,
|
||||
feeRate: feeObject!.fast,
|
||||
amount: Format
|
||||
|
@ -496,6 +528,7 @@ class _TransactionFeeSelectionSheetState
|
|||
if (feeObject != null)
|
||||
FutureBuilder(
|
||||
future: feeFor(
|
||||
coin: manager.coin,
|
||||
feeRateType: FeeRateType.slow,
|
||||
feeRate: feeObject!.slow,
|
||||
amount: Format
|
||||
|
|
|
@ -4483,6 +4483,70 @@ class FiroWallet extends CoinServiceAPI {
|
|||
return fee;
|
||||
}
|
||||
|
||||
Future<int> estimateFeeForPublic(int satoshiAmount, int feeRate) async {
|
||||
final available =
|
||||
Format.decimalAmountToSatoshis(await availablePublicBalance());
|
||||
|
||||
if (available == satoshiAmount) {
|
||||
return satoshiAmount - sweepAllEstimate(feeRate);
|
||||
} else if (satoshiAmount <= 0 || satoshiAmount > available) {
|
||||
return roughFeeEstimate(1, 2, feeRate);
|
||||
}
|
||||
|
||||
int runningBalance = 0;
|
||||
int inputCount = 0;
|
||||
for (final output in _outputsList) {
|
||||
runningBalance += output.value;
|
||||
inputCount++;
|
||||
if (runningBalance > satoshiAmount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate);
|
||||
final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate);
|
||||
|
||||
if (runningBalance - satoshiAmount > oneOutPutFee) {
|
||||
if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) {
|
||||
final change = runningBalance - satoshiAmount - twoOutPutFee;
|
||||
if (change > DUST_LIMIT &&
|
||||
runningBalance - satoshiAmount - change == twoOutPutFee) {
|
||||
return runningBalance - satoshiAmount - change;
|
||||
} else {
|
||||
return runningBalance - satoshiAmount;
|
||||
}
|
||||
} else {
|
||||
return runningBalance - satoshiAmount;
|
||||
}
|
||||
} else if (runningBalance - satoshiAmount == oneOutPutFee) {
|
||||
return oneOutPutFee;
|
||||
} else {
|
||||
return twoOutPutFee;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: correct formula for firo?
|
||||
int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) {
|
||||
return ((181 * inputCount) + (34 * outputCount) + 10) *
|
||||
(feeRatePerKB / 1000).ceil();
|
||||
}
|
||||
|
||||
int sweepAllEstimate(int feeRate) {
|
||||
int available = 0;
|
||||
int inputCount = 0;
|
||||
for (final output in _outputsList) {
|
||||
if (output.status.confirmed) {
|
||||
available += output.value;
|
||||
inputCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// transaction will only have 1 output minus the fee
|
||||
final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate);
|
||||
|
||||
return available - estimatedFee;
|
||||
}
|
||||
|
||||
Future<List<models.Transaction>> getJMintTransactions(
|
||||
CachedElectrumX cachedClient,
|
||||
List<String> transactions,
|
||||
|
|
Loading…
Reference in a new issue