public firo fee estimate and firo fee ui updates

This commit is contained in:
julian 2022-09-07 10:58:54 -06:00
parent 15c4d0e617
commit 88df57177b
3 changed files with 301 additions and 98 deletions

View file

@ -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,12 +208,33 @@ class _SendViewState extends ConsumerState<SendView> {
break;
}
final fee = await manager.estimateFeeFor(amount, feeRate);
int fee;
cachedFees[amount] =
Format.satoshisToAmount(fee).toStringAsFixed(Constants.decimalPlaces);
if (coin == Coin.firo || coin == Coin.firoTestNet) {
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
"Private") {
fee = await manager.estimateFeeFor(amount, feeRate);
return cachedFees[amount]!;
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(
@ -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,74 +1250,126 @@ class _SendViewState extends ConsumerState<SendView> {
Constants.size.circularBorderRadius,
),
),
onPressed: () {
showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
onPressed: (coin == Coin.firo ||
coin == Coin.firoTestNet) &&
ref
.watch(
publicPrivateBalanceStateProvider
.state)
.state ==
"Private"
? null
: () {
showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (_) =>
TransactionFeeSelectionSheet(
walletId: walletId,
amount: Decimal.tryParse(
cryptoAmountController
.text) ??
Decimal.zero,
),
);
},
child: ((coin == Coin.firo ||
coin == Coin.firoTestNet) &&
ref
.watch(
publicPrivateBalanceStateProvider
.state)
.state ==
"Private")
? Row(
children: [
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,
);
}
},
),
],
)
: 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,
);
}
},
),
],
),
SvgPicture.asset(
Assets.svg.chevronDown,
width: 8,
height: 4,
color: CFColors.gray3,
),
],
),
),
builder: (_) =>
TransactionFeeSelectionSheet(
walletId: walletId,
amount: Decimal.tryParse(
cryptoAmountController.text) ??
Decimal.zero,
),
);
},
child: 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,
);
}
},
),
],
),
SvgPicture.asset(
Assets.svg.chevronDown,
width: 8,
height: 4,
color: CFColors.gray3,
),
],
),
),
)
],
@ -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 ==
.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(

View file

@ -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) {
ref.read(feeSheetSessionCacheProvider).fast[amount] =
Format.satoshisToAmount(await ref
.read(walletsChangeNotifierProvider)
.getManager(walletId)
.estimateFeeFor(amount, feeRate));
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 (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) {
ref.read(feeSheetSessionCacheProvider).average[amount] =
Format.satoshisToAmount(await ref
.read(walletsChangeNotifierProvider)
.getManager(walletId)
.estimateFeeFor(amount, feeRate));
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 (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) {
ref.read(feeSheetSessionCacheProvider).slow[amount] =
Format.satoshisToAmount(await ref
.read(walletsChangeNotifierProvider)
.getManager(walletId)
.estimateFeeFor(amount, feeRate));
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 (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

View file

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