diff --git a/assets/images/unclaimed.png b/assets/images/unclaimed.png new file mode 100644 index 000000000..f99d4ab2a Binary files /dev/null and b/assets/images/unclaimed.png differ diff --git a/assets/svg/robot-head.svg b/assets/svg/robot-head.svg new file mode 100644 index 000000000..b6810c227 --- /dev/null +++ b/assets/svg/robot-head.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/whirlpool.svg b/assets/svg/whirlpool.svg new file mode 100644 index 000000000..cf075308f --- /dev/null +++ b/assets/svg/whirlpool.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/pages/paynym/paynym_claim_view.dart b/lib/pages/paynym/paynym_claim_view.dart index 23f9d868e..326983a78 100644 --- a/lib/pages/paynym/paynym_claim_view.dart +++ b/lib/pages/paynym/paynym_claim_view.dart @@ -117,7 +117,7 @@ class _PaynymClaimViewState extends ConsumerState { ), Image( image: AssetImage( - Assets.png.stack, + Assets.png.unclaimedPaynym, ), width: MediaQuery.of(context).size.width / 2, ), diff --git a/lib/pages/wallet_view/sub_widgets/transactions_list.dart b/lib/pages/wallet_view/sub_widgets/transactions_list.dart index 360b3028b..0c48e69b6 100644 --- a/lib/pages/wallet_view/sub_widgets/transactions_list.dart +++ b/lib/pages/wallet_view/sub_widgets/transactions_list.dart @@ -20,6 +20,8 @@ import 'package:stackwallet/widgets/trade_card.dart'; import 'package:stackwallet/widgets/transaction_card.dart'; import 'package:tuple/tuple.dart'; +import '../../../utilities/enums/coin_enum.dart'; + class TransactionsList extends ConsumerStatefulWidget { const TransactionsList({ Key? key, @@ -67,11 +69,18 @@ class _TransactionsListState extends ConsumerState { BuildContext context, Transaction tx, BorderRadius? radius, + Coin coin, ) { final matchingTrades = ref .read(tradesServiceProvider) .trades .where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid); + + final isConfirmed = tx.isConfirmed( + ref.watch( + widget.managerProvider.select((value) => value.currentHeight)), + coin.requiredConfirmations); + if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) { final trade = matchingTrades.first; return Container( @@ -84,13 +93,9 @@ class _TransactionsListState extends ConsumerState { children: [ TransactionCard( // this may mess with combined firo transactions - key: Key(tx.txid + - tx.type.name + - tx.address.value.toString() + - ref - .watch(widget.managerProvider - .select((value) => value.currentHeight)) - .toString()), // + key: isConfirmed + ? Key(tx.txid + tx.type.name + tx.address.value.toString()) + : UniqueKey(), // transaction: tx, walletId: widget.walletId, ), @@ -185,13 +190,9 @@ class _TransactionsListState extends ConsumerState { ), child: TransactionCard( // this may mess with combined firo transactions - key: Key(tx.txid + - tx.type.name + - tx.address.value.toString() + - ref - .watch(widget.managerProvider - .select((value) => value.currentHeight)) - .toString()), // + key: isConfirmed + ? Key(tx.txid + tx.type.name + tx.address.value.toString()) + : UniqueKey(), transaction: tx, walletId: widget.walletId, ), @@ -263,7 +264,7 @@ class _TransactionsListState extends ConsumerState { radius = _borderRadiusFirst; } final tx = _transactions2[index]; - return itemBuilder(context, tx, radius); + return itemBuilder(context, tx, radius, manager.coin); }, separatorBuilder: (context, index) { return Container( @@ -290,7 +291,7 @@ class _TransactionsListState extends ConsumerState { radius = _borderRadiusFirst; } final tx = _transactions2[index]; - return itemBuilder(context, tx, radius); + return itemBuilder(context, tx, radius, manager.coin); }, ), ); diff --git a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart index 4e6be9e97..379362dbc 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart @@ -177,6 +177,14 @@ class _WalletNavigationBarState extends ConsumerState { "Paynym", style: STextStyles.w600_12(context), ), + const SizedBox( + width: 16, + ), + SvgPicture.asset( + Assets.svg.robotHead, + height: 20, + width: 20, + ), ], ), ), diff --git a/lib/services/buy/simplex/simplex_api.dart b/lib/services/buy/simplex/simplex_api.dart index 85b4d5d9e..37417fcbf 100644 --- a/lib/services/buy/simplex/simplex_api.dart +++ b/lib/services/buy/simplex/simplex_api.dart @@ -213,7 +213,7 @@ class SimplexAPI { // final Map lol = // Map.from(jsonArray as Map); - String? cryptoAmount = jsonArray['digital_money']?['amount'] as String?; + double? cryptoAmount = jsonArray['digital_money']?['amount'] as double?; if (cryptoAmount == null) { String error = jsonArray['error'] as String; diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index 32c24686b..ad8e9f5da 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -218,6 +218,19 @@ class BitcoinWallet extends CoinServiceAPI .findFirst()) ?? await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); + Future get currentChangeAddressP2PKH async => + (await _currentChangeAddressP2PKH).value; + + Future get _currentChangeAddressP2PKH async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathType.bip44); + @override Future exit() async { _hasCalledExit = true; @@ -1313,13 +1326,14 @@ class BitcoinWallet extends CoinServiceAPI secureStorage: secureStore, getMnemonic: () => mnemonic, getChainHeight: () => chainHeight, - getCurrentChangeAddress: () => currentChangeAddress, + getCurrentChangeAddress: () => currentChangeAddressP2PKH, estimateTxFee: estimateTxFee, prepareSend: prepareSend, getTxCount: getTxCount, fetchBuildTxData: fetchBuildTxData, refresh: refresh, - checkChangeAddressForTransactions: _checkChangeAddressForTransactions, + checkChangeAddressForTransactions: + _checkP2PKHChangeAddressForTransactions, addDerivation: addDerivation, addDerivations: addDerivations, dustLimitP2PKH: DUST_LIMIT_P2PKH, @@ -1966,6 +1980,50 @@ class BitcoinWallet extends CoinServiceAPI } } + Future _checkP2PKHChangeAddressForTransactions() async { + try { + final currentChange = await _currentChangeAddressP2PKH; + final int txCount = await getTxCount(address: currentChange.value); + Logging.instance.log( + 'Number of txs for current change address $currentChange: $txCount', + level: LogLevel.Info); + + if (txCount >= 1 || currentChange.derivationIndex < 0) { + // First increment the change index + final newChangeIndex = currentChange.derivationIndex + 1; + + // Use new index to derive a new change address + final newChangeAddress = await _generateAddressForChain( + 1, newChangeIndex, DerivePathType.bip44); + + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + // keep checking until address with no tx history is set as current + await _checkP2PKHChangeAddressForTransactions(); + } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $se\n$s", + level: LogLevel.Error); + return; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + Future _checkCurrentReceivingAddressesForTransactions() async { try { // for (final type in DerivePathType.values) { diff --git a/lib/services/mixins/paynym_wallet_interface.dart b/lib/services/mixins/paynym_wallet_interface.dart index 2292f3ec5..a8354b75f 100644 --- a/lib/services/mixins/paynym_wallet_interface.dart +++ b/lib/services/mixins/paynym_wallet_interface.dart @@ -438,11 +438,13 @@ mixin PaynymWalletInterface { feeRatePerKB: selectedTxFeeRate, ); - if (feeForNoChange < vSizeForNoChange * 1000) { - feeForNoChange = vSizeForNoChange * 1000; - } - if (feeForWithChange < vSizeForWithChange * 1000) { - feeForWithChange = vSizeForWithChange * 1000; + if (_coin == Coin.dogecoin || _coin == Coin.dogecoinTestNet) { + if (feeForNoChange < vSizeForNoChange * 1000) { + feeForNoChange = vSizeForNoChange * 1000; + } + if (feeForWithChange < vSizeForWithChange * 1000) { + feeForWithChange = vSizeForWithChange * 1000; + } } if (satoshisBeingUsed - amountToSend > feeForNoChange + _dustLimitP2PKH) { @@ -453,7 +455,7 @@ mixin PaynymWalletInterface { // check estimates are correct and build notification tx if (changeAmount >= _dustLimitP2PKH && satoshisBeingUsed - amountToSend - changeAmount == feeForWithChange) { - final txn = await _createNotificationTx( + var txn = await _createNotificationTx( targetPaymentCodeString: targetPaymentCodeString, utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, @@ -462,6 +464,18 @@ mixin PaynymWalletInterface { int feeBeingPaid = satoshisBeingUsed - amountToSend - changeAmount; + // make sure minimum fee is accurate if that is being used + if (txn.item2 - feeBeingPaid == 1) { + changeAmount -= 1; + feeBeingPaid += 1; + txn = await _createNotificationTx( + targetPaymentCodeString: targetPaymentCodeString, + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + change: changeAmount, + ); + } + Map transactionObject = { "hex": txn.item1, "recipientPaynym": targetPaymentCodeString, @@ -574,6 +588,8 @@ mixin PaynymWalletInterface { txb.addInput( utxo.txid, txPointIndex, + null, + utxoSigningData[utxo.txid]["output"] as Uint8List, ); // todo: modify address once segwit support is in our bip47 @@ -592,15 +608,18 @@ mixin PaynymWalletInterface { txb.sign( vin: 0, keyPair: myKeyPair, + witnessValue: utxo.value, + witnessScript: utxoSigningData[utxo.txid]["redeemScript"] as Uint8List?, ); // sign rest of possible inputs - for (var i = 1; i < utxosToUse.length - 1; i++) { + for (var i = 1; i < utxosToUse.length; i++) { final txid = utxosToUse[i].txid; txb.sign( vin: i, keyPair: utxoSigningData[txid]["keyPair"] as btc_dart.ECPair, - // witnessValue: utxosToUse[i].value, + witnessValue: utxosToUse[i].value, + witnessScript: utxoSigningData[utxo.txid]["redeemScript"] as Uint8List?, ); } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 5a0427782..5bb7c3edb 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -194,6 +194,8 @@ class _SVG { String get exitDesktop => "assets/svg/exit-desktop.svg"; String get keys => "assets/svg/keys.svg"; String get arrowDown => "assets/svg/arrow-down.svg"; + String get robotHead => "assets/svg/robot-head.svg"; + String get whirlPool => "assets/svg/whirlpool.svg"; String get ellipse1 => "assets/svg/Ellipse-43.svg"; String get ellipse2 => "assets/svg/Ellipse-42.svg"; @@ -262,6 +264,7 @@ class _PNG { const _PNG(); String get stack => "assets/images/stack.png"; + String get unclaimedPaynym => "assets/images/unclaimed.png"; String get splash => "assets/images/splash.png"; String get monero => "assets/images/monero.png"; diff --git a/pubspec.yaml b/pubspec.yaml index 5ab5c8b80..fb8b264d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -205,6 +205,7 @@ flutter: - assets/svg/circle-check.svg - assets/svg/clipboard.svg - assets/images/stack.png + - assets/images/unclaimed.png - assets/images/monero.png - assets/images/wownero.png - assets/images/firo.png @@ -309,6 +310,8 @@ flutter: - assets/svg/plus-circle.svg - assets/svg/circle-plus-filled.svg - assets/svg/configuration.svg + - assets/svg/robot-head.svg + - assets/svg/whirlpool.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg - assets/svg/coin_icons/Litecoin.svg