Merge pull request #326 from cypherstack/paynyms

Paynyms
This commit is contained in:
Diego Salazar 2023-01-30 14:18:09 -07:00 committed by GitHub
commit 5252c3efff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 366 additions and 236 deletions

View file

@ -108,9 +108,36 @@ class _BuyFormState extends ConsumerState<BuyForm> {
static Decimal maxCrypto = Decimal.parse((10000.00000000).toString());
static String boundedCryptoTicker = '';
void fiatFieldOnChanged(String value) async {}
String _amountOutOfRangeErrorString = "";
void validateAmount() {
if (_buyAmountController.text.isEmpty) {
setState(() {
_amountOutOfRangeErrorString = "";
});
return;
}
void cryptoFieldOnChanged(String value) async {}
final value = Decimal.tryParse(_buyAmountController.text);
if (value == null) {
setState(() {
_amountOutOfRangeErrorString = "Invalid amount";
});
} else if (value > maxFiat && buyWithFiat) {
setState(() {
_amountOutOfRangeErrorString =
"Maximum amount: ${maxFiat.toStringAsFixed(2)}";
});
} else if (value < minFiat && buyWithFiat) {
setState(() {
_amountOutOfRangeErrorString =
"Minimum amount: ${minFiat.toStringAsFixed(2)}";
});
} else {
setState(() {
_amountOutOfRangeErrorString = "";
});
}
}
void selectCrypto() async {
if (ref.read(simplexProvider).supportedCryptos.isEmpty) {
@ -253,6 +280,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
minFiat = fiat.minAmount != minFiat ? fiat.minAmount : minFiat;
maxFiat = fiat.maxAmount != maxFiat ? fiat.maxAmount : maxFiat;
});
validateAmount();
},
);
}
@ -359,11 +387,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
}
}
String? _fetchIconUrlFromTicker(String? ticker) {
if (ticker == null) return null;
return null;
}
// String? _fetchIconUrlFromTicker(String? ticker) {
// if (ticker == null) return null;
//
// return null;
// }
bool isStackCoin(String? ticker) {
if (ticker == null) return false;
@ -377,13 +405,15 @@ class _BuyFormState extends ConsumerState<BuyForm> {
}
Widget? getIconForTicker(String ticker) {
String? iconAsset = /*isStackCoin(ticker)
String iconAsset = /*isStackCoin(ticker)
?*/
Assets.svg.iconFor(coin: coinFromTickerCaseInsensitive(ticker));
// : Assets.svg.buyIconFor(ticker);
return (iconAsset != null)
? SvgPicture.asset(iconAsset, height: 20, width: 20)
: null;
// return (iconAsset != null)
// ? SvgPicture.asset(iconAsset, height: 20, width: 20)
// : null;
return SvgPicture.asset(iconAsset, height: 20, width: 20);
}
Future<void> previewQuote(SimplexQuote quote) async {
@ -501,25 +531,25 @@ class _BuyFormState extends ConsumerState<BuyForm> {
}
} else {
// Error; probably amount out of bounds
String errorMessage = "${quoteResponse.exception?.errorMessage}";
if (errorMessage.contains('must be between')) {
errorMessage = errorMessage.substring(
(errorMessage.indexOf('getQuote exception: ') ?? 19) + 20,
errorMessage.indexOf(", value: null"));
_BuyFormState.boundedCryptoTicker = errorMessage.substring(
errorMessage.indexOf('The ') + 4,
errorMessage.indexOf(' amount must be between'));
_BuyFormState.minCrypto = Decimal.parse(errorMessage.substring(
errorMessage.indexOf('must be between ') + 16,
errorMessage.indexOf(' and ')));
_BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring(
errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length,
errorMessage.length));
if (Decimal.parse(_buyAmountController.text) >
_BuyFormState.maxCrypto) {
_buyAmountController.text = _BuyFormState.maxCrypto.toString();
}
}
// String errorMessage = "${quoteResponse.exception?.errorMessage}";
// if (errorMessage.contains('must be between')) {
// errorMessage = errorMessage.substring(
// errorMessage.indexOf('getQuote exception: ') + 20,
// errorMessage.indexOf(", value: null"));
// _BuyFormState.boundedCryptoTicker = errorMessage.substring(
// errorMessage.indexOf('The ') + 4,
// errorMessage.indexOf(' amount must be between'));
// _BuyFormState.minCrypto = Decimal.parse(errorMessage.substring(
// errorMessage.indexOf('must be between ') + 16,
// errorMessage.indexOf(' and ')));
// _BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring(
// errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length,
// errorMessage.length));
// if (Decimal.parse(_buyAmountController.text) >
// _BuyFormState.maxCrypto) {
// _buyAmountController.text = _BuyFormState.maxCrypto.toString();
// }
// }
await showDialog<dynamic>(
context: context,
barrierDismissible: true,
@ -541,7 +571,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
height: 24,
),
Text(
errorMessage,
quoteResponse.exception!.errorMessage,
style: STextStyles.smallMed14(context),
),
const SizedBox(
@ -603,10 +633,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
level: LogLevel.Warning,
);
return BuyResponse(
exception: BuyException(
response.toString(),
BuyExceptionType.generic,
),
exception: response.exception ??
BuyException(
response.toString(),
BuyExceptionType.generic,
),
);
}
}
@ -852,7 +883,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
.textFieldDefaultBG,
child: Padding(
padding: const EdgeInsets.only(
left: 12.0, top: 12.0, right: 12.0, bottom: 12.0),
left: 12.0,
top: 12.0,
right: 12.0,
bottom: 12.0,
),
child: Row(
children: <Widget>[
Container(
@ -878,7 +913,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
width: 8,
),
Text(
"${selectedFiat?.ticker ?? 'ERR'}",
selectedFiat?.ticker ?? 'ERR',
style: STextStyles.largeMedium14(context),
),
const SizedBox(
@ -886,7 +921,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
),
Expanded(
child: Text(
"${selectedFiat?.name ?? 'Error'}",
selectedFiat?.name ?? 'Error',
style: STextStyles.largeMedium14(context),
),
),
@ -924,6 +959,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
setState(() {
buyWithFiat = !buyWithFiat;
});
validateAmount();
},
)
],
@ -938,10 +974,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
color: Theme.of(context).extension<StackColors>()!.textDark,
),
key: const Key("buyAmountInputFieldTextFieldKey"),
controller: _buyAmountController
..text = _BuyFormState.buyWithFiat
? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00'
: _BuyFormState.minCrypto.toStringAsFixed(8),
controller: _buyAmountController,
// note: setting the text value here will set it every time this widget rebuilds
// ..text = _BuyFormState.buyWithFiat
// ? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00'
// : _BuyFormState.minCrypto.toStringAsFixed(8),
focusNode: _buyAmountFocusNode,
keyboardType: Util.isDesktop
? null
@ -950,7 +987,10 @@ class _BuyFormState extends ConsumerState<BuyForm> {
decimal: true,
),
textAlign: TextAlign.left,
inputFormatters: [NumericalRangeFormatter()],
// inputFormatters: [NumericalRangeFormatter()],
onChanged: (_) {
validateAmount();
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
// top: 22,
@ -985,8 +1025,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
),
child: Text(
format.simpleCurrencySymbol(
selectedFiat?.ticker ??
"ERR".toUpperCase()),
selectedFiat?.ticker.toUpperCase() ??
"ERR"),
textAlign: TextAlign.center,
style: STextStyles.smallMed12(context).copyWith(
color: Theme.of(context)
@ -1023,18 +1063,20 @@ class _BuyFormState extends ConsumerState<BuyForm> {
key: const Key(
"buyViewClearAmountFieldButtonKey"),
onTap: () {
if (_BuyFormState.buyWithFiat) {
_buyAmountController.text = _BuyFormState
.minFiat
.toStringAsFixed(2);
} else {
if (selectedCrypto?.ticker ==
_BuyFormState.boundedCryptoTicker) {
_buyAmountController.text = _BuyFormState
.minCrypto
.toStringAsFixed(8);
}
}
// if (_BuyFormState.buyWithFiat) {
// _buyAmountController.text = _BuyFormState
// .minFiat
// .toStringAsFixed(2);
// } else {
// if (selectedCrypto?.ticker ==
// _BuyFormState.boundedCryptoTicker) {
// _buyAmountController.text = _BuyFormState
// .minCrypto
// .toStringAsFixed(8);
// }
// }
_buyAmountController.text = "";
validateAmount();
},
child: const XIcon(),
)
@ -1051,7 +1093,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
_buyAmountController.text =
amountString.toString();
setState(() {});
validateAmount();
}
},
child: _buyAmountController.text.isEmpty
@ -1064,6 +1106,14 @@ class _BuyFormState extends ConsumerState<BuyForm> {
),
),
),
SizedBox(
height: isDesktop ? 10 : 4,
),
if (_amountOutOfRangeErrorString.isNotEmpty)
Text(
_amountOutOfRangeErrorString,
style: STextStyles.errorSmall(context),
),
SizedBox(
height: isDesktop ? 20 : 12,
),
@ -1171,7 +1221,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
_receiveAddressController.text = "";
_address = "";
setState(() {
_addressToggleFlag = true;
_addressToggleFlag = false;
});
},
child: const XIcon(),
@ -1342,27 +1392,15 @@ class _BuyFormState extends ConsumerState<BuyForm> {
SizedBox(
height: isDesktop ? 20 : 12,
),
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
if (_receiveAddressController.text.isNotEmpty &&
_buyAmountController.text.isNotEmpty) {
previewQuote(quote);
}
},
child: PrimaryButton(
buttonHeight: isDesktop ? ButtonHeight.l : null,
enabled: _receiveAddressController.text.isNotEmpty &&
_buyAmountController.text.isNotEmpty,
onPressed: () {
if (_receiveAddressController.text.isNotEmpty &&
_buyAmountController.text.isNotEmpty) {
previewQuote(quote);
}
},
label: "Preview quote",
)),
PrimaryButton(
buttonHeight: isDesktop ? ButtonHeight.l : null,
enabled: _addressToggleFlag &&
_amountOutOfRangeErrorString.isEmpty &&
_buyAmountController.text.isNotEmpty,
onPressed: () {
previewQuote(quote);
},
label: "Preview quote",
),
],
),
@ -1371,51 +1409,53 @@ class _BuyFormState extends ConsumerState<BuyForm> {
}
}
// See https://stackoverflow.com/a/68072967
class NumericalRangeFormatter extends TextInputFormatter {
NumericalRangeFormatter();
// might need this again in the future
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
TextSelection newSelection = newValue.selection;
String newVal = _BuyFormState.buyWithFiat
? Decimal.parse(newValue.text).toStringAsFixed(2)
: Decimal.parse(newValue.text).toStringAsFixed(8);
if (newValue.text == '') {
return newValue;
} else {
if (_BuyFormState.buyWithFiat) {
if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) {
newVal = _BuyFormState.minFiat.toStringAsFixed(2);
// _BuyFormState._buyAmountController.selection =
// TextSelection.collapsed(
// offset: _BuyFormState.buyWithFiat
// ? _BuyFormState._buyAmountController.text.length - 2
// : _BuyFormState._buyAmountController.text.length - 8);
} else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) {
newVal = _BuyFormState.maxFiat.toStringAsFixed(2);
}
} else if (!_BuyFormState.buyWithFiat &&
_BuyFormState.selectedCrypto?.ticker ==
_BuyFormState.boundedCryptoTicker) {
if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) {
newVal = _BuyFormState.minCrypto.toStringAsFixed(8);
} else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) {
newVal = _BuyFormState.maxCrypto.toStringAsFixed(8);
}
}
}
final regexString = _BuyFormState.buyWithFiat
? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$'
: r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
// return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
return RegExp(regexString).hasMatch(newVal)
? TextEditingValue(text: newVal, selection: newSelection)
: oldValue;
}
}
// // See https://stackoverflow.com/a/68072967
// class NumericalRangeFormatter extends TextInputFormatter {
// NumericalRangeFormatter();
//
// @override
// TextEditingValue formatEditUpdate(
// TextEditingValue oldValue,
// TextEditingValue newValue,
// ) {
// TextSelection newSelection = newValue.selection;
// String newVal = _BuyFormState.buyWithFiat
// ? Decimal.parse(newValue.text).toStringAsFixed(2)
// : Decimal.parse(newValue.text).toStringAsFixed(8);
// if (newValue.text == '') {
// return newValue;
// } else {
// if (_BuyFormState.buyWithFiat) {
// if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) {
// newVal = _BuyFormState.minFiat.toStringAsFixed(2);
// // _BuyFormState._buyAmountController.selection =
// // TextSelection.collapsed(
// // offset: _BuyFormState.buyWithFiat
// // ? _BuyFormState._buyAmountController.text.length - 2
// // : _BuyFormState._buyAmountController.text.length - 8);
// } else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) {
// newVal = _BuyFormState.maxFiat.toStringAsFixed(2);
// }
// } else if (!_BuyFormState.buyWithFiat &&
// _BuyFormState.selectedCrypto?.ticker ==
// _BuyFormState.boundedCryptoTicker) {
// if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) {
// newVal = _BuyFormState.minCrypto.toStringAsFixed(8);
// } else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) {
// newVal = _BuyFormState.maxCrypto.toStringAsFixed(8);
// }
// }
// }
//
// final regexString = _BuyFormState.buyWithFiat
// ? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$'
// : r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
//
// // return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// return RegExp(regexString).hasMatch(newVal)
// ? TextEditingValue(text: newVal, selection: newSelection)
// : oldValue;
// }
// }

View file

@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/route_generator.dart';
@ -85,7 +84,13 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
children: [
TransactionCard(
// this may mess with combined firo transactions
key: Key(tx.txid + tx.type.name + tx.address.value.toString()), //
key: Key(tx.txid +
tx.type.name +
tx.address.value.toString() +
ref
.watch(widget.managerProvider
.select((value) => value.currentHeight))
.toString()), //
transaction: tx,
walletId: widget.walletId,
),
@ -180,7 +185,13 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
),
child: TransactionCard(
// this may mess with combined firo transactions
key: Key(tx.txid + tx.type.name + tx.address.value.toString()), //
key: Key(tx.txid +
tx.type.name +
tx.address.value.toString() +
ref
.watch(widget.managerProvider
.select((value) => value.currentHeight))
.toString()), //
transaction: tx,
walletId: widget.walletId,
),
@ -188,13 +199,6 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
}
}
void updateHeightProvider(Manager manager) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
ref.read(currentHeightProvider(manager.coin).state).state =
manager.currentHeight;
});
}
@override
void initState() {
managerProvider = widget.managerProvider;
@ -203,14 +207,9 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
@override
Widget build(BuildContext context) {
// final managerProvider = ref
// .watch(walletsChangeNotifierProvider)
// .getManagerProvider(widget.walletId);
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(widget.walletId)));
updateHeightProvider(manager);
return FutureBuilder(
future: manager.transactions,
builder: (fbContext, AsyncSnapshot<List<Transaction>> snapshot) {

View file

@ -360,62 +360,6 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
),
),
),
if (ref.watch(walletsChangeNotifierProvider.select((value) =>
value.getManager(widget.walletId).hasPaynymSupport)))
RawMaterialButton(
constraints: const BoxConstraints(
minWidth: 66,
),
onPressed: () {
if (scale == 0) {
setState(() {
scale = 1;
});
} else if (scale == 1) {
setState(() {
scale = 0;
});
}
},
splashColor:
Theme.of(context).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
widget.height / 2.0,
),
),
child: Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Spacer(),
const SizedBox(
height: 2,
),
SvgPicture.asset(
Assets.svg.bars,
width: 20,
height: 20,
),
const SizedBox(
height: 6,
),
Text(
"More",
style: STextStyles.buttonSmall(context),
),
const Spacer(),
],
),
),
),
),
const SizedBox(
width: 12,
),
if (widget.coin.hasBuySupport)
RawMaterialButton(
constraints: const BoxConstraints(
@ -455,10 +399,65 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
),
),
),
if (widget.coin.hasBuySupport)
const SizedBox(
width: 12,
if (ref.watch(walletsChangeNotifierProvider.select((value) =>
value.getManager(widget.walletId).hasPaynymSupport)))
RawMaterialButton(
constraints: const BoxConstraints(
minWidth: 66,
),
onPressed: () {
if (scale == 0) {
setState(() {
scale = 1;
});
} else if (scale == 1) {
setState(() {
scale = 0;
});
}
},
splashColor:
Theme.of(context).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
widget.height / 2.0,
),
),
child: Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Spacer(),
const SizedBox(
height: 2,
),
SvgPicture.asset(
Assets.svg.bars,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.bottomNavIconIcon,
),
const SizedBox(
height: 6,
),
Text(
"More",
style: STextStyles.buttonSmall(context),
),
const Spacer(),
],
),
),
),
),
const SizedBox(
width: 12,
),
],
),
),

View file

@ -10,7 +10,6 @@ import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
@ -131,7 +130,9 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
// check if address book name contains
contains |= contacts
.where((e) =>
e.addresses.where((a) => a.address == tx.address).isNotEmpty &&
e.addresses
.where((a) => a.address == tx.address.value?.value)
.isNotEmpty &&
e.name.toLowerCase().contains(keyword))
.isNotEmpty;
@ -853,7 +854,8 @@ class _DesktopTransactionCardRowState
prefix = "";
}
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
final currentHeight = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).currentHeight));
return Material(
color: Theme.of(context).extension<StackColors>()!.popupBG,

View file

@ -11,7 +11,6 @@ import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
@ -298,7 +297,8 @@ class _TransactionDetailsViewState
@override
Widget build(BuildContext context) {
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
final currentHeight = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).currentHeight));
return ConditionalParent(
condition: !isDesktop,

View file

@ -774,9 +774,6 @@ class _WalletViewState extends ConsumerState<WalletView> {
);
},
onBuyPressed: () {
// TODO set default coin to currently open wallet here by passing it as an argument
// final coin = ref.read(managerProvider).coin;
unawaited(Navigator.of(context).pushNamed(
BuyInWalletView.routeName,
arguments: coin,

View file

@ -1,6 +0,0 @@
import 'package:dart_numerics/dart_numerics.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
final currentHeightProvider =
StateProvider.family<int, Coin>((ref, coin) => int64MaxValue);

View file

@ -1,4 +1,8 @@
enum BuyExceptionType { generic, serializeResponseError }
enum BuyExceptionType {
generic,
serializeResponseError,
cryptoAmountOutOfRange,
}
class BuyException implements Exception {
String errorMessage;

View file

@ -210,7 +210,20 @@ class SimplexAPI {
BuyResponse<SimplexQuote> _parseQuote(dynamic jsonArray) {
try {
String cryptoAmount = "${jsonArray['digital_money']['amount']}";
// final Map<String, dynamic> lol =
// Map<String, dynamic>.from(jsonArray as Map);
String? cryptoAmount = jsonArray['digital_money']?['amount'] as String?;
if (cryptoAmount == null) {
String error = jsonArray['error'] as String;
return BuyResponse(
exception: BuyException(
error,
BuyExceptionType.cryptoAmountOutOfRange,
),
);
}
SimplexQuote quote = jsonArray['quote'] as SimplexQuote;
final SimplexQuote _quote = SimplexQuote(
@ -277,9 +290,9 @@ class SimplexAPI {
}
final jsonArray = jsonDecode(res.body); // TODO check if valid json
if (jsonArray.containsKey('error') as bool) {
if (jsonArray['error'] == true || jsonArray['error'] == 'true') {
throw Exception(jsonArray['message']);
}
if (jsonArray['error'] == true || jsonArray['error'] == 'true') {
throw Exception(jsonArray['message']);
}
}
SimplexOrder _order = SimplexOrder(

View file

@ -251,6 +251,14 @@ class BitcoinWallet extends CoinServiceAPI
final result = await _electrumXClient.getBlockHeadTip();
final height = result["height"] as int;
await updateCachedChainHeight(height);
if (height > storedChainHeight) {
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"Updated current chain height in $walletId $walletName!",
walletId,
),
);
}
return height;
} catch (e, s) {
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
@ -1314,6 +1322,8 @@ class BitcoinWallet extends CoinServiceAPI
checkChangeAddressForTransactions: _checkChangeAddressForTransactions,
addDerivation: addDerivation,
addDerivations: addDerivations,
dustLimitP2PKH: DUST_LIMIT_P2PKH,
minConfirms: MINIMUM_CONFIRMATIONS,
);
}

View file

@ -217,6 +217,14 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
final result = await _electrumXClient.getBlockHeadTip();
final height = result["height"] as int;
await updateCachedChainHeight(height);
if (height > storedChainHeight) {
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"Updated current chain height in $walletId $walletName!",
walletId,
),
);
}
return height;
} catch (e, s) {
Logging.instance.log("Exception caught in chainHeight: $e\n$s",

View file

@ -226,6 +226,14 @@ class DogecoinWallet extends CoinServiceAPI
final result = await _electrumXClient.getBlockHeadTip();
final height = result["height"] as int;
await updateCachedChainHeight(height);
if (height > storedChainHeight) {
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"Updated current chain height in $walletId $walletName!",
walletId,
),
);
}
return height;
} catch (e, s) {
Logging.instance.log("Exception caught in chainHeight: $e\n$s",

View file

@ -1416,6 +1416,14 @@ class EpicCashWallet extends CoinServiceAPI
});
await updateCachedChainHeight(latestHeight!);
if (latestHeight! > storedChainHeight) {
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"Updated current chain height in $walletId $walletName!",
walletId,
),
);
}
return latestHeight!;
} catch (e, s) {
Logging.instance.log("Exception caught in chainHeight: $e\n$s",

View file

@ -4897,6 +4897,14 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
final result = await _electrumXClient.getBlockHeadTip();
final height = result["height"] as int;
await updateCachedChainHeight(height);
if (height > storedChainHeight) {
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"Updated current chain height in $walletId $walletName!",
walletId,
),
);
}
return height;
} catch (e, s) {
Logging.instance.log("Exception caught in chainHeight: $e\n$s",

View file

@ -241,6 +241,14 @@ class LitecoinWallet extends CoinServiceAPI
final result = await _electrumXClient.getBlockHeadTip();
final height = result["height"] as int;
await updateCachedChainHeight(height);
if (height > storedChainHeight) {
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"Updated current chain height in $walletId $walletName!",
walletId,
),
);
}
return height;
} catch (e, s) {
Logging.instance.log("Exception caught in chainHeight: $e\n$s",

View file

@ -236,6 +236,14 @@ class NamecoinWallet extends CoinServiceAPI
final result = await _electrumXClient.getBlockHeadTip();
final height = result["height"] as int;
await updateCachedChainHeight(height);
if (height > storedChainHeight) {
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"Updated current chain height in $walletId $walletName!",
walletId,
),
);
}
return height;
} catch (e, s) {
Logging.instance.log("Exception caught in chainHeight: $e\n$s",

View file

@ -231,6 +231,14 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
final result = await _electrumXClient.getBlockHeadTip();
final height = result["height"] as int;
await updateCachedChainHeight(height);
if (height > storedChainHeight) {
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"Updated current chain height in $walletId $walletName!",
walletId,
),
);
}
return height;
} catch (e, s) {
Logging.instance.log("Exception caught in chainHeight: $e\n$s",

View file

@ -189,14 +189,16 @@ mixin ElectrumXParsing {
TransactionSubType txSubType = TransactionSubType.none;
if (this is PaynymWalletInterface && outs.length > 1 && ins.isNotEmpty) {
List<String>? scriptChunks = outs[1].scriptPubKeyAsm?.split(" ");
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
final blindedPaymentCode = scriptChunks![1];
final bytes = blindedPaymentCode.fromHex;
for (int i = 0; i < outs.length; i++) {
List<String>? scriptChunks = outs[i].scriptPubKeyAsm?.split(" ");
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
final blindedPaymentCode = scriptChunks![1];
final bytes = blindedPaymentCode.fromHex;
// https://en.bitcoin.it/wiki/BIP_0047#Sending
if (bytes.length == 80 && bytes.first == 1) {
txSubType = TransactionSubType.bip47Notification;
// https://en.bitcoin.it/wiki/BIP_0047#Sending
if (bytes.length == 80 && bytes.first == 1) {
txSubType = TransactionSubType.bip47Notification;
}
}
}
}

View file

@ -15,7 +15,6 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart';
import 'package:stackwallet/exceptions/wallet/paynym_send_exception.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
import 'package:stackwallet/utilities/bip32_utils.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
@ -35,6 +34,8 @@ mixin PaynymWalletInterface {
late final MainDB _db;
late final ElectrumX _electrumXClient;
late final SecureStorageInterface _secureStorage;
late final int _dustLimitP2PKH;
late final int _minConfirms;
// passed in wallet functions
late final Future<List<String>> Function() _getMnemonic;
@ -79,6 +80,8 @@ mixin PaynymWalletInterface {
required MainDB db,
required ElectrumX electrumXClient,
required SecureStorageInterface secureStorage,
required int dustLimitP2PKH,
required int minConfirms,
required Future<List<String>> Function() getMnemonic,
required Future<int> Function() getChainHeight,
required Future<String> Function() getCurrentChangeAddress,
@ -125,6 +128,8 @@ mixin PaynymWalletInterface {
_db = db;
_electrumXClient = electrumXClient;
_secureStorage = secureStorage;
_dustLimitP2PKH = dustLimitP2PKH;
_minConfirms = minConfirms;
_getMnemonic = getMnemonic;
_getChainHeight = getChainHeight;
_getCurrentChangeAddress = getCurrentChangeAddress;
@ -353,7 +358,7 @@ mixin PaynymWalletInterface {
int additionalOutputs = 0,
List<UTXO>? utxos,
}) async {
const amountToSend = DUST_LIMIT;
final amountToSend = _dustLimitP2PKH;
final List<UTXO> availableOutputs =
utxos ?? await _db.getUTXOs(_walletId).findAll();
final List<UTXO> spendableOutputs = [];
@ -362,8 +367,8 @@ mixin PaynymWalletInterface {
// Build list of spendable outputs and totaling their satoshi amount
for (var i = 0; i < availableOutputs.length; i++) {
if (availableOutputs[i].isBlocked == false &&
availableOutputs[i].isConfirmed(
await _getChainHeight(), MINIMUM_CONFIRMATIONS) ==
availableOutputs[i]
.isConfirmed(await _getChainHeight(), _minConfirms) ==
true) {
spendableOutputs.add(availableOutputs[i]);
spendableSatoshiValue += availableOutputs[i].value;
@ -440,13 +445,13 @@ mixin PaynymWalletInterface {
feeForWithChange = vSizeForWithChange * 1000;
}
if (satoshisBeingUsed - amountToSend > feeForNoChange + DUST_LIMIT) {
if (satoshisBeingUsed - amountToSend > feeForNoChange + _dustLimitP2PKH) {
// try to add change output due to "left over" amount being greater than
// the estimated fee + the dust limit
int changeAmount = satoshisBeingUsed - amountToSend - feeForWithChange;
// check estimates are correct and build notification tx
if (changeAmount >= DUST_LIMIT &&
if (changeAmount >= _dustLimitP2PKH &&
satoshisBeingUsed - amountToSend - changeAmount == feeForWithChange) {
final txn = await _createNotificationTx(
targetPaymentCodeString: targetPaymentCodeString,
@ -572,7 +577,8 @@ mixin PaynymWalletInterface {
);
// todo: modify address once segwit support is in our bip47
txb.addOutput(targetPaymentCode.notificationAddressP2PKH(), DUST_LIMIT);
txb.addOutput(
targetPaymentCode.notificationAddressP2PKH(), _dustLimitP2PKH);
txb.addOutput(opReturnScript, 0);
// TODO: add possible change output and mark output as dangerous

View file

@ -6,7 +6,6 @@ import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -121,7 +120,8 @@ class _TransactionCardState extends ConsumerState<TransactionCard> {
}
}
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
final currentHeight = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).currentHeight));
return Material(
color: Theme.of(context).extension<StackColors>()!.popupBG,

View file

@ -80,6 +80,8 @@ void main() {
when(wallets.getManager("wallet-id"))
.thenAnswer((realInvocation) => Manager(wallet));
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
//
await tester.pumpWidget(
ProviderScope(
@ -173,6 +175,7 @@ void main() {
.thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00));
when(wallet.coin).thenAnswer((_) => Coin.firo);
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
when(wallets.getManager("wallet-id"))
.thenAnswer((realInvocation) => Manager(wallet));
@ -271,6 +274,8 @@ void main() {
when(wallets.getManager("wallet-id"))
.thenAnswer((realInvocation) => Manager(wallet));
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
await tester.pumpWidget(
ProviderScope(
overrides: [
@ -358,6 +363,8 @@ void main() {
when(wallets.getManager("wallet id"))
.thenAnswer((realInvocation) => Manager(wallet));
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
mockingjay
.when(() => navigator.pushNamed("/transactionDetails",
arguments: Tuple3(tx, Coin.firo, "wallet id")))
@ -395,6 +402,7 @@ void main() {
verify(mockPrefs.currency).called(2);
verify(mockLocaleService.locale).called(4);
verify(wallet.coin.ticker).called(2);
verify(wallet.storedChainHeight).called(2);
verifyNoMoreInteractions(wallet);
verifyNoMoreInteractions(mockLocaleService);