From 6b795b5ba3f25fc2fe3f4cb64d544e27d146c2c8 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 26 Feb 2024 15:32:54 -0300 Subject: [PATCH 001/242] feat: rebase btc-addr-types, migrate to bitcoin_base --- cw_bitcoin/lib/bitcoin_address_record.dart | 6 +- .../lib/bitcoin_receive_page_option.dart | 4 + cw_bitcoin/lib/bitcoin_unspent.dart | 28 +- cw_bitcoin/lib/bitcoin_wallet.dart | 17 +- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 4 +- cw_bitcoin/lib/electrum.dart | 6 + cw_bitcoin/lib/electrum_transaction_info.dart | 76 +- cw_bitcoin/lib/electrum_wallet.dart | 719 +++++++++++++++++- cw_bitcoin/lib/electrum_wallet_addresses.dart | 85 ++- cw_bitcoin/lib/electrum_wallet_snapshot.dart | 23 +- cw_bitcoin/pubspec.lock | 12 +- cw_bitcoin/pubspec.yaml | 3 +- cw_bitcoin_cash/pubspec.yaml | 2 +- cw_core/lib/sync_status.dart | 14 +- cw_core/lib/unspent_transaction_output.dart | 3 +- howto-build-android.md | 20 +- lib/bitcoin/cw_bitcoin.dart | 5 + lib/core/address_validator.dart | 5 +- lib/core/sync_status_title.dart | 6 +- lib/di.dart | 5 + .../present_receive_option_picker.dart | 128 ++-- lib/src/screens/receive/receive_page.dart | 32 +- lib/src/screens/send/widgets/send_card.dart | 3 +- lib/src/widgets/address_text_field.dart | 6 +- .../dashboard/dashboard_view_model.dart | 5 + lib/view_model/rescan_view_model.dart | 11 +- .../wallet_address_list_view_model.dart | 23 +- model_generator.sh | 19 +- tool/configure.dart | 1 + 29 files changed, 1047 insertions(+), 224 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index d8d9082305..2c40ba34cd 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -16,6 +16,7 @@ class BitcoinAddressRecord { required this.type, String? scriptHash, required this.network, + this.silentPaymentTweak, }) : _txCount = txCount, _balance = balance, _name = name, @@ -23,7 +24,7 @@ class BitcoinAddressRecord { scriptHash = scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null); - factory BitcoinAddressRecord.fromJSON(String jsonSource, BasedUtxoNetwork? network) { + factory BitcoinAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) { final decoded = json.decode(jsonSource) as Map; return BitcoinAddressRecord( @@ -42,6 +43,7 @@ class BitcoinAddressRecord { network: (decoded['network'] as String?) == null ? network : BasedUtxoNetwork.fromName(decoded['network'] as String), + silentPaymentTweak: decoded['silentPaymentTweak'] as String?, ); } @@ -57,6 +59,7 @@ class BitcoinAddressRecord { bool _isUsed; String? scriptHash; BasedUtxoNetwork? network; + final String? silentPaymentTweak; int get txCount => _txCount; @@ -96,5 +99,6 @@ class BitcoinAddressRecord { 'type': type.toString(), 'scriptHash': scriptHash, 'network': network?.value, + 'silentPaymentTweak': silentPaymentTweak, }); } diff --git a/cw_bitcoin/lib/bitcoin_receive_page_option.dart b/cw_bitcoin/lib/bitcoin_receive_page_option.dart index 2e246f532a..2b025965bd 100644 --- a/cw_bitcoin/lib/bitcoin_receive_page_option.dart +++ b/cw_bitcoin/lib/bitcoin_receive_page_option.dart @@ -8,6 +8,8 @@ class BitcoinReceivePageOption implements ReceivePageOption { static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)'); static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)'); + static const silent_payments = BitcoinReceivePageOption._('Silent Payments'); + const BitcoinReceivePageOption._(this.value); final String value; @@ -34,6 +36,8 @@ class BitcoinReceivePageOption implements ReceivePageOption { return BitcoinReceivePageOption.p2pkh; case P2shAddressType.p2wpkhInP2sh: return BitcoinReceivePageOption.p2sh; + case SilentPaymentsAddresType.p2sp: + return BitcoinReceivePageOption.silent_payments; case SegwitAddresType.p2wpkh: default: return BitcoinReceivePageOption.p2wpkh; diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index 52edea0913..131f47ab65 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -1,14 +1,38 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_core/unspent_transaction_output.dart'; class BitcoinUnspent extends Unspent { - BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout) + BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout, + {this.silentPaymentTweak, this.type}) : bitcoinAddressRecord = addressRecord, super(addressRecord.address, hash, value, vout, null); factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map json) => BitcoinUnspent( - address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int); + address, + json['tx_hash'] as String, + json['value'] as int, + json['tx_pos'] as int, + silentPaymentTweak: json['silent_payment_tweak'] as String?, + type: json['type'] == null + ? null + : BitcoinAddressType.values.firstWhere((e) => e.toString() == json['type']), + ); + + Map toJson() { + final json = { + 'address_record': bitcoinAddressRecord.toJSON(), + 'tx_hash': hash, + 'value': value, + 'tx_pos': vout, + 'silent_payment_tweak': silentPaymentTweak, + 'type': type.toString(), + }; + return json; + } final BitcoinAddressRecord bitcoinAddressRecord; + String? silentPaymentTweak; + BitcoinAddressType? type; } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 3b3e9c6363..a018abbd6a 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -30,6 +30,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { ElectrumBalance? initialBalance, Map? initialRegularAddressIndex, Map? initialChangeAddressIndex, + List? initialSilentAddresses, + int initialSilentAddressIndex = 0, + SilentPaymentOwner? silentAddress, }) : super( mnemonic: mnemonic, password: password, @@ -46,10 +49,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { currency: CryptoCurrency.btc) { walletAddresses = BitcoinWalletAddresses( walletInfo, - electrumClient: electrumClient, initialAddresses: initialAddresses, initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, + initialSilentAddresses: initialSilentAddresses, + initialSilentAddressIndex: initialSilentAddressIndex, + silentAddress: silentAddress, mainHd: hd, sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"), network: networkParam ?? network, @@ -67,9 +72,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { String? addressPageType, BasedUtxoNetwork? network, List? initialAddresses, + List? initialSilentAddresses, ElectrumBalance? initialBalance, Map? initialRegularAddressIndex, Map? initialChangeAddressIndex, + int initialSilentAddressIndex = 0, }) async { return BitcoinWallet( mnemonic: mnemonic, @@ -77,6 +84,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, + initialSilentAddresses: initialSilentAddresses, + initialSilentAddressIndex: initialSilentAddressIndex, + silentAddress: await SilentPaymentOwner.fromMnemonic(mnemonic, + hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'), initialBalance: initialBalance, seedBytes: await mnemonicToSeedBytes(mnemonic), initialRegularAddressIndex: initialRegularAddressIndex, @@ -101,6 +112,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp.addresses, + initialSilentAddresses: snp.silentAddresses, + initialSilentAddressIndex: snp.silentAddressIndex, + silentAddress: await SilentPaymentOwner.fromMnemonic(snp.mnemonic, + hrp: snp.network == BitcoinNetwork.testnet ? 'tsp' : 'sp'), initialBalance: snp.balance, seedBytes: await mnemonicToSeedBytes(snp.mnemonic), initialRegularAddressIndex: snp.regularAddressIndex, diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index f125774929..48960ce3d1 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -15,10 +15,12 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S required super.mainHd, required super.sideHd, required super.network, - required super.electrumClient, super.initialAddresses, super.initialRegularAddressIndex, super.initialChangeAddressIndex, + super.initialSilentAddresses, + super.initialSilentAddressIndex = 0, + super.silentAddress, }) : super(walletInfo); @override diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 51a53e285b..59c864eb2e 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -349,6 +349,12 @@ class ElectrumClient { return null; }); + BehaviorSubject? chainTipUpdate() { + _id += 1; + return subscribe( + id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe'); + } + BehaviorSubject? scripthashUpdate(String scripthash) { _id += 1; return subscribe( diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index cfea0e089a..5a7f797f96 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -1,9 +1,8 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; +import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/format_amount.dart'; @@ -20,6 +19,8 @@ class ElectrumTransactionBundle { } class ElectrumTransactionInfo extends TransactionInfo { + BitcoinUnspent? unspent; + ElectrumTransactionInfo(this.type, {required String id, required int height, @@ -28,7 +29,9 @@ class ElectrumTransactionInfo extends TransactionInfo { required TransactionDirection direction, required bool isPending, required DateTime date, - required int confirmations}) { + required int confirmations, + String? to, + this.unspent}) { this.id = id; this.height = height; this.amount = amount; @@ -37,6 +40,7 @@ class ElectrumTransactionInfo extends TransactionInfo { this.date = date; this.isPending = isPending; this.confirmations = confirmations; + this.to = to; } factory ElectrumTransactionInfo.fromElectrumVerbose(Map obj, WalletType type, @@ -144,50 +148,24 @@ class ElectrumTransactionInfo extends TransactionInfo { confirmations: bundle.confirmations); } - factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex, - {List? addresses, required int height, int? timestamp, required int confirmations}) { - final tx = bitcoin.Transaction.fromHex(hex); - var exist = false; - var amount = 0; - - if (addresses != null) { - tx.outs.forEach((out) { - try { - final p2pkh = - bitcoin.P2PKH(data: PaymentData(output: out.script), network: bitcoin.bitcoin); - exist = addresses.contains(p2pkh.data.address); - - if (exist) { - amount += out.value!; - } - } catch (_) {} - }); - } - - final date = - timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) : DateTime.now(); - - return ElectrumTransactionInfo(type, - id: tx.getId(), - height: height, - isPending: false, - fee: null, - direction: TransactionDirection.incoming, - amount: amount, - date: date, - confirmations: confirmations); - } - factory ElectrumTransactionInfo.fromJson(Map data, WalletType type) { - return ElectrumTransactionInfo(type, - id: data['id'] as String, - height: data['height'] as int, - amount: data['amount'] as int, - fee: data['fee'] as int, - direction: parseTransactionDirectionFromInt(data['direction'] as int), - date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), - isPending: data['isPending'] as bool, - confirmations: data['confirmations'] as int); + return ElectrumTransactionInfo( + type, + id: data['id'] as String, + height: data['height'] as int, + amount: data['amount'] as int, + fee: data['fee'] as int, + direction: parseTransactionDirectionFromInt(data['direction'] as int), + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), + isPending: data['isPending'] as bool, + confirmations: data['confirmations'] as int, + to: data['to'] as String?, + unspent: data['unspent'] != null + ? BitcoinUnspent.fromJSON( + BitcoinAddressRecord.fromJSON(data['unspent']['address_record'] as String), + data['unspent'] as Map) + : null, + ); } final WalletType type; @@ -231,6 +209,12 @@ class ElectrumTransactionInfo extends TransactionInfo { m['isPending'] = isPending; m['confirmations'] = confirmations; m['fee'] = fee; + m['to'] = to; + m['unspent'] = unspent?.toJson() ?? {}; return m; } + + String toString() { + return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, confirmations: $confirmations, to: $to, unspent: $unspent)'; + } } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 873fe2977a..4bef9b7489 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:isolate'; import 'dart:math'; import 'package:bitcoin_base/bitcoin_base.dart'; @@ -143,21 +144,93 @@ abstract class ElectrumWalletBase List unspentCoins; List _feeRates; Map?> _scripthashesUpdateSubject; + BehaviorSubject? _chainTipUpdateSubject; bool _isTransactionUpdating; + // Future? _isolate; void Function(FlutterErrorDetails)? _onError; + Timer? _autoSaveTimer; + static const int _autoSaveInterval = 30; Future init() async { await walletAddresses.init(); await transactionHistory.init(); - await save(); + + _autoSaveTimer = + Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); } + // @action + // Future _setListeners(int height, {int? chainTip}) async { + // final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; + // syncStatus = AttemptingSyncStatus(); + + // if (_isolate != null) { + // final runningIsolate = await _isolate!; + // runningIsolate.kill(priority: Isolate.immediate); + // } + + // final receivePort = ReceivePort(); + // _isolate = Isolate.spawn( + // startRefresh, + // ScanData( + // sendPort: receivePort.sendPort, + // primarySilentAddress: walletAddresses.primarySilentAddress!, + // networkType: networkType, + // height: height, + // chainTip: currentChainTip, + // electrumClient: ElectrumClient(), + // transactionHistoryIds: transactionHistory.transactions.keys.toList(), + // node: electrumClient.uri.toString(), + // labels: walletAddresses.labels, + // )); + + // await for (var message in receivePort) { + // if (message is BitcoinUnspent) { + // if (!unspentCoins.any((utx) => + // utx.hash.contains(message.hash) && + // utx.vout == message.vout && + // utx.address.contains(message.address))) { + // unspentCoins.add(message); + + // if (unspentCoinsInfo.values.any((element) => + // element.walletId.contains(id) && + // element.hash.contains(message.hash) && + // element.address.contains(message.address))) { + // _addCoinInfo(message); + + // await walletInfo.save(); + // await save(); + // } + + // balance[currency] = await _fetchBalances(); + // } + // } + + // if (message is Map) { + // transactionHistory.addMany(message); + // await transactionHistory.save(); + // } + + // // check if is a SyncStatus type since "is SyncStatus" doesn't work here + // if (message is SyncResponse) { + // syncStatus = message.syncStatus; + // walletInfo.restoreHeight = message.height; + // await walletInfo.save(); + // } + // } + // } + @action @override Future startSync() async { try { - syncStatus = AttemptingSyncStatus(); + await _setInitialHeight(); + } catch (_) {} + + try { + rescan(height: walletInfo.restoreHeight); + await updateTransactions(); _subscribeForUpdates(); await updateUnspent(); @@ -187,6 +260,12 @@ abstract class ElectrumWalletBase } }; syncStatus = ConnectedSyncStatus(); + + // final currentChainTip = await electrumClient.getCurrentBlockChainTip(); + + // if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { + // _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); + // } } catch (e) { print(e.toString()); syncStatus = FailedSyncStatus(); @@ -213,6 +292,124 @@ abstract class ElectrumWalletBase allInputsAmount += utx.value; leftAmount = leftAmount - utx.value; + if (utx.bitcoinAddressRecord.silentPaymentTweak != null) { + // final d = ECPrivate.fromHex(walletAddresses.primarySilentAddress!.spendPrivkey.toHex()) + // .tweakAdd(utx.bitcoinAddressRecord.silentPaymentTweak!)!; + + // inputPrivKeys.add(bitcoin.PrivateKeyInfo(d, true)); + // address = bitcoin.P2trAddress(address: utx.address, networkType: networkType); + // keyPairs.add(bitcoin.ECPair.fromPrivateKey(d.toCompressedHex().fromHex, + // compressed: true, network: networkType)); + // scriptType = bitcoin.AddressType.p2tr; + // script = bitcoin.P2trAddress(pubkey: d.publicKey.toHex(), networkType: networkType) + // .scriptPubkey + // .toBytes(); + } + + final address = _addressTypeFromStr(utx.address, network); + final privkey = generateECPrivate( + hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + index: utx.bitcoinAddressRecord.index, + network: network); + + privateKeys.add(privkey); + + utxos.add( + UtxoWithAddress( + utxo: BitcoinUtxo( + txHash: utx.hash, + value: BigInt.from(utx.value), + vout: utx.vout, + scriptType: _getScriptType(address), + ), + ownerDetails: + UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: address), + ), + ); + + bool amountIsAcquired = !sendAll && leftAmount <= 0; + if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) { + break; + } + } + } + + if (inputs.isEmpty) { + throw BitcoinTransactionNoInputsException(); + } + + final allAmountFee = transactionCredentials.feeRate != null + ? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length) + : feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length); + + final allAmount = allInputsAmount - allAmountFee; + + var credentialsAmount = 0; + var amount = 0; + var fee = 0; + + if (hasMultiDestination) { + if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!); + + if (allAmount - credentialsAmount < minAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + amount = credentialsAmount; + + if (transactionCredentials.feeRate != null) { + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount, + outputsCount: outputs.length + 1); + } else { + fee = calculateEstimatedFee(transactionCredentials.priority, amount, + outputsCount: outputs.length + 1); + } + } else { + final output = outputs.first; + credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0; + + if (credentialsAmount > allAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + amount = output.sendAll || allAmount - credentialsAmount < minAmount + ? allAmount + : credentialsAmount; + + if (output.sendAll || amount == allAmount) { + fee = allAmountFee; + } else if (transactionCredentials.feeRate != null) { + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount); + } else { + fee = calculateEstimatedFee(transactionCredentials.priority, amount); + } + } + + if (fee == 0) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + final totalAmount = amount + fee; + + if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + final txb = bitcoin.TransactionBuilder(network: networkType); + final changeAddress = await walletAddresses.getChangeAddress(); + var leftAmount = totalAmount; + var totalInputAmount = 0; + + inputs.clear(); + + for (final utx in unspentCoins) { + if (utx.isSending) { + leftAmount = leftAmount - utx.value; + final address = _addressTypeFromStr(utx.address, network); final privkey = generateECPrivate( hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, @@ -304,23 +501,22 @@ abstract class ElectrumWalletBase } } - return EstimatedTxResult(utxos: utxos, privateKeys: privateKeys, fee: fee, amount: amount); - } - - @override - Future createTransaction(Object credentials) async { - try { - final outputs = []; - final outputAddresses = []; - final transactionCredentials = credentials as BitcoinTransactionCredentials; - final hasMultiDestination = transactionCredentials.outputs.length > 1; - final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll; - - var credentialsAmount = 0; - - for (final out in transactionCredentials.outputs) { - final outputAddress = out.isParsedAddress ? out.extractedAddress! : out.address; - final address = _addressTypeFromStr(outputAddress, network); + if (SilentPaymentAddress.regex.hasMatch(outputAddress)) { + // final outpointsHash = SilentPayment.hashOutpoints(outpoints); + // final generatedOutputs = SilentPayment.generateMultipleRecipientPubkeys(inputPrivKeys, + // outpointsHash, SilentPaymentDestination.fromAddress(outputAddress, outputAmount!)); + + // generatedOutputs.forEach((recipientSilentAddress, generatedOutput) { + // generatedOutput.forEach((output) { + // outputs.add(BitcoinOutputDetails( + // address: P2trAddress( + // program: ECPublic.fromHex(output.$1.toHex()).toTapPoint(), + // networkType: networkType), + // value: BigInt.from(output.$2), + // )); + // }); + // }); + } outputAddresses.add(address); @@ -392,6 +588,8 @@ abstract class ElectrumWalletBase ? SegwitAddresType.p2wpkh.toString() : walletInfo.addressPageType.toString(), 'balance': balance[currency]?.toJSON(), + 'silent_addresses': walletAddresses.silentAddresses.map((addr) => addr.toJSON()).toList(), + 'silent_address_index': walletAddresses.currentSilentAddressIndex.toString(), 'network_type': network == BitcoinNetwork.testnet ? 'testnet' : 'mainnet', }); @@ -498,18 +696,31 @@ abstract class ElectrumWalletBase } @override - Future rescan({required int height}) async => throw UnimplementedError(); + Future rescan({required int height, int? chainTip, ScanData? scanData}) async { + // _setListeners(height); + } @override Future close() async { try { await electrumClient.close(); } catch (_) {} + _autoSaveTimer?.cancel(); } Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); Future updateUnspent() async { + // Update unspents stored from scanned silent payment transactions + transactionHistory.transactions.values.forEach((tx) { + if (tx.unspent != null) { + if (!unspentCoins + .any((utx) => utx.hash.contains(tx.unspent!.hash) && utx.vout == tx.unspent!.vout)) { + unspentCoins.add(tx.unspent!); + } + } + }); + List updatedUnspentCoins = []; final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); @@ -538,7 +749,7 @@ abstract class ElectrumWalletBase final coinInfoList = unspentCoinsInfo.values.where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash) && - element.vout == coin.vout); + element.address.contains(coin.address)); if (coinInfoList.isNotEmpty) { final coinInfo = coinInfoList.first; @@ -555,6 +766,7 @@ abstract class ElectrumWalletBase await _refreshUnspentCoinsInfo(); } + @action Future _addCoinInfo(BitcoinUnspent coin) async { final newInfo = UnspentCoinsInfo( walletId: id, @@ -619,19 +831,17 @@ abstract class ElectrumWalletBase confirmations = verboseTransaction['confirmations'] as int? ?? 0; } - final original = bitcoin_base.BtcTransaction.fromRaw(transactionHex); - final ins = []; + final original = BtcTransaction.fromRaw(transactionHex); + final ins = []; for (final vin in original.inputs) { try { final id = HEX.encode(HEX.decode(vin.txId).reversed.toList()); final txHex = await electrumClient.getTransactionHex(hash: id); - final tx = bitcoin_base.BtcTransaction.fromRaw(txHex); + final tx = BtcTransaction.fromRaw(txHex); ins.add(tx); } catch (_) { - ins.add(bitcoin_base.BtcTransaction.fromRaw( - await electrumClient.getTransactionHex(hash: vin.txId), - )); + ins.add(BtcTransaction.fromRaw(await electrumClient.getTransactionHex(hash: vin.txId))); } } @@ -767,7 +977,7 @@ abstract class ElectrumWalletBase } } - void _subscribeForUpdates() { + void _subscribeForUpdates() async { scriptHashes.forEach((sh) async { await _scripthashesUpdateSubject[sh]?.close(); _scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh); @@ -786,6 +996,23 @@ abstract class ElectrumWalletBase } }); }); + + await _chainTipUpdateSubject?.close(); + _chainTipUpdateSubject = electrumClient.chainTipUpdate(); + _chainTipUpdateSubject?.listen((_) async { + try { + final currentHeight = await electrumClient.getCurrentBlockChainTip(); + if (currentHeight != null) walletInfo.restoreHeight = currentHeight; + // _setListeners(walletInfo.restoreHeight, chainTip: currentHeight); + } catch (e, s) { + print(e.toString()); + _onError?.call(FlutterErrorDetails( + exception: e, + stack: s, + library: this.runtimeType.toString(), + )); + } + }); } Future _fetchBalances() async { @@ -799,21 +1026,25 @@ abstract class ElectrumWalletBase } var totalFrozen = 0; + var totalConfirmed = 0; + var totalUnconfirmed = 0; + + // Add values from unspent coins that are not fetched by the address list + // i.e. scanned silent payments unspentCoinsInfo.values.forEach((info) { unspentCoins.forEach((element) { if (element.hash == info.hash && - element.vout == info.vout && - info.isFrozen && element.bitcoinAddressRecord.address == info.address && element.value == info.value) { - totalFrozen += element.value; + if (info.isFrozen) totalFrozen += element.value; + if (element.bitcoinAddressRecord.silentPaymentTweak != null) { + totalConfirmed += element.value; + } } }); }); final balances = await Future.wait(balanceFutures); - var totalConfirmed = 0; - var totalUnconfirmed = 0; for (var i = 0; i < balances.length; i++) { final addressRecord = addresses[i]; @@ -860,6 +1091,428 @@ abstract class ElectrumWalletBase final HD = index == null ? hd : hd.derive(index); return base64Encode(HD.signMessage(message)); } + + Future _setInitialHeight() async { + if (walletInfo.isRecovery) { + return; + } + + if (walletInfo.restoreHeight == 0) { + final currentHeight = await electrumClient.getCurrentBlockChainTip(); + if (currentHeight != null) walletInfo.restoreHeight = currentHeight; + } + } +} + +class ScanData { + final SendPort sendPort; + final SilentPaymentReceiver primarySilentAddress; + final int height; + final String node; + final bitcoin.NetworkType networkType; + final int chainTip; + final ElectrumClient electrumClient; + final List transactionHistoryIds; + final Map labels; + + ScanData({ + required this.sendPort, + required this.primarySilentAddress, + required this.height, + required this.node, + required this.networkType, + required this.chainTip, + required this.electrumClient, + required this.transactionHistoryIds, + required this.labels, + }); + + factory ScanData.fromHeight(ScanData scanData, int newHeight) { + return ScanData( + sendPort: scanData.sendPort, + primarySilentAddress: scanData.primarySilentAddress, + height: newHeight, + node: scanData.node, + networkType: scanData.networkType, + chainTip: scanData.chainTip, + transactionHistoryIds: scanData.transactionHistoryIds, + electrumClient: scanData.electrumClient, + labels: scanData.labels, + ); + } +} + +class SyncResponse { + final int height; + final SyncStatus syncStatus; + + SyncResponse(this.height, this.syncStatus); +} + +// Future startRefresh(ScanData scanData) async { +// var cachedBlockchainHeight = scanData.chainTip; + +// Future getNodeHeightOrUpdate(int baseHeight) async { +// if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) { +// final electrumClient = scanData.electrumClient; +// if (!electrumClient.isConnected) { +// final node = scanData.node; +// await electrumClient.connectToUri(Uri.parse(node)); +// } + +// cachedBlockchainHeight = +// await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; +// } + +// return cachedBlockchainHeight; +// } + +// var lastKnownBlockHeight = 0; +// var initialSyncHeight = 0; + +// var syncHeight = scanData.height; +// var currentChainTip = scanData.chainTip; + +// if (syncHeight <= 0) { +// syncHeight = currentChainTip; +// } + +// if (initialSyncHeight <= 0) { +// initialSyncHeight = syncHeight; +// } + +// if (lastKnownBlockHeight == syncHeight) { +// scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); +// return; +// } + +// // Run this until no more blocks left to scan txs. At first this was recursive +// // i.e. re-calling the startRefresh function but this was easier for the above values to retain +// // their initial values +// while (true) { +// lastKnownBlockHeight = syncHeight; + +// final syncingStatus = +// SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight); +// scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); + +// if (syncingStatus.blocksLeft <= 0) { +// scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); +// return; +// } + +// // print(["Scanning from height:", syncHeight]); + +// try { +// final networkPath = +// scanData.networkType.network == bitcoin.BtcNetwork.mainnet ? "" : "/testnet"; + +// // This endpoint gets up to 10 latest blocks from the given height +// final tenNewestBlocks = +// (await http.get(Uri.parse("https://blockstream.info$networkPath/api/blocks/$syncHeight"))) +// .body; +// var decodedBlocks = json.decode(tenNewestBlocks) as List; + +// decodedBlocks.sort((a, b) => (a["height"] as int).compareTo(b["height"] as int)); +// decodedBlocks = +// decodedBlocks.where((element) => (element["height"] as int) >= syncHeight).toList(); + +// // for each block, get up to 25 txs +// for (var i = 0; i < decodedBlocks.length; i++) { +// final blockJson = decodedBlocks[i]; +// final blockHash = blockJson["id"]; +// final txCount = blockJson["tx_count"] as int; + +// // print(["Scanning block index:", i, "with tx count:", txCount]); + +// int startIndex = 0; +// // go through each tx in block until no more txs are left +// while (startIndex < txCount) { +// // This endpoint gets up to 25 txs from the given block hash and start index +// final twentyFiveTxs = json.decode((await http.get(Uri.parse( +// "https://blockstream.info$networkPath/api/block/$blockHash/txs/$startIndex"))) +// .body) as List; + +// // print(["Scanning txs index:", startIndex]); + +// // For each tx, apply silent payment filtering and do shared secret calculation when applied +// for (var i = 0; i < twentyFiveTxs.length; i++) { +// try { +// final tx = twentyFiveTxs[i]; +// final txid = tx["txid"] as String; + +// // print(["Scanning tx:", txid]); + +// // TODO: if tx already scanned & stored skip +// // if (scanData.transactionHistoryIds.contains(txid)) { +// // // already scanned tx, continue to next tx +// // pos++; +// // continue; +// // } + +// List pubkeys = []; +// List outpoints = []; + +// bool skip = false; + +// for (var i = 0; i < (tx["vin"] as List).length; i++) { +// final input = tx["vin"][i]; +// final prevout = input["prevout"]; +// final scriptPubkeyType = prevout["scriptpubkey_type"]; +// String? pubkey; + +// if (scriptPubkeyType == "v0_p2wpkh" || scriptPubkeyType == "v1_p2tr") { +// final witness = input["witness"]; +// if (witness == null) { +// skip = true; +// // print("Skipping, no witness"); +// break; +// } + +// if (witness.length == 2) { +// pubkey = witness[1] as String; +// } else if (witness.length == 1) { +// pubkey = "02" + (prevout["scriptpubkey"] as String).fromHex.sublist(2).hex; +// } +// } + +// if (scriptPubkeyType == "p2pkh") { +// pubkey = bitcoin.P2pkhAddress( +// scriptSig: bitcoin.Script.fromRaw(hexData: input["scriptsig"] as String)) +// .pubkey; +// } + +// if (pubkey == null) { +// skip = true; +// // print("Skipping, invalid witness"); +// break; +// } + +// pubkeys.add(pubkey); +// outpoints.add( +// bitcoin.Outpoint(txid: input["txid"] as String, index: input["vout"] as int)); +// } + +// if (skip) { +// // skipped tx, continue to next tx +// continue; +// } + +// Map outpointsByP2TRpubkey = {}; +// for (var i = 0; i < (tx["vout"] as List).length; i++) { +// final output = tx["vout"][i]; +// if (output["scriptpubkey_type"] != "v1_p2tr") { +// // print("Skipping, not a v1_p2tr output"); +// continue; +// } + +// final script = (output["scriptpubkey"] as String).fromHex; + +// // final alreadySpentOutput = (await electrumClient.getHistory( +// // scriptHashFromScript(script, networkType: scanData.networkType))) +// // .length > +// // 1; + +// // if (alreadySpentOutput) { +// // print("Skipping, invalid witness"); +// // break; +// // } + +// final p2tr = bitcoin.P2trAddress( +// program: script.sublist(2).hex, networkType: scanData.networkType); +// final address = p2tr.address; + +// print(["Verifying taproot address:", address]); + +// outpointsByP2TRpubkey[script.sublist(2).hex] = +// bitcoin.Outpoint(txid: txid, index: i, value: output["value"] as int); +// } + +// if (pubkeys.isEmpty || outpoints.isEmpty || outpointsByP2TRpubkey.isEmpty) { +// // skipped tx, continue to next tx +// continue; +// } + +// final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints); + +// final result = bitcoin.scanOutputs( +// scanData.primarySilentAddress.scanPrivkey, +// scanData.primarySilentAddress.spendPubkey, +// bitcoin.getSumInputPubKeys(pubkeys), +// outpointHash, +// outpointsByP2TRpubkey.keys.map((e) => e.fromHex).toList(), +// labels: scanData.labels, +// ); + +// if (result.isEmpty) { +// // no results tx, continue to next tx +// continue; +// } + +// if (result.length > 1) { +// print("MULTIPLE UNSPENT COINS FOUND!"); +// } else { +// print("UNSPENT COIN FOUND!"); +// } + +// result.forEach((key, value) async { +// final outpoint = outpointsByP2TRpubkey[key]; + +// if (outpoint == null) { +// return; +// } + +// final tweak = value[0]; +// String? label; +// if (value.length > 1) label = value[1]; + +// final txInfo = ElectrumTransactionInfo( +// WalletType.bitcoin, +// id: txid, +// height: syncHeight, +// amount: outpoint.value!, +// fee: 0, +// direction: TransactionDirection.incoming, +// isPending: false, +// date: DateTime.fromMillisecondsSinceEpoch((blockJson["timestamp"] as int) * 1000), +// confirmations: currentChainTip - syncHeight, +// to: bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress( +// scanData.primarySilentAddress.scanPubkey, +// scanData.primarySilentAddress.spendPubkey, +// label != null ? label.fromHex : "0".fromHex, +// hrp: scanData.primarySilentAddress.hrp, +// version: scanData.primarySilentAddress.version) +// .toString(), +// unspent: null, +// ); + +// final status = json.decode((await http +// .get(Uri.parse("https://blockstream.info/testnet/api/tx/$txid/outspends"))) +// .body) as List; + +// bool spent = false; +// for (final s in status) { +// if ((s["spent"] as bool) == true) { +// spent = true; + +// scanData.sendPort.send({txid: txInfo}); + +// final sentTxId = s["txid"] as String; +// final sentTx = json.decode((await http +// .get(Uri.parse("https://blockstream.info/testnet/api/tx/$sentTxId"))) +// .body); + +// int amount = 0; +// for (final out in (sentTx["vout"] as List)) { +// amount += out["value"] as int; +// } + +// final height = s["status"]["block_height"] as int; + +// scanData.sendPort.send({ +// sentTxId: ElectrumTransactionInfo( +// WalletType.bitcoin, +// id: sentTxId, +// height: height, +// amount: amount, +// fee: 0, +// direction: TransactionDirection.outgoing, +// isPending: false, +// date: DateTime.fromMillisecondsSinceEpoch( +// (s["status"]["block_time"] as int) * 1000), +// confirmations: currentChainTip - height, +// ) +// }); +// } +// } + +// if (spent) { +// return; +// } + +// final unspent = BitcoinUnspent( +// BitcoinAddressRecord( +// bitcoin.P2trAddress(program: key, networkType: scanData.networkType).address, +// index: 0, +// isHidden: true, +// isUsed: true, +// silentAddressLabel: null, +// silentPaymentTweak: tweak, +// type: bitcoin.AddressType.p2tr, +// ), +// txid, +// outpoint.value!, +// outpoint.index, +// silentPaymentTweak: tweak, +// type: bitcoin.AddressType.p2tr, +// ); + +// // found utxo for tx, send unspent coin to main isolate +// scanData.sendPort.send(unspent); + +// // also send tx data for tx history +// txInfo.unspent = unspent; +// scanData.sendPort.send({txid: txInfo}); +// }); +// } catch (_) {} +// } + +// // Finished scanning batch of txs in block, add 25 to start index and continue to next block in loop +// startIndex += 25; +// } + +// // Finished scanning block, add 1 to height and continue to next block in loop +// syncHeight += 1; +// currentChainTip = await getNodeHeightOrUpdate(syncHeight); +// scanData.sendPort.send(SyncResponse(syncHeight, +// SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); +// } +// } catch (e, stacktrace) { +// print(stacktrace); +// print(e.toString()); + +// scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus())); +// break; +// } +// } +// } + +class EstimatedTxResult { + EstimatedTxResult( + {required this.utxos, required this.privateKeys, required this.fee, required this.amount}); + + final List utxos; + final List privateKeys; + final int fee; + final int amount; +} + +BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network) { + if (P2pkhAddress.regex.hasMatch(address)) { + return P2pkhAddress.fromAddress(address: address, network: network); + } else if (P2shAddress.regex.hasMatch(address)) { + return P2shAddress.fromAddress(address: address, network: network); + } else if (P2wshAddress.regex.hasMatch(address)) { + return P2wshAddress.fromAddress(address: address, network: network); + } else if (P2trAddress.regex.hasMatch(address)) { + return P2trAddress.fromAddress(address: address, network: network); + } else { + return P2wpkhAddress.fromAddress(address: address, network: network); + } +} + +BitcoinAddressType _getScriptType(BitcoinBaseAddress type) { + if (type is P2pkhAddress) { + return P2pkhAddressType.p2pkh; + } else if (type is P2shAddress) { + return P2shAddressType.p2wpkhInP2sh; + } else if (type is P2wshAddress) { + return SegwitAddresType.p2wsh; + } else if (type is P2trAddress) { + return SegwitAddresType.p2tr; + } else { + return SegwitAddresType.p2wpkh; + } } class EstimateTxParams { diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 5880f5a19b..23482e4d70 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -2,7 +2,6 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitbox/bitbox.dart' as bitbox; import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -25,12 +24,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { WalletInfo walletInfo, { required this.mainHd, required this.sideHd, - required this.electrumClient, required this.network, List? initialAddresses, Map? initialRegularAddressIndex, Map? initialChangeAddressIndex, + List? initialSilentAddresses, + int initialSilentAddressIndex = 0, + SilentPaymentOwner? silentAddress, }) : _addresses = ObservableList.of((initialAddresses ?? []).toSet()), + primarySilentAddress = silentAddress, addressesByReceiveType = ObservableList.of(([]).toSet()), receiveAddresses = ObservableList.of((initialAddresses ?? []) @@ -44,6 +46,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { _addressPageType = walletInfo.addressPageType != null ? BitcoinAddressType.fromValue(walletInfo.addressPageType!) : SegwitAddresType.p2wpkh, + silentAddresses = ObservableList.of((initialSilentAddresses ?? []) + .where((addressRecord) => addressRecord.silentPaymentTweak != null) + .toSet()), + currentSilentAddressIndex = initialSilentAddressIndex, super(walletInfo) { updateAddressesByMatch(); } @@ -61,27 +67,57 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { late ObservableList addressesByReceiveType; final ObservableList receiveAddresses; final ObservableList changeAddresses; - final ElectrumClient electrumClient; + final ObservableList silentAddresses; final BasedUtxoNetwork network; final bitcoin.HDWallet mainHd; final bitcoin.HDWallet sideHd; + final SilentPaymentOwner? primarySilentAddress; + @observable BitcoinAddressType _addressPageType = SegwitAddresType.p2wpkh; @computed BitcoinAddressType get addressPageType => _addressPageType; + @observable + String? activeSilentAddress; + @computed List get allAddresses => _addresses; @override @computed String get address { + if (addressPageType == SilentPaymentsAddresType.p2sp) { + if (activeSilentAddress != null) { + return activeSilentAddress!; + } + + return primarySilentAddress!.toString(); + } + String receiveAddress; final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch); + if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) || + typeMatchingReceiveAddresses.isEmpty) { + receiveAddress = generateNewAddress().address; + } else { + final previousAddressMatchesType = + previousAddressRecord != null && previousAddressRecord!.type == addressPageType; + + if (previousAddressMatchesType && + typeMatchingReceiveAddresses.first.address != addressesByReceiveType.first.address) { + receiveAddress = previousAddressRecord!.address; + } else { + receiveAddress = typeMatchingReceiveAddresses.first.address; + } + final receiveAddress = receiveAddresses.first.address; + + final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch); + if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) || typeMatchingReceiveAddresses.isEmpty) { receiveAddress = generateNewAddress().address; @@ -105,6 +141,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @override set address(String addr) { + if (addressPageType == SilentPaymentsAddresType.p2sp) { + activeSilentAddress = addr; + return; + } + if (addr.startsWith('bitcoincash:')) { addr = toLegacy(addr); } @@ -134,6 +175,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { void set currentChangeAddressIndex(int index) => currentChangeAddressIndexByType[_addressPageType.toString()] = index; + int currentSilentAddressIndex; + @observable BitcoinAddressRecord? previousAddressRecord; @@ -195,7 +238,43 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return address; } + Map get labels { + final labels = {}; + for (int i = 0; i < silentAddresses.length; i++) { + final silentAddressRecord = silentAddresses[i]; + final silentAddress = + SilentPaymentDestination.fromAddress(silentAddressRecord.address, 0).spendPubkey.toHex(); + + if (silentAddressRecord.silentPaymentTweak != null) + labels[silentAddress] = silentAddressRecord.silentPaymentTweak!; + } + return labels; + } + BitcoinAddressRecord generateNewAddress({String label = ''}) { + if (addressPageType == SilentPaymentsAddresType.p2sp) { + currentSilentAddressIndex += 1; + + final tweak = BigInt.from(currentSilentAddressIndex); + + final address = BitcoinAddressRecord( + SilentPaymentAddress.createLabeledSilentPaymentAddress( + primarySilentAddress!.scanPubkey, primarySilentAddress!.spendPubkey, tweak, + hrp: primarySilentAddress!.hrp, version: primarySilentAddress!.version) + .toString(), + index: currentSilentAddressIndex, + isHidden: false, + name: label, + silentPaymentTweak: tweak.toString(), + network: network, + type: SilentPaymentsAddresType.p2sp, + ); + + silentAddresses.add(address); + + return address; + } + final newAddressIndex = addressesByReceiveType.fold( 0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc); diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 98c3753db3..ceb603f9f3 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -13,11 +13,13 @@ class ElectrumWalletSnapshot { required this.password, required this.mnemonic, required this.addresses, + required this.silentAddresses, required this.balance, required this.regularAddressIndex, required this.changeAddressIndex, required this.addressPageType, required this.network, + required this.silentAddressIndex, }); final String name; @@ -28,24 +30,36 @@ class ElectrumWalletSnapshot { String mnemonic; List addresses; + List silentAddresses; ElectrumBalance balance; Map regularAddressIndex; Map changeAddressIndex; + int silentAddressIndex; - static Future load(String name, WalletType type, String password, BasedUtxoNetwork? network) async { + static Future load( + String name, WalletType type, String password, BasedUtxoNetwork? network) async { final path = await pathForWallet(name: name, type: type); final jsonSource = await read(path: path, password: password); final data = json.decode(jsonSource) as Map; - final addressesTmp = data['addresses'] as List? ?? []; final mnemonic = data['mnemonic'] as String; + + final addressesTmp = data['addresses'] as List? ?? []; final addresses = addressesTmp .whereType() - .map((addr) => BitcoinAddressRecord.fromJSON(addr, network)) + .map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network)) .toList(); + + final silentAddressesTmp = data['silent_addresses'] as List? ?? []; + final silentAddresses = silentAddressesTmp + .whereType() + .map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network)) + .toList(); + final balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; + var silentAddressIndex = 0; try { regularAddressIndexByType = { @@ -55,6 +69,7 @@ class ElectrumWalletSnapshot { SegwitAddresType.p2wpkh.toString(): int.parse(data['change_address_index'] as String? ?? '0') }; + silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0'); } catch (_) { try { regularAddressIndexByType = data["account_index"] as Map? ?? {}; @@ -68,11 +83,13 @@ class ElectrumWalletSnapshot { password: password, mnemonic: mnemonic, addresses: addresses, + silentAddresses: silentAddresses, balance: balance, regularAddressIndex: regularAddressIndexByType, changeAddressIndex: changeAddressIndexByType, addressPageType: data['address_page_type'] as String? ?? SegwitAddresType.p2wpkh.toString(), network: data['network_type'] == 'testnet' ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet, + silentAddressIndex: silentAddressIndex, ); } } diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 25e6f269d8..aff28df6eb 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -79,8 +79,8 @@ packages: dependency: "direct main" description: path: "." - ref: cake-update-v1 - resolved-ref: "9611e9db77e92a8434e918cdfb620068f6fcb1aa" + ref: cake-update-v2 + resolved-ref: e4686da77cace5400697de69f7885020297cb900 url: "https://github.com/cake-tech/bitcoin_base.git" source: git version: "4.0.0" @@ -93,14 +93,6 @@ packages: url: "https://github.com/cake-tech/bitcoin_flutter.git" source: git version: "2.1.0" - blockchain_utils: - dependency: "direct main" - description: - name: blockchain_utils - sha256: "9701dfaa74caad4daae1785f1ec4445cf7fb94e45620bc3a4aca1b9b281dc6c9" - url: "https://pub.dev" - source: hosted - version: "1.6.0" boolean_selector: dependency: transitive description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 847b777731..b6acab7f48 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -33,8 +33,7 @@ dependencies: bitcoin_base: git: url: https://github.com/cake-tech/bitcoin_base.git - ref: cake-update-v1 - blockchain_utils: ^1.6.0 + ref: cake-update-v2 dev_dependencies: flutter_test: diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 9c098c0ff3..7130b3c58f 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -32,7 +32,7 @@ dependencies: bitcoin_base: git: url: https://github.com/cake-tech/bitcoin_base.git - ref: cake-update-v1 + ref: cake-update-v2 diff --git a/cw_core/lib/sync_status.dart b/cw_core/lib/sync_status.dart index 4983967d0b..afddc7c7a7 100644 --- a/cw_core/lib/sync_status.dart +++ b/cw_core/lib/sync_status.dart @@ -14,6 +14,16 @@ class SyncingSyncStatus extends SyncStatus { @override String toString() => '$blocksLeft'; + + factory SyncingSyncStatus.fromHeightValues(int chainTip, int initialSyncHeight, int syncHeight) { + final track = chainTip - initialSyncHeight; + final diff = track - (chainTip - syncHeight); + final ptc = diff <= 0 ? 0.0 : diff / track; + final left = chainTip - syncHeight; + + // sum 1 because if at the chain tip, will say "0 blocks left" + return SyncingSyncStatus(left + 1, ptc); + } } class SyncedSyncStatus extends SyncStatus { @@ -51,4 +61,6 @@ class ConnectedSyncStatus extends SyncStatus { class LostConnectionSyncStatus extends SyncStatus { @override double progress() => 1.0; -} \ No newline at end of file + @override + String toString() => 'Reconnecting'; +} diff --git a/cw_core/lib/unspent_transaction_output.dart b/cw_core/lib/unspent_transaction_output.dart index b52daf43cc..01b26cdcc6 100644 --- a/cw_core/lib/unspent_transaction_output.dart +++ b/cw_core/lib/unspent_transaction_output.dart @@ -16,5 +16,6 @@ class Unspent { bool isFrozen; String note; - bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc'); + bool get isP2wpkh => + address.startsWith('bc') || address.startsWith('tb') || address.startsWith('ltc'); } diff --git a/howto-build-android.md b/howto-build-android.md index a2a4e4d9f5..c3fe415ee3 100644 --- a/howto-build-android.md +++ b/howto-build-android.md @@ -142,27 +142,9 @@ Then we need to generate localization files. `$ flutter packages pub run tool/generate_localization.dart` -Lastly, we will generate mobx models for the project. - -Generate mobx models for `cw_core`: - -`cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..` - -Generate mobx models for `cw_monero`: - -`cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..` - -Generate mobx models for `cw_bitcoin`: - -`cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..` - -Generate mobx models for `cw_haven`: - -`cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..` - Finally build mobx models for the app: -`$ flutter packages pub run build_runner build --delete-conflicting-outputs` +`$ ./model_generator.sh` ### 9. Build! diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index f9c20d45ec..891c6298a2 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -188,4 +188,9 @@ class CWBitcoin extends Bitcoin { @override List getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all; + + List getSilentAddresses(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.walletAddresses.silentAddresses; + } } diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 853762a1c8..19cdb16160 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -26,7 +26,7 @@ class AddressValidator extends TextValidator { return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$' '|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$'; case CryptoCurrency.btc: - return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$'; + return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${bitcoin.SilentPaymentAddress.REGEX.pattern}\$'; case CryptoCurrency.nano: return '[0-9a-zA-Z_]'; case CryptoCurrency.banano: @@ -274,7 +274,8 @@ class AddressValidator extends TextValidator { '([^0-9a-zA-Z]|^)${P2shAddress.regex.pattern}|\$)' '([^0-9a-zA-Z]|^)${P2wpkhAddress.regex.pattern}|\$)' '([^0-9a-zA-Z]|^)${P2wshAddress.regex.pattern}|\$)' - '([^0-9a-zA-Z]|^)${P2trAddress.regex.pattern}|\$)'; + '([^0-9a-zA-Z]|^)${P2trAddress.regex.pattern}|\$)' + '|${bitcoin.SilentPaymentAddress.REGEX.pattern}\$'; case CryptoCurrency.ltc: return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)' diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index 66094de2b0..fbb86fa9fb 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -3,7 +3,9 @@ import 'package:cw_core/sync_status.dart'; String syncStatusTitle(SyncStatus syncStatus) { if (syncStatus is SyncingSyncStatus) { - return S.current.Blocks_remaining('${syncStatus.blocksLeft}'); + return syncStatus.blocksLeft == 1 + ? S.current.Block_remaining('${syncStatus.blocksLeft}') + : S.current.Blocks_remaining('${syncStatus.blocksLeft}'); } if (syncStatus is SyncedSyncStatus) { @@ -35,4 +37,4 @@ String syncStatusTitle(SyncStatus syncStatus) { } return ''; -} \ No newline at end of file +} diff --git a/lib/di.dart b/lib/di.dart index 473eaed00e..d2af49f4b2 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -229,6 +229,7 @@ import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as btc; import 'buy/dfx/dfx_buy_provider.dart'; import 'core/totp_request_details.dart'; @@ -657,6 +658,10 @@ Future setup({ getIt.registerFactory(() { final wallet = getIt.get().wallet!; + // if ((wallet.type == WalletType.bitcoin && + // wallet.walletAddresses.addressPageType == btc.AddressType.p2sp) || + // wallet.type == WalletType.monero || + // wallet.type == WalletType.haven) { if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) { return MoneroAccountListViewModel(wallet); } diff --git a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart index 33bceeb5c0..64125b1457 100644 --- a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart +++ b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart @@ -10,8 +10,7 @@ import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; class PresentReceiveOptionPicker extends StatelessWidget { - PresentReceiveOptionPicker( - {required this.receiveOptionViewModel, required this.color}); + PresentReceiveOptionPicker({required this.receiveOptionViewModel, required this.color}); final ReceiveOptionViewModel receiveOptionViewModel; final Color color; @@ -43,17 +42,11 @@ class PresentReceiveOptionPicker extends StatelessWidget { Text( S.current.receive, style: TextStyle( - fontSize: 18.0, - fontWeight: FontWeight.bold, - fontFamily: 'Lato', - color: color), + fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color), ), Observer( - builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(), - style: TextStyle( - fontSize: 10.0, - fontWeight: FontWeight.w500, - color: color))) + builder: (_) => Text(describeOption(receiveOptionViewModel.selectedReceiveOption), + style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w500, color: color))) ], ), SizedBox(width: 5), @@ -73,65 +66,68 @@ class PresentReceiveOptionPicker extends StatelessWidget { backgroundColor: Colors.transparent, body: Stack( alignment: AlignmentDirectional.center, - children:[ AlertBackground( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Spacer(), - Container( - margin: EdgeInsets.symmetric(horizontal: 24), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: Theme.of(context).colorScheme.background, - ), - child: Padding( - padding: const EdgeInsets.only(top: 24, bottom: 24), - child: (ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - itemCount: receiveOptionViewModel.options.length, - itemBuilder: (_, index) { - final option = receiveOptionViewModel.options[index]; - return InkWell( - onTap: () { - Navigator.pop(popUpContext); + children: [ + AlertBackground( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Spacer(), + Container( + margin: EdgeInsets.symmetric(horizontal: 24), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Theme.of(context).colorScheme.background, + ), + child: Padding( + padding: const EdgeInsets.only(top: 24, bottom: 24), + child: (ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: receiveOptionViewModel.options.length, + itemBuilder: (_, index) { + final option = receiveOptionViewModel.options[index]; + return InkWell( + onTap: () { + Navigator.pop(popUpContext); - receiveOptionViewModel.selectReceiveOption(option); - }, - child: Padding( - padding: const EdgeInsets.only(left: 24, right: 24), - child: Observer(builder: (_) { - final value = receiveOptionViewModel.selectedReceiveOption; + receiveOptionViewModel.selectReceiveOption(option); + }, + child: Padding( + padding: const EdgeInsets.only(left: 24, right: 24), + child: Observer(builder: (_) { + final value = receiveOptionViewModel.selectedReceiveOption; - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(option.toString(), - textAlign: TextAlign.left, - style: textSmall( - color: Theme.of(context).extension()!.titleColor, - ).copyWith( - fontWeight: - value == option ? FontWeight.w800 : FontWeight.w500, - )), - RoundedCheckbox( - value: value == option, - ) - ], - ); - }), - ), - ); - }, - separatorBuilder: (_, index) => SizedBox(height: 30), - )), + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(describeOption(option), + textAlign: TextAlign.left, + style: textSmall( + color: Theme.of(context) + .extension()! + .titleColor, + ).copyWith( + fontWeight: + value == option ? FontWeight.w800 : FontWeight.w500, + )), + RoundedCheckbox( + value: value == option, + ) + ], + ); + }), + ), + ); + }, + separatorBuilder: (_, index) => SizedBox(height: 30), + )), + ), ), - ), - Spacer() - ], + Spacer() + ], + ), ), - ), AlertCloseButton(onTap: () => Navigator.of(popUpContext).pop(), bottom: 40) ], ), diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 75719d1239..3f3e546b3b 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -67,8 +67,7 @@ class ReceivePage extends BasePage { @override Widget Function(BuildContext, Widget) get rootWrapper => - (BuildContext context, Widget scaffold) => - GradientBackground(scaffold: scaffold); + (BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold); @override Widget trailing(BuildContext context) { @@ -159,7 +158,8 @@ class ReceivePage extends BasePage { trailingIcon: Icon( Icons.arrow_forward_ios, size: 14, - color: Theme.of(context).extension()!.iconsColor, + color: + Theme.of(context).extension()!.iconsColor, )); } @@ -185,11 +185,19 @@ class ReceivePage extends BasePage { final isCurrent = item.address == addressListViewModel.address.address; final backgroundColor = isCurrent - ? Theme.of(context).extension()!.currentTileBackgroundColor - : Theme.of(context).extension()!.tilesBackgroundColor; + ? Theme.of(context) + .extension()! + .currentTileBackgroundColor + : Theme.of(context) + .extension()! + .tilesBackgroundColor; final textColor = isCurrent - ? Theme.of(context).extension()!.currentTileTextColor - : Theme.of(context).extension()!.tilesTextColor; + ? Theme.of(context) + .extension()! + .currentTileTextColor + : Theme.of(context) + .extension()! + .tilesTextColor; return AddressCell.fromItem(item, isCurrent: isCurrent, @@ -211,6 +219,16 @@ class ReceivePage extends BasePage { child: cell, ); })), + if (!addressListViewModel.hasSilentAddresses) + Padding( + padding: EdgeInsets.fromLTRB(24, 24, 24, 32), + child: Text(S.of(context).electrum_address_disclaimer, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + color: + Theme.of(context).extension()!.labelTextColor)), + ), ], ), )) diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 6bd2d81e9f..7b3df5175d 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -164,7 +165,7 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin _presentQRScanner(BuildContext context) async { bool isCameraPermissionGranted = - await PermissionHandler.checkPermission(Permission.camera, context); + await PermissionHandler.checkPermission(Permission.camera, context); if (!isCameraPermissionGranted) return; final code = await presentQRScanner(); if (code.isEmpty) { diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index da5eb03739..db932ff333 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -278,6 +278,11 @@ abstract class DashboardViewModelBase with Store { WalletBase, TransactionInfo> wallet; bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven; + // bool get hasRescan => + // (wallet.type == WalletType.bitcoin && + // wallet.walletAddresses.addressPageType == bitcoin.AddressType.p2sp) || + // wallet.type == WalletType.monero || + // wallet.type == WalletType.haven; final KeyService keyService; diff --git a/lib/view_model/rescan_view_model.dart b/lib/view_model/rescan_view_model.dart index c973b7b3f0..e263f4a121 100644 --- a/lib/view_model/rescan_view_model.dart +++ b/lib/view_model/rescan_view_model.dart @@ -1,4 +1,5 @@ import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; part 'rescan_view_model.g.dart'; @@ -9,8 +10,8 @@ enum RescanWalletState { rescaning, none } abstract class RescanViewModelBase with Store { RescanViewModelBase(this._wallet) - : state = RescanWalletState.none, - isButtonEnabled = false; + : state = RescanWalletState.none, + isButtonEnabled = false; final WalletBase _wallet; @@ -23,8 +24,8 @@ abstract class RescanViewModelBase with Store { @action Future rescanCurrentWallet({required int restoreHeight}) async { state = RescanWalletState.rescaning; - await _wallet.rescan(height: restoreHeight); - _wallet.transactionHistory.clear(); + _wallet.rescan(height: restoreHeight); + if (_wallet.type != WalletType.bitcoin) _wallet.transactionHistory.clear(); state = RescanWalletState.none; } -} \ No newline at end of file +} diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index a2aab5251f..817b37d7a0 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -183,8 +183,6 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo }) : _baseItems = [], selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type), _cryptoNumberFormat = NumberFormat(_cryptoNumberPattern), - hasAccounts = - appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.haven, amount = '', _settingsStore = appStore.settingsStore, super(appStore: appStore) { @@ -196,7 +194,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo _init(); selectedCurrency = walletTypeToCryptoCurrency(wallet.type); - hasAccounts = wallet.type == WalletType.monero || wallet.type == WalletType.haven; + _hasAccounts = + hasSilentAddresses || wallet.type == WalletType.monero || wallet.type == WalletType.haven; } static const String _cryptoNumberPattern = '0.00000000'; @@ -365,7 +364,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo } @observable - bool hasAccounts; + bool _hasAccounts = false; + + @computed + bool get hasAccounts => _hasAccounts; @computed String get accountLabel { @@ -380,8 +382,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return ''; } + @observable + // ignore: prefer_final_fields + bool? _hasSilentAddresses = null; + + @computed + bool get hasSilentAddresses => _hasSilentAddresses ?? wallet.type == WalletType.bitcoin; + // @computed + // bool get hasSilentAddresses => + // _hasSilentAddresses ?? + // wallet.type == WalletType.bitcoin && + // wallet.walletAddresses.addressPageType == btc.AddressType.p2sp; + @computed bool get hasAddressList => + hasSilentAddresses || wallet.type == WalletType.monero || wallet.type == WalletType.haven || wallet.type == WalletType.bitcoinCash || diff --git a/model_generator.sh b/model_generator.sh index 8a60986216..fa1ea6fac0 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -1,11 +1,10 @@ -cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_solana && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_ethereum && flutter pub get && cd .. -cd cw_polygon && flutter pub get && cd .. +cd cw_core; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. +cd cw_evm; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. +cd cw_monero; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. +cd cw_bitcoin; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. +cd cw_haven; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. +cd cw_nano; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. +cd cw_bitcoin_cash; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. +cd cw_polygon; flutter pub get; cd .. +cd cw_ethereum; flutter pub get; cd .. flutter packages pub run build_runner build --delete-conflicting-outputs diff --git a/tool/configure.dart b/tool/configure.dart index fb1647e131..91ea71d1f8 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -125,6 +125,7 @@ abstract class Bitcoin { List getAddresses(Object wallet); String getAddress(Object wallet); + List getSilentAddresses(Object wallet); List getSubAddresses(Object wallet); From 2cbf1dca880c018358ae713f3ce21d17bb3bbbbb Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 26 Feb 2024 15:33:31 -0300 Subject: [PATCH 002/242] feat: allow scanning elect-rs using get_tweaks --- .../lib/bitcoin_receive_page_option.dart | 21 +- cw_bitcoin/lib/bitcoin_wallet.dart | 26 +- cw_bitcoin/lib/electrum.dart | 13 +- cw_bitcoin/lib/electrum_wallet.dart | 685 ++++++++---------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 4 +- cw_bitcoin/lib/litecoin_wallet.dart | 1 - cw_bitcoin/lib/litecoin_wallet_addresses.dart | 1 - cw_bitcoin/pubspec.yaml | 4 + .../lib/src/bitcoin_cash_wallet.dart | 1 - .../src/bitcoin_cash_wallet_addresses.dart | 1 - cw_bitcoin_cash/pubspec.yaml | 6 +- lib/bitcoin/cw_bitcoin.dart | 6 + lib/core/address_validator.dart | 4 +- lib/core/sync_status_title.dart | 2 +- .../screens/dashboard/pages/address_page.dart | 5 + .../present_receive_option_picker.dart | 4 +- .../address_edit_or_create_page.dart | 24 +- .../dashboard/dashboard_view_model.dart | 21 +- .../wallet_address_list_view_model.dart | 5 +- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + tool/configure.dart | 1 + 46 files changed, 413 insertions(+), 448 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_receive_page_option.dart b/cw_bitcoin/lib/bitcoin_receive_page_option.dart index 2b025965bd..b14753ffce 100644 --- a/cw_bitcoin/lib/bitcoin_receive_page_option.dart +++ b/cw_bitcoin/lib/bitcoin_receive_page_option.dart @@ -23,9 +23,28 @@ class BitcoinReceivePageOption implements ReceivePageOption { BitcoinReceivePageOption.p2sh, BitcoinReceivePageOption.p2tr, BitcoinReceivePageOption.p2wsh, - BitcoinReceivePageOption.p2pkh + BitcoinReceivePageOption.p2pkh, + BitcoinReceivePageOption.silent_payments, ]; + BitcoinAddressType toType() { + switch (this) { + case BitcoinReceivePageOption.p2tr: + return SegwitAddresType.p2tr; + case BitcoinReceivePageOption.p2wsh: + return SegwitAddresType.p2wsh; + case BitcoinReceivePageOption.p2pkh: + return P2pkhAddressType.p2pkh; + case BitcoinReceivePageOption.p2sh: + return P2shAddressType.p2wpkhInP2sh; + case BitcoinReceivePageOption.silent_payments: + return SilentPaymentsAddresType.p2sp; + case BitcoinReceivePageOption.p2wpkh: + default: + return SegwitAddresType.p2wpkh; + } + } + factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) { switch (type) { case SegwitAddresType.p2tr: diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index a018abbd6a..dfd7c8fe54 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -78,6 +78,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { Map? initialChangeAddressIndex, int initialSilentAddressIndex = 0, }) async { + final seedBytes = await mnemonicToSeedBytes(mnemonic); return BitcoinWallet( mnemonic: mnemonic, password: password, @@ -86,10 +87,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialAddresses: initialAddresses, initialSilentAddresses: initialSilentAddresses, initialSilentAddressIndex: initialSilentAddressIndex, - silentAddress: await SilentPaymentOwner.fromMnemonic(mnemonic, + silentAddress: await SilentPaymentOwner.fromPrivateKeys( + scanPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + seedBytes, + network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, + ).derivePath(SCAN_PATH).privKey!), + spendPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + seedBytes, + network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, + ).derivePath(SPEND_PATH).privKey!), hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'), initialBalance: initialBalance, - seedBytes: await mnemonicToSeedBytes(mnemonic), + seedBytes: seedBytes, initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, addressPageType: addressPageType, @@ -106,6 +115,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : null); + final seedBytes = await mnemonicToSeedBytes(snp.mnemonic); return BitcoinWallet( mnemonic: snp.mnemonic, password: password, @@ -114,10 +124,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialAddresses: snp.addresses, initialSilentAddresses: snp.silentAddresses, initialSilentAddressIndex: snp.silentAddressIndex, - silentAddress: await SilentPaymentOwner.fromMnemonic(snp.mnemonic, + silentAddress: await SilentPaymentOwner.fromPrivateKeys( + scanPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + seedBytes, + network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, + ).derivePath(SCAN_PATH).privKey!), + spendPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + seedBytes, + network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, + ).derivePath(SPEND_PATH).privKey!), hrp: snp.network == BitcoinNetwork.testnet ? 'tsp' : 'sp'), initialBalance: snp.balance, - seedBytes: await mnemonicToSeedBytes(snp.mnemonic), + seedBytes: seedBytes, initialRegularAddressIndex: snp.regularAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex, addressPageType: snp.addressPageType, diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 59c864eb2e..27a43ddcb0 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -48,15 +48,19 @@ class ElectrumClient { Timer? _aliveTimer; String unterminatedString; - Future connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port); + Uri? uri; + + Future connectToUri(Uri uri) async { + this.uri = uri; + await connect(host: uri.host, port: uri.port); + } Future connect({required String host, required int port}) async { try { await socket?.close(); } catch (_) {} - socket = await SecureSocket.connect(host, port, - timeout: connectionTimeout, onBadCertificate: (_) => true); + socket = await Socket.connect(host, port, timeout: connectionTimeout); _setIsConnected(true); socket!.listen((Uint8List event) { @@ -275,6 +279,9 @@ class ElectrumClient { Future> getHeader({required int height}) async => await call(method: 'blockchain.block.get_header', params: [height]) as Map; + Future> getTweaks({required int height}) async => + await call(method: 'blockchain.block.tweaks', params: [height]) as Map; + Future estimatefee({required int p}) => call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) { if (result is double) { diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 4bef9b7489..f875a1b1f0 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -6,7 +6,7 @@ import 'dart:math'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:collection/collection.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; @@ -146,7 +146,7 @@ abstract class ElectrumWalletBase Map?> _scripthashesUpdateSubject; BehaviorSubject? _chainTipUpdateSubject; bool _isTransactionUpdating; - // Future? _isolate; + Future? _isolate; void Function(FlutterErrorDetails)? _onError; Timer? _autoSaveTimer; @@ -160,66 +160,66 @@ abstract class ElectrumWalletBase Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); } - // @action - // Future _setListeners(int height, {int? chainTip}) async { - // final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; - // syncStatus = AttemptingSyncStatus(); - - // if (_isolate != null) { - // final runningIsolate = await _isolate!; - // runningIsolate.kill(priority: Isolate.immediate); - // } - - // final receivePort = ReceivePort(); - // _isolate = Isolate.spawn( - // startRefresh, - // ScanData( - // sendPort: receivePort.sendPort, - // primarySilentAddress: walletAddresses.primarySilentAddress!, - // networkType: networkType, - // height: height, - // chainTip: currentChainTip, - // electrumClient: ElectrumClient(), - // transactionHistoryIds: transactionHistory.transactions.keys.toList(), - // node: electrumClient.uri.toString(), - // labels: walletAddresses.labels, - // )); - - // await for (var message in receivePort) { - // if (message is BitcoinUnspent) { - // if (!unspentCoins.any((utx) => - // utx.hash.contains(message.hash) && - // utx.vout == message.vout && - // utx.address.contains(message.address))) { - // unspentCoins.add(message); - - // if (unspentCoinsInfo.values.any((element) => - // element.walletId.contains(id) && - // element.hash.contains(message.hash) && - // element.address.contains(message.address))) { - // _addCoinInfo(message); - - // await walletInfo.save(); - // await save(); - // } - - // balance[currency] = await _fetchBalances(); - // } - // } - - // if (message is Map) { - // transactionHistory.addMany(message); - // await transactionHistory.save(); - // } - - // // check if is a SyncStatus type since "is SyncStatus" doesn't work here - // if (message is SyncResponse) { - // syncStatus = message.syncStatus; - // walletInfo.restoreHeight = message.height; - // await walletInfo.save(); - // } - // } - // } + @action + Future _setListeners(int height, {int? chainTip}) async { + final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; + syncStatus = AttemptingSyncStatus(); + + if (_isolate != null) { + final runningIsolate = await _isolate!; + runningIsolate.kill(priority: Isolate.immediate); + } + + final receivePort = ReceivePort(); + _isolate = Isolate.spawn( + startRefresh, + ScanData( + sendPort: receivePort.sendPort, + primarySilentAddress: walletAddresses.primarySilentAddress!, + network: network, + height: height, + chainTip: currentChainTip, + electrumClient: ElectrumClient(), + transactionHistoryIds: transactionHistory.transactions.keys.toList(), + node: electrumClient.uri.toString(), + labels: walletAddresses.labels, + )); + + await for (var message in receivePort) { + if (message is BitcoinUnspent) { + if (!unspentCoins.any((utx) => + utx.hash.contains(message.hash) && + utx.vout == message.vout && + utx.address.contains(message.address))) { + unspentCoins.add(message); + + if (unspentCoinsInfo.values.any((element) => + element.walletId.contains(id) && + element.hash.contains(message.hash) && + element.address.contains(message.address))) { + _addCoinInfo(message); + + await walletInfo.save(); + await save(); + } + + balance[currency] = await _fetchBalances(); + } + } + + if (message is Map) { + transactionHistory.addMany(message); + await transactionHistory.save(); + } + + // check if is a SyncStatus type since "is SyncStatus" doesn't work here + if (message is SyncResponse) { + syncStatus = message.syncStatus; + walletInfo.restoreHeight = message.height; + await walletInfo.save(); + } + } + } @action @override @@ -261,11 +261,11 @@ abstract class ElectrumWalletBase }; syncStatus = ConnectedSyncStatus(); - // final currentChainTip = await electrumClient.getCurrentBlockChainTip(); + final currentChainTip = await electrumClient.getCurrentBlockChainTip(); - // if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { - // _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); - // } + if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { + _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); + } } catch (e) { print(e.toString()); syncStatus = FailedSyncStatus(); @@ -697,7 +697,7 @@ abstract class ElectrumWalletBase @override Future rescan({required int height, int? chainTip, ScanData? scanData}) async { - // _setListeners(height); + _setListeners(height); } @override @@ -1003,7 +1003,7 @@ abstract class ElectrumWalletBase try { final currentHeight = await electrumClient.getCurrentBlockChainTip(); if (currentHeight != null) walletInfo.restoreHeight = currentHeight; - // _setListeners(walletInfo.restoreHeight, chainTip: currentHeight); + _setListeners(walletInfo.restoreHeight, chainTip: currentHeight); } catch (e, s) { print(e.toString()); _onError?.call(FlutterErrorDetails( @@ -1106,10 +1106,10 @@ abstract class ElectrumWalletBase class ScanData { final SendPort sendPort; - final SilentPaymentReceiver primarySilentAddress; + final SilentPaymentOwner primarySilentAddress; final int height; final String node; - final bitcoin.NetworkType networkType; + final BasedUtxoNetwork network; final int chainTip; final ElectrumClient electrumClient; final List transactionHistoryIds; @@ -1120,7 +1120,7 @@ class ScanData { required this.primarySilentAddress, required this.height, required this.node, - required this.networkType, + required this.network, required this.chainTip, required this.electrumClient, required this.transactionHistoryIds, @@ -1133,7 +1133,7 @@ class ScanData { primarySilentAddress: scanData.primarySilentAddress, height: newHeight, node: scanData.node, - networkType: scanData.networkType, + network: scanData.network, chainTip: scanData.chainTip, transactionHistoryIds: scanData.transactionHistoryIds, electrumClient: scanData.electrumClient, @@ -1149,333 +1149,220 @@ class SyncResponse { SyncResponse(this.height, this.syncStatus); } -// Future startRefresh(ScanData scanData) async { -// var cachedBlockchainHeight = scanData.chainTip; - -// Future getNodeHeightOrUpdate(int baseHeight) async { -// if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) { -// final electrumClient = scanData.electrumClient; -// if (!electrumClient.isConnected) { -// final node = scanData.node; -// await electrumClient.connectToUri(Uri.parse(node)); -// } - -// cachedBlockchainHeight = -// await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; -// } - -// return cachedBlockchainHeight; -// } - -// var lastKnownBlockHeight = 0; -// var initialSyncHeight = 0; - -// var syncHeight = scanData.height; -// var currentChainTip = scanData.chainTip; - -// if (syncHeight <= 0) { -// syncHeight = currentChainTip; -// } - -// if (initialSyncHeight <= 0) { -// initialSyncHeight = syncHeight; -// } - -// if (lastKnownBlockHeight == syncHeight) { -// scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); -// return; -// } - -// // Run this until no more blocks left to scan txs. At first this was recursive -// // i.e. re-calling the startRefresh function but this was easier for the above values to retain -// // their initial values -// while (true) { -// lastKnownBlockHeight = syncHeight; - -// final syncingStatus = -// SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight); -// scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); - -// if (syncingStatus.blocksLeft <= 0) { -// scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); -// return; -// } - -// // print(["Scanning from height:", syncHeight]); - -// try { -// final networkPath = -// scanData.networkType.network == bitcoin.BtcNetwork.mainnet ? "" : "/testnet"; - -// // This endpoint gets up to 10 latest blocks from the given height -// final tenNewestBlocks = -// (await http.get(Uri.parse("https://blockstream.info$networkPath/api/blocks/$syncHeight"))) -// .body; -// var decodedBlocks = json.decode(tenNewestBlocks) as List; - -// decodedBlocks.sort((a, b) => (a["height"] as int).compareTo(b["height"] as int)); -// decodedBlocks = -// decodedBlocks.where((element) => (element["height"] as int) >= syncHeight).toList(); - -// // for each block, get up to 25 txs -// for (var i = 0; i < decodedBlocks.length; i++) { -// final blockJson = decodedBlocks[i]; -// final blockHash = blockJson["id"]; -// final txCount = blockJson["tx_count"] as int; - -// // print(["Scanning block index:", i, "with tx count:", txCount]); - -// int startIndex = 0; -// // go through each tx in block until no more txs are left -// while (startIndex < txCount) { -// // This endpoint gets up to 25 txs from the given block hash and start index -// final twentyFiveTxs = json.decode((await http.get(Uri.parse( -// "https://blockstream.info$networkPath/api/block/$blockHash/txs/$startIndex"))) -// .body) as List; - -// // print(["Scanning txs index:", startIndex]); - -// // For each tx, apply silent payment filtering and do shared secret calculation when applied -// for (var i = 0; i < twentyFiveTxs.length; i++) { -// try { -// final tx = twentyFiveTxs[i]; -// final txid = tx["txid"] as String; - -// // print(["Scanning tx:", txid]); - -// // TODO: if tx already scanned & stored skip -// // if (scanData.transactionHistoryIds.contains(txid)) { -// // // already scanned tx, continue to next tx -// // pos++; -// // continue; -// // } - -// List pubkeys = []; -// List outpoints = []; - -// bool skip = false; - -// for (var i = 0; i < (tx["vin"] as List).length; i++) { -// final input = tx["vin"][i]; -// final prevout = input["prevout"]; -// final scriptPubkeyType = prevout["scriptpubkey_type"]; -// String? pubkey; - -// if (scriptPubkeyType == "v0_p2wpkh" || scriptPubkeyType == "v1_p2tr") { -// final witness = input["witness"]; -// if (witness == null) { -// skip = true; -// // print("Skipping, no witness"); -// break; -// } - -// if (witness.length == 2) { -// pubkey = witness[1] as String; -// } else if (witness.length == 1) { -// pubkey = "02" + (prevout["scriptpubkey"] as String).fromHex.sublist(2).hex; -// } -// } - -// if (scriptPubkeyType == "p2pkh") { -// pubkey = bitcoin.P2pkhAddress( -// scriptSig: bitcoin.Script.fromRaw(hexData: input["scriptsig"] as String)) -// .pubkey; -// } - -// if (pubkey == null) { -// skip = true; -// // print("Skipping, invalid witness"); -// break; -// } - -// pubkeys.add(pubkey); -// outpoints.add( -// bitcoin.Outpoint(txid: input["txid"] as String, index: input["vout"] as int)); -// } - -// if (skip) { -// // skipped tx, continue to next tx -// continue; -// } - -// Map outpointsByP2TRpubkey = {}; -// for (var i = 0; i < (tx["vout"] as List).length; i++) { -// final output = tx["vout"][i]; -// if (output["scriptpubkey_type"] != "v1_p2tr") { -// // print("Skipping, not a v1_p2tr output"); -// continue; -// } - -// final script = (output["scriptpubkey"] as String).fromHex; - -// // final alreadySpentOutput = (await electrumClient.getHistory( -// // scriptHashFromScript(script, networkType: scanData.networkType))) -// // .length > -// // 1; - -// // if (alreadySpentOutput) { -// // print("Skipping, invalid witness"); -// // break; -// // } - -// final p2tr = bitcoin.P2trAddress( -// program: script.sublist(2).hex, networkType: scanData.networkType); -// final address = p2tr.address; - -// print(["Verifying taproot address:", address]); - -// outpointsByP2TRpubkey[script.sublist(2).hex] = -// bitcoin.Outpoint(txid: txid, index: i, value: output["value"] as int); -// } - -// if (pubkeys.isEmpty || outpoints.isEmpty || outpointsByP2TRpubkey.isEmpty) { -// // skipped tx, continue to next tx -// continue; -// } - -// final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints); - -// final result = bitcoin.scanOutputs( -// scanData.primarySilentAddress.scanPrivkey, -// scanData.primarySilentAddress.spendPubkey, -// bitcoin.getSumInputPubKeys(pubkeys), -// outpointHash, -// outpointsByP2TRpubkey.keys.map((e) => e.fromHex).toList(), -// labels: scanData.labels, -// ); - -// if (result.isEmpty) { -// // no results tx, continue to next tx -// continue; -// } - -// if (result.length > 1) { -// print("MULTIPLE UNSPENT COINS FOUND!"); -// } else { -// print("UNSPENT COIN FOUND!"); -// } - -// result.forEach((key, value) async { -// final outpoint = outpointsByP2TRpubkey[key]; - -// if (outpoint == null) { -// return; -// } - -// final tweak = value[0]; -// String? label; -// if (value.length > 1) label = value[1]; - -// final txInfo = ElectrumTransactionInfo( -// WalletType.bitcoin, -// id: txid, -// height: syncHeight, -// amount: outpoint.value!, -// fee: 0, -// direction: TransactionDirection.incoming, -// isPending: false, -// date: DateTime.fromMillisecondsSinceEpoch((blockJson["timestamp"] as int) * 1000), -// confirmations: currentChainTip - syncHeight, -// to: bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress( -// scanData.primarySilentAddress.scanPubkey, -// scanData.primarySilentAddress.spendPubkey, -// label != null ? label.fromHex : "0".fromHex, -// hrp: scanData.primarySilentAddress.hrp, -// version: scanData.primarySilentAddress.version) -// .toString(), -// unspent: null, -// ); - -// final status = json.decode((await http -// .get(Uri.parse("https://blockstream.info/testnet/api/tx/$txid/outspends"))) -// .body) as List; - -// bool spent = false; -// for (final s in status) { -// if ((s["spent"] as bool) == true) { -// spent = true; - -// scanData.sendPort.send({txid: txInfo}); - -// final sentTxId = s["txid"] as String; -// final sentTx = json.decode((await http -// .get(Uri.parse("https://blockstream.info/testnet/api/tx/$sentTxId"))) -// .body); - -// int amount = 0; -// for (final out in (sentTx["vout"] as List)) { -// amount += out["value"] as int; -// } - -// final height = s["status"]["block_height"] as int; - -// scanData.sendPort.send({ -// sentTxId: ElectrumTransactionInfo( -// WalletType.bitcoin, -// id: sentTxId, -// height: height, -// amount: amount, -// fee: 0, -// direction: TransactionDirection.outgoing, -// isPending: false, -// date: DateTime.fromMillisecondsSinceEpoch( -// (s["status"]["block_time"] as int) * 1000), -// confirmations: currentChainTip - height, -// ) -// }); -// } -// } - -// if (spent) { -// return; -// } - -// final unspent = BitcoinUnspent( -// BitcoinAddressRecord( -// bitcoin.P2trAddress(program: key, networkType: scanData.networkType).address, -// index: 0, -// isHidden: true, -// isUsed: true, -// silentAddressLabel: null, -// silentPaymentTweak: tweak, -// type: bitcoin.AddressType.p2tr, -// ), -// txid, -// outpoint.value!, -// outpoint.index, -// silentPaymentTweak: tweak, -// type: bitcoin.AddressType.p2tr, -// ); - -// // found utxo for tx, send unspent coin to main isolate -// scanData.sendPort.send(unspent); - -// // also send tx data for tx history -// txInfo.unspent = unspent; -// scanData.sendPort.send({txid: txInfo}); -// }); -// } catch (_) {} -// } - -// // Finished scanning batch of txs in block, add 25 to start index and continue to next block in loop -// startIndex += 25; -// } - -// // Finished scanning block, add 1 to height and continue to next block in loop -// syncHeight += 1; -// currentChainTip = await getNodeHeightOrUpdate(syncHeight); -// scanData.sendPort.send(SyncResponse(syncHeight, -// SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); -// } -// } catch (e, stacktrace) { -// print(stacktrace); -// print(e.toString()); - -// scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus())); -// break; -// } -// } -// } +Future startRefresh(ScanData scanData) async { + var cachedBlockchainHeight = scanData.chainTip; + + Future getNodeHeightOrUpdate(int baseHeight) async { + if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) { + final electrumClient = scanData.electrumClient; + if (!electrumClient.isConnected) { + final node = scanData.node; + await electrumClient.connectToUri(Uri.parse(node)); + } + + cachedBlockchainHeight = + await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; + } + + return cachedBlockchainHeight; + } + + var lastKnownBlockHeight = 0; + var initialSyncHeight = 0; + + var syncHeight = scanData.height; + var currentChainTip = scanData.chainTip; + + if (syncHeight <= 0) { + syncHeight = currentChainTip; + } + + if (initialSyncHeight <= 0) { + initialSyncHeight = syncHeight; + } + + if (lastKnownBlockHeight == syncHeight) { + scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); + return; + } + + // Run this until no more blocks left to scan txs. At first this was recursive + // i.e. re-calling the startRefresh function but this was easier for the above values to retain + // their initial values + while (true) { + lastKnownBlockHeight = syncHeight; + + final syncingStatus = + SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight); + scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); + + if (syncingStatus.blocksLeft <= 0) { + scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); + return; + } + + print(["Scanning from height:", syncHeight]); + + try { + // Get all the tweaks from the block + final electrumClient = scanData.electrumClient; + if (!electrumClient.isConnected) { + final node = scanData.node; + await electrumClient.connectToUri(Uri.parse(node)); + } + final tweaks = await electrumClient.getTweaks(height: syncHeight); + + for (var i = 0; i < tweaks.length; i++) { + try { + // final txid = tweaks.keys.toList()[i]; + final details = tweaks.values.toList()[i]; + print(["details", details]); + final output_pubkeys = (details["output_pubkeys"] as List); + + // print(["Scanning tx:", txid]); + + // TODO: if tx already scanned & stored skip + // if (scanData.transactionHistoryIds.contains(txid)) { + // // already scanned tx, continue to next tx + // pos++; + // continue; + // } + + final result = SilentPayment.scanTweak( + scanData.primarySilentAddress.b_scan, + scanData.primarySilentAddress.B_spend, + details["tweak"] as String, + output_pubkeys.map((e) => BytesUtils.fromHexString(e)).toList(), + labels: scanData.labels, + ); + + if (result.isEmpty) { + // no results tx, continue to next tx + continue; + } + + if (result.length > 1) { + print("MULTIPLE UNSPENT COINS FOUND!"); + } else { + print("UNSPENT COIN FOUND!"); + } + + // result.forEach((key, value) async { + // final outpoint = output_pubkeys[key]; + + // if (outpoint == null) { + // return; + // } + + // final tweak = value[0]; + // String? label; + // if (value.length > 1) label = value[1]; + + // final txInfo = ElectrumTransactionInfo( + // WalletType.bitcoin, + // id: txid, + // height: syncHeight, + // amount: outpoint.value!, + // fee: 0, + // direction: TransactionDirection.incoming, + // isPending: false, + // date: DateTime.fromMillisecondsSinceEpoch((blockJson["timestamp"] as int) * 1000), + // confirmations: currentChainTip - syncHeight, + // to: bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress( + // scanData.primarySilentAddress.scanPubkey, + // scanData.primarySilentAddress.spendPubkey, + // label != null ? label.fromHex : "0".fromHex, + // hrp: scanData.primarySilentAddress.hrp, + // version: scanData.primarySilentAddress.version) + // .toString(), + // unspent: null, + // ); + + // final status = json.decode((await http + // .get(Uri.parse("https://blockstream.info/testnet/api/tx/$txid/outspends"))) + // .body) as List; + + // bool spent = false; + // for (final s in status) { + // if ((s["spent"] as bool) == true) { + // spent = true; + + // scanData.sendPort.send({txid: txInfo}); + + // final sentTxId = s["txid"] as String; + // final sentTx = json.decode( + // (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$sentTxId"))) + // .body); + + // int amount = 0; + // for (final out in (sentTx["vout"] as List)) { + // amount += out["value"] as int; + // } + + // final height = s["status"]["block_height"] as int; + + // scanData.sendPort.send({ + // sentTxId: ElectrumTransactionInfo( + // WalletType.bitcoin, + // id: sentTxId, + // height: height, + // amount: amount, + // fee: 0, + // direction: TransactionDirection.outgoing, + // isPending: false, + // date: DateTime.fromMillisecondsSinceEpoch( + // (s["status"]["block_time"] as int) * 1000), + // confirmations: currentChainTip - height, + // ) + // }); + // } + // } + + // if (spent) { + // return; + // } + + // final unspent = BitcoinUnspent( + // BitcoinAddressRecord( + // bitcoin.P2trAddress(program: key, networkType: scanData.network).address, + // index: 0, + // isHidden: true, + // isUsed: true, + // silentAddressLabel: null, + // silentPaymentTweak: tweak, + // type: bitcoin.AddressType.p2tr, + // ), + // txid, + // outpoint.value!, + // outpoint.index, + // silentPaymentTweak: tweak, + // type: bitcoin.AddressType.p2tr, + // ); + + // // found utxo for tx, send unspent coin to main isolate + // scanData.sendPort.send(unspent); + + // // also send tx data for tx history + // txInfo.unspent = unspent; + // scanData.sendPort.send({txid: txInfo}); + // }); + } catch (_) {} + } + + // Finished scanning block, add 1 to height and continue to next block in loop + syncHeight += 1; + currentChainTip = await getNodeHeightOrUpdate(syncHeight); + scanData.sendPort.send(SyncResponse(syncHeight, + SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); + } catch (e, stacktrace) { + print(stacktrace); + print(e.toString()); + + scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus())); + break; + } + } +} class EstimatedTxResult { EstimatedTxResult( diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 23482e4d70..bd6781ea80 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -243,7 +243,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { for (int i = 0; i < silentAddresses.length; i++) { final silentAddressRecord = silentAddresses[i]; final silentAddress = - SilentPaymentDestination.fromAddress(silentAddressRecord.address, 0).spendPubkey.toHex(); + SilentPaymentDestination.fromAddress(silentAddressRecord.address, 0).B_spend.toHex(); if (silentAddressRecord.silentPaymentTweak != null) labels[silentAddress] = silentAddressRecord.silentPaymentTweak!; @@ -259,7 +259,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final address = BitcoinAddressRecord( SilentPaymentAddress.createLabeledSilentPaymentAddress( - primarySilentAddress!.scanPubkey, primarySilentAddress!.spendPubkey, tweak, + primarySilentAddress!.B_scan, primarySilentAddress!.B_spend, tweak, hrp: primarySilentAddress!.hrp, version: primarySilentAddress!.version) .toString(), index: currentSilentAddressIndex, diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index d2379d5a5a..5641909c01 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -44,7 +44,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { currency: CryptoCurrency.ltc) { walletAddresses = LitecoinWalletAddresses( walletInfo, - electrumClient: electrumClient, initialAddresses: initialAddresses, initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index 993d17933a..99b7445fc9 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -15,7 +15,6 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with required super.mainHd, required super.sideHd, required super.network, - required super.electrumClient, super.initialAddresses, super.initialRegularAddressIndex, super.initialChangeAddressIndex, diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index b6acab7f48..13a0e26886 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -34,6 +34,10 @@ dependencies: git: url: https://github.com/cake-tech/bitcoin_base.git ref: cake-update-v2 + blockchain_utils: + git: + url: https://github.com/rafael-xmr/blockchain_utils + ref: cake-update-v1 dev_dependencies: flutter_test: diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 3c40cf9e91..2e4788c2d2 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -51,7 +51,6 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { currency: CryptoCurrency.bch) { walletAddresses = BitcoinCashWalletAddresses( walletInfo, - electrumClient: electrumClient, initialAddresses: initialAddresses, initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart index 8291ce2a57..492b2daa80 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart @@ -15,7 +15,6 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi required super.mainHd, required super.sideHd, required super.network, - required super.electrumClient, super.initialAddresses, super.initialRegularAddressIndex, super.initialChangeAddressIndex, diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 7130b3c58f..fd7ed5a0fc 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -33,8 +33,10 @@ dependencies: git: url: https://github.com/cake-tech/bitcoin_base.git ref: cake-update-v2 - - + blockchain_utils: + git: + url: https://github.com/rafael-xmr/blockchain_utils + ref: cake-update-v1 dev_dependencies: flutter_test: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 891c6298a2..fb470a58f1 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -186,6 +186,12 @@ class CWBitcoin extends Bitcoin { return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType); } + @override + bool hasSelectedSilentPayments(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp; + } + @override List getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all; diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 19cdb16160..e9ab8b827c 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -26,7 +26,7 @@ class AddressValidator extends TextValidator { return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$' '|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$'; case CryptoCurrency.btc: - return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${bitcoin.SilentPaymentAddress.REGEX.pattern}\$'; + return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${SilentPaymentAddress.regex.pattern}\$'; case CryptoCurrency.nano: return '[0-9a-zA-Z_]'; case CryptoCurrency.banano: @@ -275,7 +275,7 @@ class AddressValidator extends TextValidator { '([^0-9a-zA-Z]|^)${P2wpkhAddress.regex.pattern}|\$)' '([^0-9a-zA-Z]|^)${P2wshAddress.regex.pattern}|\$)' '([^0-9a-zA-Z]|^)${P2trAddress.regex.pattern}|\$)' - '|${bitcoin.SilentPaymentAddress.REGEX.pattern}\$'; + '|${SilentPaymentAddress.regex.pattern}\$'; case CryptoCurrency.ltc: return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)' diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index fbb86fa9fb..226b97f508 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -4,7 +4,7 @@ import 'package:cw_core/sync_status.dart'; String syncStatusTitle(SyncStatus syncStatus) { if (syncStatus is SyncingSyncStatus) { return syncStatus.blocksLeft == 1 - ? S.current.Block_remaining('${syncStatus.blocksLeft}') + ? S.current.block_remaining : S.current.Blocks_remaining('${syncStatus.blocksLeft}'); } diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart index 7b7c84c28c..ce1ab31f14 100644 --- a/lib/src/screens/dashboard/pages/address_page.dart +++ b/lib/src/screens/dashboard/pages/address_page.dart @@ -199,6 +199,11 @@ class AddressPage extends BasePage { } reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { + if (option is BitcoinReceivePageOption) { + addressListViewModel.setAddressType(option.toType()); + return; + } + switch (option) { case ReceivePageOption.anonPayInvoice: Navigator.pushNamed( diff --git a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart index 64125b1457..0ae750cf99 100644 --- a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart +++ b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart @@ -45,7 +45,7 @@ class PresentReceiveOptionPicker extends StatelessWidget { fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color), ), Observer( - builder: (_) => Text(describeOption(receiveOptionViewModel.selectedReceiveOption), + builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(), style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w500, color: color))) ], ), @@ -101,7 +101,7 @@ class PresentReceiveOptionPicker extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(describeOption(option), + Text(option.toString(), textAlign: TextAlign.left, style: textSmall( color: Theme.of(context) diff --git a/lib/src/screens/subaddress/address_edit_or_create_page.dart b/lib/src/screens/subaddress/address_edit_or_create_page.dart index e067c78d0f..ed6369a736 100644 --- a/lib/src/screens/subaddress/address_edit_or_create_page.dart +++ b/lib/src/screens/subaddress/address_edit_or_create_page.dart @@ -13,8 +13,7 @@ class AddressEditOrCreatePage extends BasePage { : _formKey = GlobalKey(), _labelController = TextEditingController(), super() { - _labelController.addListener( - () => addressEditOrCreateViewModel.label = _labelController.text); + _labelController.addListener(() => addressEditOrCreateViewModel.label = _labelController.text); _labelController.text = addressEditOrCreateViewModel.label; } @@ -55,10 +54,8 @@ class AddressEditOrCreatePage extends BasePage { : S.of(context).new_subaddress_create, color: Theme.of(context).primaryColor, textColor: Colors.white, - isLoading: - addressEditOrCreateViewModel.state is AddressIsSaving, - isDisabled: - addressEditOrCreateViewModel.label?.isEmpty ?? true, + isLoading: addressEditOrCreateViewModel.state is AddressIsSaving, + isDisabled: addressEditOrCreateViewModel.label?.isEmpty ?? true, ), ) ], @@ -70,14 +67,13 @@ class AddressEditOrCreatePage extends BasePage { if (_isEffectsInstalled) { return; } - reaction((_) => addressEditOrCreateViewModel.state, - (AddressEditOrCreateState state) { - if (state is AddressSavedSuccessfully) { - WidgetsBinding.instance - .addPostFrameCallback((_) => Navigator.of(context).pop()); - } - }); + reaction((_) => addressEditOrCreateViewModel.state, (AddressEditOrCreateState state) { + if (state is AddressSavedSuccessfully) { + WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.of(context).pop()); + } + }); _isEffectsInstalled = true; } -} \ No newline at end of file +} + diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index db932ff333..c214fceccb 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -42,7 +42,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:eth_sig_util/util/utils.dart'; import 'package:flutter/services.dart'; import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/entities/provider_types.dart'; +import 'package:cake_wallet/bitcoin/bitcoin.dart'; part 'dashboard_view_model.g.dart'; @@ -277,12 +277,10 @@ abstract class DashboardViewModelBase with Store { @observable WalletBase, TransactionInfo> wallet; - bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven; - // bool get hasRescan => - // (wallet.type == WalletType.bitcoin && - // wallet.walletAddresses.addressPageType == bitcoin.AddressType.p2sp) || - // wallet.type == WalletType.monero || - // wallet.type == WalletType.haven; + bool get hasRescan => + (wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet)) || + wallet.type == WalletType.monero || + wallet.type == WalletType.haven; final KeyService keyService; @@ -344,15 +342,13 @@ abstract class DashboardViewModelBase with Store { bool hasExchangeAction; @computed - bool get isEnabledBuyAction => - !settingsStore.disableBuy && availableBuyProviders.isNotEmpty; + bool get isEnabledBuyAction => !settingsStore.disableBuy && availableBuyProviders.isNotEmpty; @observable bool hasBuyAction; @computed - bool get isEnabledSellAction => - !settingsStore.disableSell && availableSellProviders.isNotEmpty; + bool get isEnabledSellAction => !settingsStore.disableSell && availableSellProviders.isNotEmpty; @observable bool hasSellAction; @@ -477,7 +473,8 @@ abstract class DashboardViewModelBase with Store { Future> checkAffectedWallets() async { // await load file - final vulnerableSeedsString = await rootBundle.loadString('assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt'); + final vulnerableSeedsString = await rootBundle + .loadString('assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt'); final vulnerableSeeds = vulnerableSeedsString.split("\n"); final walletInfoSource = await CakeHive.openBox(WalletInfo.boxName); diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 817b37d7a0..5e843ad789 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -410,8 +410,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo wallet.type == WalletType.bitcoinCash; @computed - bool get isAutoGenerateSubaddressEnabled => - _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled; + bool get isAutoGenerateSubaddressEnabled => wallet.type == WalletType.bitcoin + ? !bitcoin!.hasSelectedSilentPayments(wallet) + : _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled; List _baseItems; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index af92cead41..02024fe1a9 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "موضوع البيتكوين الظلام", "bitcoin_light_theme": "موضوع البيتكوين الخفيفة", "bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.", + "block_remaining": "1 كتلة متبقية", "Blocks_remaining": "بلوك متبقي ${status}", "bright_theme": "مشرق", "buy": "اشتري", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 9f46ac2f49..807592f913 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Тъмна тема за биткойн", "bitcoin_light_theme": "Лека биткойн тема", "bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.", + "block_remaining": "1 блок останал", "Blocks_remaining": "${status} оставащи блока", "bright_theme": "Ярко", "buy": "Купуване", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index f47c7acb3a..74d7298da6 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Tmavé téma bitcoinů", "bitcoin_light_theme": "Světlé téma bitcoinů", "bitcoin_payments_require_1_confirmation": "U plateb Bitcoinem je vyžadováno alespoň 1 potvrzení, což může trvat 20 minut i déle. Děkujeme za vaši trpělivost! Až bude platba potvrzena, budete informováni e-mailem.", + "block_remaining": "1 blok zbývající", "Blocks_remaining": "Zbývá ${status} bloků", "bright_theme": "Jasný", "buy": "Koupit", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 3381120228..d71f68fd4c 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Dunkles Bitcoin-Thema", "bitcoin_light_theme": "Bitcoin Light-Thema", "bitcoin_payments_require_1_confirmation": "Bitcoin-Zahlungen erfordern 1 Bestätigung, was 20 Minuten oder länger dauern kann. Danke für Ihre Geduld! Sie erhalten eine E-Mail, wenn die Zahlung bestätigt ist.", + "block_remaining": "1 Block verbleibend", "Blocks_remaining": "${status} verbleibende Blöcke", "bright_theme": "Strahlend hell", "buy": "Kaufen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 86dc6518d5..d943ed4cd8 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Dark Theme", "bitcoin_light_theme": "Bitcoin Light Theme", "bitcoin_payments_require_1_confirmation": "Bitcoin payments require 1 confirmation, which can take 20 minutes or longer. Thanks for your patience! You will be emailed when the payment is confirmed.", + "block_remaining": "1 Block Remaining", "Blocks_remaining": "${status} Blocks Remaining", "bright_theme": "Bright", "buy": "Buy", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 09ad9948b5..9cea08be43 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Tema oscuro de Bitcoin", "bitcoin_light_theme": "Tema de la luz de Bitcoin", "bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por su paciencia! Se le enviará un correo electrónico cuando se confirme el pago.", + "block_remaining": "1 bloqueo restante", "Blocks_remaining": "${status} Bloques restantes", "bright_theme": "Brillante", "buy": "Comprar", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index e552dfafa8..ac2d944dbb 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Thème sombre Bitcoin", "bitcoin_light_theme": "Thème léger Bitcoin", "bitcoin_payments_require_1_confirmation": "Les paiements Bitcoin nécessitent 1 confirmation, ce qui peut prendre 20 minutes ou plus. Merci pour votre patience ! Vous serez averti par e-mail lorsque le paiement sera confirmé.", + "block_remaining": "1 bloc restant", "Blocks_remaining": "Blocs Restants : ${status}", "bright_theme": "Vif", "buy": "Acheter", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 40f6b28575..557e6951ed 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Dark Jigo", "bitcoin_light_theme": "Jigon Hasken Bitcoin", "bitcoin_payments_require_1_confirmation": "Akwatin Bitcoin na buɗe 1 sambumbu, da yake za ta samu mintuna 20 ko yawa. Ina kira ga sabuwar lafiya! Zaka sanarwa ta email lokacin da aka samu akwatin samun lambar waya.", + "block_remaining": "1 toshe ragowar", "Blocks_remaining": "${status} Katanga ya rage", "bright_theme": "Mai haske", "buy": "Sayi", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index d35a5f7168..12383e2fb3 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "बिटकॉइन डार्क थीम", "bitcoin_light_theme": "बिटकॉइन लाइट थीम", "bitcoin_payments_require_1_confirmation": "बिटकॉइन भुगतान के लिए 1 पुष्टिकरण की आवश्यकता होती है, जिसमें 20 मिनट या अधिक समय लग सकता है। आपके धैर्य के लिए धन्यवाद! भुगतान की पुष्टि होने पर आपको ईमेल किया जाएगा।", + "block_remaining": "1 ब्लॉक शेष", "Blocks_remaining": "${status} शेष रहते हैं", "bright_theme": "उज्ज्वल", "buy": "खरीदें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index c41310441a..ac351b03ed 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Tamna tema", "bitcoin_light_theme": "Bitcoin Light Theme", "bitcoin_payments_require_1_confirmation": "Bitcoin plaćanja zahtijevaju 1 potvrdu, što može potrajati 20 minuta ili dulje. Hvala na Vašem strpljenju! Dobit ćete e-poruku kada plaćanje bude potvrđeno.", + "block_remaining": "Preostalo 1 blok", "Blocks_remaining": "${status} preostalih blokova", "bright_theme": "Jarka", "buy": "Kupi", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index a9b9b50b3d..2756fcca06 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Tema Gelap Bitcoin", "bitcoin_light_theme": "Tema Cahaya Bitcoin", "bitcoin_payments_require_1_confirmation": "Pembayaran Bitcoin memerlukan 1 konfirmasi, yang bisa memakan waktu 20 menit atau lebih. Terima kasih atas kesabaran Anda! Anda akan diemail saat pembayaran dikonfirmasi.", + "block_remaining": "1 blok tersisa", "Blocks_remaining": "${status} Blok Tersisa", "bright_theme": "Cerah", "buy": "Beli", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 9682e3d7fc..83597d4c91 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Tema oscuro di Bitcoin", "bitcoin_light_theme": "Tema luce Bitcoin", "bitcoin_payments_require_1_confirmation": "I pagamenti in bitcoin richiedono 1 conferma, che può richiedere 20 minuti o più. Grazie per la vostra pazienza! Riceverai un'e-mail quando il pagamento sarà confermato.", + "block_remaining": "1 blocco rimanente", "Blocks_remaining": "${status} Blocchi Rimanenti", "bright_theme": "Colorato", "buy": "Comprare", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 0f4513eeed..eb74091a97 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "ビットコインダークテーマ", "bitcoin_light_theme": "ビットコインライトテーマ", "bitcoin_payments_require_1_confirmation": "ビットコインの支払いには 1 回の確認が必要で、これには 20 分以上かかる場合があります。お待ち頂きまして、ありがとうございます!支払いが確認されると、メールが送信されます。", + "block_remaining": "残り1ブロック", "Blocks_remaining": "${status} 残りのブロック", "bright_theme": "明るい", "buy": "購入", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 2dd80cf5c7..9a8bf48838 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "비트코인 다크 테마", "bitcoin_light_theme": "비트코인 라이트 테마", "bitcoin_payments_require_1_confirmation": "비트코인 결제는 1번의 확인이 필요하며 20분 이상이 소요될 수 있습니다. 기다려 주셔서 감사합니다! 결제가 확인되면 이메일이 전송됩니다.", + "block_remaining": "남은 블록 1 개", "Blocks_remaining": "${status} 남은 블록", "bright_theme": "선명한", "buy": "구입", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 1bc338dcb7..711c3c8841 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Dark Theme", "bitcoin_light_theme": "Bitcoin Light အပြင်အဆင်", "bitcoin_payments_require_1_confirmation": "Bitcoin ငွေပေးချေမှုများသည် မိနစ် 20 သို့မဟုတ် ထို့ထက်ပိုကြာနိုင်သည် 1 အတည်ပြုချက် လိုအပ်သည်။ မင်းရဲ့စိတ်ရှည်မှုအတွက် ကျေးဇူးတင်ပါတယ်။ ငွေပေးချေမှုကို အတည်ပြုပြီးသောအခါ သင့်ထံ အီးမေးလ်ပို့ပါမည်။", + "block_remaining": "ကျန်ရှိနေသေးသော block", "Blocks_remaining": "${status} ဘလောက်များ ကျန်နေပါသည်။", "bright_theme": "တောက်ပ", "buy": "ဝယ်ပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 56175b636e..7527eb10fa 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin donker thema", "bitcoin_light_theme": "Bitcoin Light-thema", "bitcoin_payments_require_1_confirmation": "Bitcoin-betalingen vereisen 1 bevestiging, wat 20 minuten of langer kan duren. Dank voor uw geduld! U ontvangt een e-mail wanneer de betaling is bevestigd.", + "block_remaining": "1 blok resterend", "Blocks_remaining": "${status} Resterende blokken", "bright_theme": "Helder", "buy": "Kopen", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index cbd1a71718..e93e56f743 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Ciemny motyw Bitcoina", "bitcoin_light_theme": "Lekki motyw Bitcoin", "bitcoin_payments_require_1_confirmation": "Płatności Bitcoin wymagają 1 potwierdzenia, co może zająć 20 minut lub dłużej. Dziękuję za cierpliwość! Otrzymasz wiadomość e-mail, gdy płatność zostanie potwierdzona.", + "block_remaining": "1 blok pozostałym", "Blocks_remaining": "Pozostało ${status} bloków", "bright_theme": "Biały", "buy": "Kup", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 7520f9327e..a7c1ff7e53 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Tema escuro Bitcoin", "bitcoin_light_theme": "Tema claro de bitcoin", "bitcoin_payments_require_1_confirmation": "Os pagamentos em Bitcoin exigem 1 confirmação, o que pode levar 20 minutos ou mais. Obrigado pela sua paciência! Você receberá um e-mail quando o pagamento for confirmado.", + "block_remaining": "1 bloco restante", "Blocks_remaining": "${status} blocos restantes", "bright_theme": "Brilhante", "buy": "Comprar", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index ee31e003a7..d339d2670f 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Биткойн Темная тема", "bitcoin_light_theme": "Легкая биткойн-тема", "bitcoin_payments_require_1_confirmation": "Биткойн-платежи требуют 1 подтверждения, что может занять 20 минут или дольше. Спасибо тебе за твое терпение! Вы получите электронное письмо, когда платеж будет подтвержден.", + "block_remaining": "1 Блок остался", "Blocks_remaining": "${status} Осталось блоков", "bright_theme": "Яркая", "buy": "Купить", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 3fddae1701..a4bb58d7de 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "ธีมมืด Bitcoin", "bitcoin_light_theme": "ธีมแสง Bitcoin", "bitcoin_payments_require_1_confirmation": "การชำระเงินด้วย Bitcoin ต้องการการยืนยัน 1 ครั้ง ซึ่งอาจใช้เวลา 20 นาทีหรือนานกว่านั้น ขอบคุณสำหรับความอดทนของคุณ! คุณจะได้รับอีเมลเมื่อการชำระเงินได้รับการยืนยัน", + "block_remaining": "เหลือ 1 บล็อก", "Blocks_remaining": "${status} บล็อกที่เหลืออยู่", "bright_theme": "สดใส", "buy": "ซื้อ", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 6dc6d793a9..2e9520dce5 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Madilim na Tema", "bitcoin_light_theme": "Tema ng ilaw ng bitcoin", "bitcoin_payments_require_1_confirmation": "Ang mga pagbabayad sa Bitcoin ay nangangailangan ng 1 kumpirmasyon, na maaaring tumagal ng 20 minuto o mas mahaba. Salamat sa iyong pasensya! Mag -email ka kapag nakumpirma ang pagbabayad.", + "block_remaining": "1 bloke ang natitira", "Blocks_remaining": "Ang natitirang ${status} ay natitira", "bright_theme": "Maliwanag", "buy": "Bilhin", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 585ef495a5..75148b2cab 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Karanlık Teması", "bitcoin_light_theme": "Bitcoin Hafif Tema", "bitcoin_payments_require_1_confirmation": "Bitcoin ödemeleri, 20 dakika veya daha uzun sürebilen 1 onay gerektirir. Sabrınız için teşekkürler! Ödeme onaylandığında e-posta ile bilgilendirileceksiniz.", + "block_remaining": "Kalan 1 blok", "Blocks_remaining": "${status} Blok Kaldı", "bright_theme": "Parlak", "buy": "Alış", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index a22fa8fb17..a461d10a5d 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Темна тема Bitcoin", "bitcoin_light_theme": "Світла тема Bitcoin", "bitcoin_payments_require_1_confirmation": "Платежі Bitcoin потребують 1 підтвердження, яке може зайняти 20 хвилин або більше. Дякую за Ваше терпіння! Ви отримаєте електронний лист, коли платіж буде підтверджено.", + "block_remaining": "1 блок, що залишився", "Blocks_remaining": "${status} Залишилось блоків", "bright_theme": "Яскрава", "buy": "Купити", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 35e17feaca..7ba95a3c57 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "بٹ کوائن ڈارک تھیم", "bitcoin_light_theme": "بٹ کوائن لائٹ تھیم", "bitcoin_payments_require_1_confirmation": "بٹ کوائن کی ادائیگی میں 1 تصدیق کی ضرورت ہوتی ہے ، جس میں 20 منٹ یا اس سے زیادہ وقت لگ سکتا ہے۔ آپ کے صبر کا شکریہ! ادائیگی کی تصدیق ہونے پر آپ کو ای میل کیا جائے گا۔", + "block_remaining": "1 بلاک باقی", "Blocks_remaining": "${status} بلاکس باقی ہیں۔", "bright_theme": "روشن", "buy": "خریدنے", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 0f141ebdf2..13af654809 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Dark Akori", "bitcoin_light_theme": "Bitcoin Light Akori", "bitcoin_payments_require_1_confirmation": "Àwọn àránṣẹ́ Bitcoin nílò ìjẹ́rìísí kan. Ó lè lo ìṣéjú ogun tàbí ìṣéjú jù. A dúpẹ́ fún sùúrù yín! Ẹ máa gba ímeèlì t'ó bá jẹ́rìísí àránṣẹ́ náà.", + "block_remaining": "1 bulọọki to ku", "Blocks_remaining": "Àkójọpọ̀ ${status} kikù", "bright_theme": "Funfun", "buy": "Rà", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index ffea168a9f..9c82981346 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "比特币黑暗主题", "bitcoin_light_theme": "比特币浅色主题", "bitcoin_payments_require_1_confirmation": "比特币支付需要 1 次确认,这可能需要 20 分钟或更长时间。谢谢你的耐心!确认付款后,您将收到电子邮件。", + "block_remaining": "剩下1个块", "Blocks_remaining": "${status} 剩余的块", "bright_theme": "明亮", "buy": "购买", diff --git a/tool/configure.dart b/tool/configure.dart index 91ea71d1f8..bf89743efb 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -145,6 +145,7 @@ abstract class Bitcoin { Future setAddressType(Object wallet, dynamic option); BitcoinReceivePageOption getSelectedAddressType(Object wallet); + bool hasSelectedSilentPayments(Object wallet); List getBitcoinReceivePageOptions(); } """; From 3c881466521249ca332952e781ff8077e6b1bdc5 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 27 Feb 2024 11:40:56 -0300 Subject: [PATCH 003/242] feat: scanning and adding addresses working with getTweaks, add btc SP address type --- cw_bitcoin/lib/bitcoin_address_record.dart | 134 +++-- .../lib/bitcoin_receive_page_option.dart | 2 +- cw_bitcoin/lib/bitcoin_unspent.dart | 6 +- cw_bitcoin/lib/bitcoin_wallet.dart | 22 +- cw_bitcoin/lib/electrum.dart | 4 +- cw_bitcoin/lib/electrum_wallet.dart | 529 +++++++----------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 63 +-- cw_bitcoin/lib/electrum_wallet_snapshot.dart | 4 +- cw_bitcoin/pubspec.lock | 11 +- cw_bitcoin/pubspec.yaml | 2 +- cw_bitcoin_cash/pubspec.yaml | 2 +- cw_core/lib/sync_status.dart | 5 + lib/bitcoin/cw_bitcoin.dart | 6 +- lib/core/sync_status_title.dart | 4 + model_generator.sh | 1 + res/values/strings_ar.arb | 7 +- res/values/strings_bg.arb | 7 +- res/values/strings_cs.arb | 7 +- res/values/strings_de.arb | 7 +- res/values/strings_en.arb | 7 +- res/values/strings_es.arb | 7 +- res/values/strings_fr.arb | 7 +- res/values/strings_ha.arb | 7 +- res/values/strings_hi.arb | 7 +- res/values/strings_hr.arb | 7 +- res/values/strings_id.arb | 7 +- res/values/strings_it.arb | 7 +- res/values/strings_ja.arb | 7 +- res/values/strings_ko.arb | 7 +- res/values/strings_my.arb | 7 +- res/values/strings_nl.arb | 7 +- res/values/strings_pl.arb | 7 +- res/values/strings_pt.arb | 7 +- res/values/strings_ru.arb | 7 +- res/values/strings_th.arb | 7 +- res/values/strings_tl.arb | 7 +- res/values/strings_tr.arb | 7 +- res/values/strings_uk.arb | 7 +- res/values/strings_ur.arb | 7 +- res/values/strings_yo.arb | 7 +- res/values/strings_zh.arb | 7 +- tool/configure.dart | 2 +- 42 files changed, 494 insertions(+), 485 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 2c40ba34cd..afd7c34e16 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -4,8 +4,8 @@ import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/script_hash.dart' as sh; -class BitcoinAddressRecord { - BitcoinAddressRecord( +abstract class BaseBitcoinAddressRecord { + BaseBitcoinAddressRecord( this.address, { required this.index, this.isHidden = false, @@ -14,41 +14,14 @@ class BitcoinAddressRecord { String name = '', bool isUsed = false, required this.type, - String? scriptHash, required this.network, - this.silentPaymentTweak, }) : _txCount = txCount, _balance = balance, _name = name, - _isUsed = isUsed, - scriptHash = - scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null); - - factory BitcoinAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) { - final decoded = json.decode(jsonSource) as Map; - - return BitcoinAddressRecord( - decoded['address'] as String, - index: decoded['index'] as int, - isHidden: decoded['isHidden'] as bool? ?? false, - isUsed: decoded['isUsed'] as bool? ?? false, - txCount: decoded['txCount'] as int? ?? 0, - name: decoded['name'] as String? ?? '', - balance: decoded['balance'] as int? ?? 0, - type: decoded['type'] != null && decoded['type'] != '' - ? BitcoinAddressType.values - .firstWhere((type) => type.toString() == decoded['type'] as String) - : SegwitAddresType.p2wpkh, - scriptHash: decoded['scriptHash'] as String?, - network: (decoded['network'] as String?) == null - ? network - : BasedUtxoNetwork.fromName(decoded['network'] as String), - silentPaymentTweak: decoded['silentPaymentTweak'] as String?, - ); - } + _isUsed = isUsed; @override - bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address; + bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address; final String address; bool isHidden; @@ -57,9 +30,7 @@ class BitcoinAddressRecord { int _balance; String _name; bool _isUsed; - String? scriptHash; BasedUtxoNetwork? network; - final String? silentPaymentTweak; int get txCount => _txCount; @@ -76,18 +47,60 @@ class BitcoinAddressRecord { void setAsUsed() => _isUsed = true; void setNewName(String label) => _name = label; - @override int get hashCode => address.hashCode; String get cashAddr => bitbox.Address.toCashAddress(address); BitcoinAddressType type; + String toJSON(); +} + +class BitcoinAddressRecord extends BaseBitcoinAddressRecord { + BitcoinAddressRecord( + super.address, { + required super.index, + super.isHidden = false, + super.txCount = 0, + super.balance = 0, + super.name = '', + super.isUsed = false, + required super.type, + String? scriptHash, + required super.network, + }) : scriptHash = + scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null); + + factory BitcoinAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) { + final decoded = json.decode(jsonSource) as Map; + + return BitcoinAddressRecord( + decoded['address'] as String, + index: decoded['index'] as int, + isHidden: decoded['isHidden'] as bool? ?? false, + isUsed: decoded['isUsed'] as bool? ?? false, + txCount: decoded['txCount'] as int? ?? 0, + name: decoded['name'] as String? ?? '', + balance: decoded['balance'] as int? ?? 0, + type: decoded['type'] != null && decoded['type'] != '' + ? BitcoinAddressType.values + .firstWhere((type) => type.toString() == decoded['type'] as String) + : SegwitAddresType.p2wpkh, + scriptHash: decoded['scriptHash'] as String?, + network: (decoded['network'] as String?) == null + ? network + : BasedUtxoNetwork.fromName(decoded['network'] as String), + ); + } + + String? scriptHash; + String updateScriptHash(BasedUtxoNetwork network) { scriptHash = sh.scriptHash(address, network: network); return scriptHash!; } + @override String toJSON() => json.encode({ 'address': address, 'index': index, @@ -99,6 +112,59 @@ class BitcoinAddressRecord { 'type': type.toString(), 'scriptHash': scriptHash, 'network': network?.value, + }); +} + +class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { + BitcoinSilentPaymentAddressRecord( + super.address, { + required super.index, + super.isHidden = false, + super.txCount = 0, + super.balance = 0, + super.name = '', + super.isUsed = false, + required super.type, + required this.silentPaymentTweak, + required super.network, + }); + + factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource, + {BasedUtxoNetwork? network}) { + final decoded = json.decode(jsonSource) as Map; + + return BitcoinSilentPaymentAddressRecord( + decoded['address'] as String, + index: decoded['index'] as int, + isHidden: decoded['isHidden'] as bool? ?? false, + isUsed: decoded['isUsed'] as bool? ?? false, + txCount: decoded['txCount'] as int? ?? 0, + name: decoded['name'] as String? ?? '', + balance: decoded['balance'] as int? ?? 0, + type: decoded['type'] != null && decoded['type'] != '' + ? BitcoinAddressType.values + .firstWhere((type) => type.toString() == decoded['type'] as String) + : SegwitAddresType.p2wpkh, + network: (decoded['network'] as String?) == null + ? network + : BasedUtxoNetwork.fromName(decoded['network'] as String), + silentPaymentTweak: decoded['silentPaymentTweak'] as String, + ); + } + + final String silentPaymentTweak; + + @override + String toJSON() => json.encode({ + 'address': address, + 'index': index, + 'isHidden': isHidden, + 'isUsed': isUsed, + 'txCount': txCount, + 'name': name, + 'balance': balance, + 'type': type.toString(), + 'network': network?.value, 'silentPaymentTweak': silentPaymentTweak, }); } diff --git a/cw_bitcoin/lib/bitcoin_receive_page_option.dart b/cw_bitcoin/lib/bitcoin_receive_page_option.dart index b14753ffce..cd471ef282 100644 --- a/cw_bitcoin/lib/bitcoin_receive_page_option.dart +++ b/cw_bitcoin/lib/bitcoin_receive_page_option.dart @@ -19,12 +19,12 @@ class BitcoinReceivePageOption implements ReceivePageOption { } static const all = [ + BitcoinReceivePageOption.silent_payments, BitcoinReceivePageOption.p2wpkh, BitcoinReceivePageOption.p2sh, BitcoinReceivePageOption.p2tr, BitcoinReceivePageOption.p2wsh, BitcoinReceivePageOption.p2pkh, - BitcoinReceivePageOption.silent_payments, ]; BitcoinAddressType toType() { diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index 131f47ab65..ce3c0da166 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -3,12 +3,12 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_core/unspent_transaction_output.dart'; class BitcoinUnspent extends Unspent { - BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout, + BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout, {this.silentPaymentTweak, this.type}) : bitcoinAddressRecord = addressRecord, super(addressRecord.address, hash, value, vout, null); - factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map json) => + factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord address, Map json) => BitcoinUnspent( address, json['tx_hash'] as String, @@ -32,7 +32,7 @@ class BitcoinUnspent extends Unspent { return json; } - final BitcoinAddressRecord bitcoinAddressRecord; + final BaseBitcoinAddressRecord bitcoinAddressRecord; String? silentPaymentTweak; BitcoinAddressType? type; } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index dfd7c8fe54..f24142493e 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -30,7 +30,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { ElectrumBalance? initialBalance, Map? initialRegularAddressIndex, Map? initialChangeAddressIndex, - List? initialSilentAddresses, + List? initialSilentAddresses, int initialSilentAddressIndex = 0, SilentPaymentOwner? silentAddress, }) : super( @@ -59,9 +59,19 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"), network: networkParam ?? network, ); + hasSilentPaymentsScanning = addressPageType == SilentPaymentsAddresType.p2sp.toString(); + autorun((_) { this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; }); + + reaction((_) => walletAddresses.addressPageType, (BitcoinAddressType addressPageType) { + final prev = hasSilentPaymentsScanning; + hasSilentPaymentsScanning = addressPageType == SilentPaymentsAddresType.p2sp; + if (prev != hasSilentPaymentsScanning) { + startSync(); + } + }); } static Future create({ @@ -72,7 +82,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { String? addressPageType, BasedUtxoNetwork? network, List? initialAddresses, - List? initialSilentAddresses, + List? initialSilentAddresses, ElectrumBalance? initialBalance, Map? initialRegularAddressIndex, Map? initialChangeAddressIndex, @@ -88,11 +98,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialSilentAddresses: initialSilentAddresses, initialSilentAddressIndex: initialSilentAddressIndex, silentAddress: await SilentPaymentOwner.fromPrivateKeys( - scanPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + b_scan: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( seedBytes, network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, ).derivePath(SCAN_PATH).privKey!), - spendPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + b_spend: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( seedBytes, network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, ).derivePath(SPEND_PATH).privKey!), @@ -125,11 +135,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialSilentAddresses: snp.silentAddresses, initialSilentAddressIndex: snp.silentAddressIndex, silentAddress: await SilentPaymentOwner.fromPrivateKeys( - scanPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + b_scan: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( seedBytes, network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, ).derivePath(SCAN_PATH).privKey!), - spendPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + b_spend: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( seedBytes, network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, ).derivePath(SPEND_PATH).privKey!), diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 27a43ddcb0..236dddaa26 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -279,8 +279,8 @@ class ElectrumClient { Future> getHeader({required int height}) async => await call(method: 'blockchain.block.get_header', params: [height]) as Map; - Future> getTweaks({required int height}) async => - await call(method: 'blockchain.block.tweaks', params: [height]) as Map; + Future> getTweaks({required int height}) async => + await callWithTimeout(method: 'blockchain.block.tweaks', params: [height]) as List; Future estimatefee({required int p}) => call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) { diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index f875a1b1f0..bd63c40972 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -35,6 +35,7 @@ import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:hex/hex.dart'; import 'package:hive/hive.dart'; @@ -116,7 +117,7 @@ abstract class ElectrumWalletBase @observable SyncStatus syncStatus; - List get scriptHashes => walletAddresses.addressesByReceiveType + List get scriptHashes => walletAddresses.allAddresses .map((addr) => scriptHash(addr.address, network: network)) .toList(); @@ -136,6 +137,11 @@ abstract class ElectrumWalletBase @override bool? isTestnet; + @observable + bool hasSilentPaymentsScanning = false; + @observable + bool nodeSupportsSilentPayments = true; + @override BitcoinWalletKeys get keys => BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); @@ -186,6 +192,11 @@ abstract class ElectrumWalletBase )); await for (var message in receivePort) { + if (message is bool) { + nodeSupportsSilentPayments = message; + syncStatus = UnsupportedSyncStatus(); + } + if (message is BitcoinUnspent) { if (!unspentCoins.any((utx) => utx.hash.contains(message.hash) && @@ -225,11 +236,18 @@ abstract class ElectrumWalletBase @override Future startSync() async { try { - await _setInitialHeight(); - } catch (_) {} + if (hasSilentPaymentsScanning) { + try { + await _setInitialHeight(); + } catch (_) {} - try { - rescan(height: walletInfo.restoreHeight); + final currentChainTip = await electrumClient.getCurrentBlockChainTip(); + if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { + _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); + } + } else { + syncStatus = AttemptingSyncStatus(); + } await updateTransactions(); _subscribeForUpdates(); @@ -240,7 +258,9 @@ abstract class ElectrumWalletBase Timer.periodic( const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates()); + // if (!hasSilentPaymentsScanning) { syncStatus = SyncedSyncStatus(); + // } } catch (e, stacktrace) { print(stacktrace); print(e.toString()); @@ -260,12 +280,6 @@ abstract class ElectrumWalletBase } }; syncStatus = ConnectedSyncStatus(); - - final currentChainTip = await electrumClient.getCurrentBlockChainTip(); - - if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { - _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); - } } catch (e) { print(e.toString()); syncStatus = FailedSyncStatus(); @@ -278,13 +292,18 @@ abstract class ElectrumWalletBase List outputAddresses, List outputs, BitcoinTransactionCredentials transactionCredentials, - {int? inputsCount}) async { + {int? inputsCount, + bool? hasSilentPayment}) async { final utxos = []; List privateKeys = []; var leftAmount = credentialsAmount; var allInputsAmount = 0; + List vinOutpoints = []; + List inputPrivKeyInfos = []; + List inputPubKeys = []; + for (int i = 0; i < unspentCoins.length; i++) { final utx = unspentCoins[i]; @@ -292,20 +311,6 @@ abstract class ElectrumWalletBase allInputsAmount += utx.value; leftAmount = leftAmount - utx.value; - if (utx.bitcoinAddressRecord.silentPaymentTweak != null) { - // final d = ECPrivate.fromHex(walletAddresses.primarySilentAddress!.spendPrivkey.toHex()) - // .tweakAdd(utx.bitcoinAddressRecord.silentPaymentTweak!)!; - - // inputPrivKeys.add(bitcoin.PrivateKeyInfo(d, true)); - // address = bitcoin.P2trAddress(address: utx.address, networkType: networkType); - // keyPairs.add(bitcoin.ECPair.fromPrivateKey(d.toCompressedHex().fromHex, - // compressed: true, network: networkType)); - // scriptType = bitcoin.AddressType.p2tr; - // script = bitcoin.P2trAddress(pubkey: d.publicKey.toHex(), networkType: networkType) - // .scriptPubkey - // .toBytes(); - } - final address = _addressTypeFromStr(utx.address, network); final privkey = generateECPrivate( hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, @@ -313,6 +318,9 @@ abstract class ElectrumWalletBase network: network); privateKeys.add(privkey); + inputPrivKeyInfos.add(ECPrivateInfo(privkey, address.type == SegwitAddresType.p2tr)); + inputPubKeys.add(privkey.getPublic()); + vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); utxos.add( UtxoWithAddress( @@ -334,125 +342,60 @@ abstract class ElectrumWalletBase } } - if (inputs.isEmpty) { + if (utxos.isEmpty) { throw BitcoinTransactionNoInputsException(); } - final allAmountFee = transactionCredentials.feeRate != null - ? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length) - : feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length); - - final allAmount = allInputsAmount - allAmountFee; - - var credentialsAmount = 0; - var amount = 0; - var fee = 0; - - if (hasMultiDestination) { - if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!); - - if (allAmount - credentialsAmount < minAmount) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - amount = credentialsAmount; - - if (transactionCredentials.feeRate != null) { - fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount, - outputsCount: outputs.length + 1); - } else { - fee = calculateEstimatedFee(transactionCredentials.priority, amount, - outputsCount: outputs.length + 1); - } - } else { - final output = outputs.first; - credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0; - - if (credentialsAmount > allAmount) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - amount = output.sendAll || allAmount - credentialsAmount < minAmount - ? allAmount - : credentialsAmount; + var changeValue = allInputsAmount - credentialsAmount; - if (output.sendAll || amount == allAmount) { - fee = allAmountFee; - } else if (transactionCredentials.feeRate != null) { - fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount); - } else { - fee = calculateEstimatedFee(transactionCredentials.priority, amount); + if (!sendAll) { + if (changeValue > 0) { + final changeAddress = await walletAddresses.getChangeAddress(); + final address = _addressTypeFromStr(changeAddress, network); + outputAddresses.add(address); + outputs.add(BitcoinOutput(address: address, value: BigInt.from(changeValue))); } } - if (fee == 0) { - throw BitcoinTransactionWrongBalanceException(currency); - } + if (hasSilentPayment == true) { + List silentPaymentDestinations = []; - final totalAmount = amount + fee; + for (final out in outputs) { + final address = out.address; + final amount = out.value; - if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) { - throw BitcoinTransactionWrongBalanceException(currency); - } + if (address is SilentPaymentAddress) { + final silentPaymentDestination = + SilentPaymentDestination.fromAddress(address.toAddress(network), amount.toInt()); + silentPaymentDestinations.add(silentPaymentDestination); + } + } - final txb = bitcoin.TransactionBuilder(network: networkType); - final changeAddress = await walletAddresses.getChangeAddress(); - var leftAmount = totalAmount; - var totalInputAmount = 0; + final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); + final sendingOutputs = spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations); - inputs.clear(); + var outputsAdded = []; - for (final utx in unspentCoins) { - if (utx.isSending) { - leftAmount = leftAmount - utx.value; - - final address = _addressTypeFromStr(utx.address, network); - final privkey = generateECPrivate( - hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, - index: utx.bitcoinAddressRecord.index, - network: network); + for (var i = 0; i < outputs.length; i++) { + final out = outputs[i]; - privateKeys.add(privkey); + final silentOutputs = sendingOutputs[out.address.toAddress(network)]; + if (silentOutputs != null) { + final silentOutput = + silentOutputs.firstWhereOrNull((element) => !outputsAdded.contains(element)); - utxos.add( - UtxoWithAddress( - utxo: BitcoinUtxo( - txHash: utx.hash, - value: BigInt.from(utx.value), - vout: utx.vout, - scriptType: _getScriptType(address), - ), - ownerDetails: - UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: address), - ), - ); + if (silentOutput != null) { + outputs[i] = BitcoinOutput( + address: silentOutput.address, + value: BigInt.from(silentOutput.amount), + ); - bool amountIsAcquired = !sendAll && leftAmount <= 0; - if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) { - break; + outputsAdded.add(silentOutput); + } } } } - if (utxos.isEmpty) { - throw BitcoinTransactionNoInputsException(); - } - - var changeValue = allInputsAmount - credentialsAmount; - - if (!sendAll) { - if (changeValue > 0) { - final changeAddress = await walletAddresses.getChangeAddress(); - final address = _addressTypeFromStr(changeAddress, network); - outputAddresses.add(address); - outputs.add(BitcoinOutput(address: address, value: BigInt.from(changeValue))); - } - } - final estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize( utxos: utxos, outputs: outputs, network: network); @@ -497,25 +440,31 @@ abstract class ElectrumWalletBase return _estimateTxFeeAndInputsToUse( credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials, - inputsCount: utxos.length + 1); + inputsCount: utxos.length + 1, hasSilentPayment: hasSilentPayment); } } - if (SilentPaymentAddress.regex.hasMatch(outputAddress)) { - // final outpointsHash = SilentPayment.hashOutpoints(outpoints); - // final generatedOutputs = SilentPayment.generateMultipleRecipientPubkeys(inputPrivKeys, - // outpointsHash, SilentPaymentDestination.fromAddress(outputAddress, outputAmount!)); - - // generatedOutputs.forEach((recipientSilentAddress, generatedOutput) { - // generatedOutput.forEach((output) { - // outputs.add(BitcoinOutputDetails( - // address: P2trAddress( - // program: ECPublic.fromHex(output.$1.toHex()).toTapPoint(), - // networkType: networkType), - // value: BigInt.from(output.$2), - // )); - // }); - // }); + return EstimatedTxResult(utxos: utxos, privateKeys: privateKeys, fee: fee, amount: amount); + } + + @override + Future createTransaction(Object credentials) async { + try { + final outputs = []; + final outputAddresses = []; + final transactionCredentials = credentials as BitcoinTransactionCredentials; + final hasMultiDestination = transactionCredentials.outputs.length > 1; + final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll; + + var credentialsAmount = 0; + bool hasSilentPayment = false; + + for (final out in transactionCredentials.outputs) { + final outputAddress = out.isParsedAddress ? out.extractedAddress! : out.address; + final address = _addressTypeFromStr(outputAddress, network); + + if (address is SilentPaymentAddress) { + hasSilentPayment = true; } outputAddresses.add(address); @@ -542,7 +491,8 @@ abstract class ElectrumWalletBase } final estimatedTx = await _estimateTxFeeAndInputsToUse( - credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials); + credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials, + hasSilentPayment: hasSilentPayment); final txb = BitcoinTransactionBuilder( utxos: estimatedTx.utxos, @@ -749,7 +699,7 @@ abstract class ElectrumWalletBase final coinInfoList = unspentCoinsInfo.values.where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash) && - element.address.contains(coin.address)); + element.vout == coin.vout); if (coinInfoList.isNotEmpty) { final coinInfo = coinInfoList.first; @@ -916,7 +866,7 @@ abstract class ElectrumWalletBase final Map historiesWithDetails = {}; final history = await electrumClient - .getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network)); + .getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network)!); if (history.isNotEmpty) { addressRecord.setAsUsed(); @@ -1037,7 +987,7 @@ abstract class ElectrumWalletBase element.bitcoinAddressRecord.address == info.address && element.value == info.value) { if (info.isFrozen) totalFrozen += element.value; - if (element.bitcoinAddressRecord.silentPaymentTweak != null) { + if (element.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { totalConfirmed += element.value; } } @@ -1208,18 +1158,29 @@ Future startRefresh(ScanData scanData) async { final electrumClient = scanData.electrumClient; if (!electrumClient.isConnected) { final node = scanData.node; + print(node); await electrumClient.connectToUri(Uri.parse(node)); } - final tweaks = await electrumClient.getTweaks(height: syncHeight); + + List? tweaks; + try { + tweaks = await electrumClient.getTweaks(height: syncHeight); + } catch (e) { + if (e is RequestFailedTimeoutException) { + return scanData.sendPort.send(false); + } + } + + if (tweaks == null) { + return scanData.sendPort.send(false); + } for (var i = 0; i < tweaks.length; i++) { try { // final txid = tweaks.keys.toList()[i]; - final details = tweaks.values.toList()[i]; - print(["details", details]); - final output_pubkeys = (details["output_pubkeys"] as List); - - // print(["Scanning tx:", txid]); + final details = tweaks[i] as Map; + final output_pubkeys = (details["output_pubkeys"] as List); + final tweak = details["tweak"].toString(); // TODO: if tx already scanned & stored skip // if (scanData.transactionHistoryIds.contains(txid)) { @@ -1228,12 +1189,14 @@ Future startRefresh(ScanData scanData) async { // continue; // } - final result = SilentPayment.scanTweak( + final spb = SilentPaymentBuilder(receiverTweak: tweak); + final result = spb.scanOutputs( scanData.primarySilentAddress.b_scan, scanData.primarySilentAddress.B_spend, - details["tweak"] as String, - output_pubkeys.map((e) => BytesUtils.fromHexString(e)).toList(), - labels: scanData.labels, + output_pubkeys + .map((p) => ECPublic.fromBytes(BytesUtils.fromHexString(p.toString()).sublist(2))) + .toList(), + precomputedLabels: scanData.labels, ); if (result.isEmpty) { @@ -1241,113 +1204,102 @@ Future startRefresh(ScanData scanData) async { continue; } - if (result.length > 1) { - print("MULTIPLE UNSPENT COINS FOUND!"); - } else { - print("UNSPENT COIN FOUND!"); - } + result.forEach((key, value) async { + final t_k = value[0]; + final address = + ECPublic.fromHex(key).toTaprootAddress(tweak: false).toAddress(scanData.network); + + final listUnspent = + await electrumClient.getListUnspentWithAddress(address, scanData.network); - // result.forEach((key, value) async { - // final outpoint = output_pubkeys[key]; - - // if (outpoint == null) { - // return; - // } - - // final tweak = value[0]; - // String? label; - // if (value.length > 1) label = value[1]; - - // final txInfo = ElectrumTransactionInfo( - // WalletType.bitcoin, - // id: txid, - // height: syncHeight, - // amount: outpoint.value!, - // fee: 0, - // direction: TransactionDirection.incoming, - // isPending: false, - // date: DateTime.fromMillisecondsSinceEpoch((blockJson["timestamp"] as int) * 1000), - // confirmations: currentChainTip - syncHeight, - // to: bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress( - // scanData.primarySilentAddress.scanPubkey, - // scanData.primarySilentAddress.spendPubkey, - // label != null ? label.fromHex : "0".fromHex, - // hrp: scanData.primarySilentAddress.hrp, - // version: scanData.primarySilentAddress.version) - // .toString(), - // unspent: null, - // ); - - // final status = json.decode((await http - // .get(Uri.parse("https://blockstream.info/testnet/api/tx/$txid/outspends"))) - // .body) as List; - - // bool spent = false; - // for (final s in status) { - // if ((s["spent"] as bool) == true) { - // spent = true; - - // scanData.sendPort.send({txid: txInfo}); - - // final sentTxId = s["txid"] as String; - // final sentTx = json.decode( - // (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$sentTxId"))) - // .body); - - // int amount = 0; - // for (final out in (sentTx["vout"] as List)) { - // amount += out["value"] as int; - // } - - // final height = s["status"]["block_height"] as int; - - // scanData.sendPort.send({ - // sentTxId: ElectrumTransactionInfo( - // WalletType.bitcoin, - // id: sentTxId, - // height: height, - // amount: amount, - // fee: 0, - // direction: TransactionDirection.outgoing, - // isPending: false, - // date: DateTime.fromMillisecondsSinceEpoch( - // (s["status"]["block_time"] as int) * 1000), - // confirmations: currentChainTip - height, - // ) - // }); - // } - // } - - // if (spent) { - // return; - // } - - // final unspent = BitcoinUnspent( - // BitcoinAddressRecord( - // bitcoin.P2trAddress(program: key, networkType: scanData.network).address, - // index: 0, - // isHidden: true, - // isUsed: true, - // silentAddressLabel: null, - // silentPaymentTweak: tweak, - // type: bitcoin.AddressType.p2tr, - // ), - // txid, - // outpoint.value!, - // outpoint.index, - // silentPaymentTweak: tweak, - // type: bitcoin.AddressType.p2tr, - // ); - - // // found utxo for tx, send unspent coin to main isolate - // scanData.sendPort.send(unspent); - - // // also send tx data for tx history - // txInfo.unspent = unspent; - // scanData.sendPort.send({txid: txInfo}); - // }); + BitcoinUnspent? info; + await Future.forEach>(listUnspent, (unspent) async { + try { + final addressRecord = BitcoinSilentPaymentAddressRecord( + address, + index: 0, + isHidden: true, + isUsed: true, + network: scanData.network, + silentPaymentTweak: t_k, + type: SegwitAddresType.p2tr, + ); + info = BitcoinUnspent.fromJSON(addressRecord, unspent); + } catch (_) {} + }); + + // final tweak = value[0]; + // String? label; + // if (value.length > 1) label = value[1]; + + final tx = info!; + final txInfo = ElectrumTransactionInfo( + WalletType.bitcoin, + id: tx.hash, + height: syncHeight, + amount: tx.value, + fee: 0, + direction: TransactionDirection.incoming, + isPending: false, + date: DateTime.now(), + confirmations: currentChainTip - syncHeight, + to: scanData.primarySilentAddress.toString(), + unspent: tx, + ); + + // final status = json.decode((await http + // .get(Uri.parse("https://blockstream.info/testnet/api/tx/$txid/outspends"))) + // .body) as List; + + // bool spent = false; + // for (final s in status) { + // if ((s["spent"] as bool) == true) { + // spent = true; + + // scanData.sendPort.send({txid: txInfo}); + + // final sentTxId = s["txid"] as String; + // final sentTx = json.decode( + // (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$sentTxId"))) + // .body); + + // int amount = 0; + // for (final out in (sentTx["vout"] as List)) { + // amount += out["value"] as int; + // } + + // final height = s["status"]["block_height"] as int; + + // scanData.sendPort.send({ + // sentTxId: ElectrumTransactionInfo( + // WalletType.bitcoin, + // id: sentTxId, + // height: height, + // amount: amount, + // fee: 0, + // direction: TransactionDirection.outgoing, + // isPending: false, + // date: DateTime.fromMillisecondsSinceEpoch( + // (s["status"]["block_time"] as int) * 1000), + // confirmations: currentChainTip - height, + // ) + // }); + // } + // } + + // if (spent) { + // return; + // } + + // found utxo for tx, send unspent coin to main isolate + // scanData.sendPort.send(txInfo); + + // also send tx data for tx history + scanData.sendPort.send({txInfo.id: txInfo}); + }); } catch (_) {} } + // break; // Finished scanning block, add 1 to height and continue to next block in loop syncHeight += 1; @@ -1383,6 +1335,8 @@ BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network) return P2wshAddress.fromAddress(address: address, network: network); } else if (P2trAddress.regex.hasMatch(address)) { return P2trAddress.fromAddress(address: address, network: network); + } else if (SilentPaymentAddress.regex.hasMatch(address)) { + return SilentPaymentAddress.fromAddress(address); } else { return P2wpkhAddress.fromAddress(address: address, network: network); } @@ -1397,59 +1351,8 @@ BitcoinAddressType _getScriptType(BitcoinBaseAddress type) { return SegwitAddresType.p2wsh; } else if (type is P2trAddress) { return SegwitAddresType.p2tr; - } else { - return SegwitAddresType.p2wpkh; - } -} - -class EstimateTxParams { - EstimateTxParams( - {required this.amount, - required this.feeRate, - required this.priority, - required this.outputsCount, - required this.size}); - - final int amount; - final int feeRate; - final TransactionPriority priority; - final int outputsCount; - final int size; -} - -class EstimatedTxResult { - EstimatedTxResult( - {required this.utxos, required this.privateKeys, required this.fee, required this.amount}); - - final List utxos; - final List privateKeys; - final int fee; - final int amount; -} - -BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network) { - if (P2pkhAddress.regex.hasMatch(address)) { - return P2pkhAddress.fromAddress(address: address, network: network); - } else if (P2shAddress.regex.hasMatch(address)) { - return P2shAddress.fromAddress(address: address, network: network); - } else if (P2wshAddress.regex.hasMatch(address)) { - return P2wshAddress.fromAddress(address: address, network: network); - } else if (P2trAddress.regex.hasMatch(address)) { - return P2trAddress.fromAddress(address: address, network: network); - } else { - return P2wpkhAddress.fromAddress(address: address, network: network); - } -} - -BitcoinAddressType _getScriptType(BitcoinBaseAddress type) { - if (type is P2pkhAddress) { - return P2pkhAddressType.p2pkh; - } else if (type is P2shAddress) { - return P2shAddressType.p2wpkhInP2sh; - } else if (type is P2wshAddress) { - return SegwitAddresType.p2wsh; - } else if (type is P2trAddress) { - return SegwitAddresType.p2tr; + } else if (type is SilentPaymentsAddresType) { + return SilentPaymentsAddresType.p2sp; } else { return SegwitAddresType.p2wpkh; } diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index bd6781ea80..ec2a27498c 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -1,6 +1,7 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitbox/bitbox.dart' as bitbox; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; @@ -28,7 +29,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { List? initialAddresses, Map? initialRegularAddressIndex, Map? initialChangeAddressIndex, - List? initialSilentAddresses, + List? initialSilentAddresses, int initialSilentAddressIndex = 0, SilentPaymentOwner? silentAddress, }) : _addresses = ObservableList.of((initialAddresses ?? []).toSet()), @@ -46,9 +47,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { _addressPageType = walletInfo.addressPageType != null ? BitcoinAddressType.fromValue(walletInfo.addressPageType!) : SegwitAddresType.p2wpkh, - silentAddresses = ObservableList.of((initialSilentAddresses ?? []) - .where((addressRecord) => addressRecord.silentPaymentTweak != null) - .toSet()), + silentAddresses = ObservableList.of( + (initialSilentAddresses ?? []).toSet()), currentSilentAddressIndex = initialSilentAddressIndex, super(walletInfo) { updateAddressesByMatch(); @@ -59,15 +59,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { static const gap = 20; static String toCashAddr(String address) => bitbox.Address.toCashAddress(address); - static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address); final ObservableList _addresses; - // Matched by addressPageType - late ObservableList addressesByReceiveType; + late ObservableList addressesByReceiveType; final ObservableList receiveAddresses; final ObservableList changeAddresses; - final ObservableList silentAddresses; + final ObservableList silentAddresses; final BasedUtxoNetwork network; final bitcoin.HDWallet mainHd; final bitcoin.HDWallet sideHd; @@ -101,23 +99,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch); - if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) || - typeMatchingReceiveAddresses.isEmpty) { - receiveAddress = generateNewAddress().address; - } else { - final previousAddressMatchesType = - previousAddressRecord != null && previousAddressRecord!.type == addressPageType; - - if (previousAddressMatchesType && - typeMatchingReceiveAddresses.first.address != addressesByReceiveType.first.address) { - receiveAddress = previousAddressRecord!.address; - } else { - receiveAddress = typeMatchingReceiveAddresses.first.address; - } - final receiveAddress = receiveAddresses.first.address; - - final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch); - if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) || typeMatchingReceiveAddresses.isEmpty) { receiveAddress = generateNewAddress().address; @@ -239,38 +220,36 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } Map get labels { + final G = ECPublic.fromBytes(BigintUtils.toBytes(Curves.generatorSecp256k1.x, length: 32)); final labels = {}; for (int i = 0; i < silentAddresses.length; i++) { final silentAddressRecord = silentAddresses[i]; - final silentAddress = - SilentPaymentDestination.fromAddress(silentAddressRecord.address, 0).B_spend.toHex(); - - if (silentAddressRecord.silentPaymentTweak != null) - labels[silentAddress] = silentAddressRecord.silentPaymentTweak!; + final silentPaymentTweak = silentAddressRecord.silentPaymentTweak; + labels[G + .tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak))) + .toHex()] = silentPaymentTweak; } return labels; } - BitcoinAddressRecord generateNewAddress({String label = ''}) { + @action + BaseBitcoinAddressRecord generateNewAddress({String label = ''}) { if (addressPageType == SilentPaymentsAddresType.p2sp) { currentSilentAddressIndex += 1; - final tweak = BigInt.from(currentSilentAddressIndex); - - final address = BitcoinAddressRecord( - SilentPaymentAddress.createLabeledSilentPaymentAddress( - primarySilentAddress!.B_scan, primarySilentAddress!.B_spend, tweak, - hrp: primarySilentAddress!.hrp, version: primarySilentAddress!.version) - .toString(), + final address = BitcoinSilentPaymentAddressRecord( + primarySilentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(), index: currentSilentAddressIndex, isHidden: false, name: label, - silentPaymentTweak: tweak.toString(), + silentPaymentTweak: + BytesUtils.toHexString(primarySilentAddress!.generateLabel(currentSilentAddressIndex)), network: network, type: SilentPaymentsAddresType.p2sp, ); silentAddresses.add(address); + updateAddressesByMatch(); return address; } @@ -321,6 +300,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action void updateAddressesByMatch() { + if (addressPageType == SilentPaymentsAddresType.p2sp) { + addressesByReceiveType.clear(); + addressesByReceiveType.addAll(silentAddresses); + return; + } + addressesByReceiveType.clear(); addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList()); } diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index ceb603f9f3..9f71d0bd82 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -30,7 +30,7 @@ class ElectrumWalletSnapshot { String mnemonic; List addresses; - List silentAddresses; + List silentAddresses; ElectrumBalance balance; Map regularAddressIndex; Map changeAddressIndex; @@ -52,7 +52,7 @@ class ElectrumWalletSnapshot { final silentAddressesTmp = data['silent_addresses'] as List? ?? []; final silentAddresses = silentAddressesTmp .whereType() - .map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network)) + .map((addr) => BitcoinSilentPaymentAddressRecord.fromJSON(addr, network: network)) .toList(); final balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index aff28df6eb..c7750f1afb 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -80,7 +80,7 @@ packages: description: path: "." ref: cake-update-v2 - resolved-ref: e4686da77cace5400697de69f7885020297cb900 + resolved-ref: "43b80bcf0ef6e7224603a6b8874b61efec3c6a4c" url: "https://github.com/cake-tech/bitcoin_base.git" source: git version: "4.0.0" @@ -93,6 +93,15 @@ packages: url: "https://github.com/cake-tech/bitcoin_flutter.git" source: git version: "2.1.0" + blockchain_utils: + dependency: "direct main" + description: + path: "." + ref: cake-update-v1 + resolved-ref: "6a0b891db4d90c647ebf5fc3a9132e614c70e1c6" + url: "https://github.com/cake-tech/blockchain_utils" + source: git + version: "1.6.0" boolean_selector: dependency: transitive description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 13a0e26886..bfa6c72e60 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: ref: cake-update-v2 blockchain_utils: git: - url: https://github.com/rafael-xmr/blockchain_utils + url: https://github.com/cake-tech/blockchain_utils ref: cake-update-v1 dev_dependencies: diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index fd7ed5a0fc..a967681503 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -35,7 +35,7 @@ dependencies: ref: cake-update-v2 blockchain_utils: git: - url: https://github.com/rafael-xmr/blockchain_utils + url: https://github.com/cake-tech/blockchain_utils ref: cake-update-v1 dev_dependencies: diff --git a/cw_core/lib/sync_status.dart b/cw_core/lib/sync_status.dart index afddc7c7a7..f562e7ce32 100644 --- a/cw_core/lib/sync_status.dart +++ b/cw_core/lib/sync_status.dart @@ -58,6 +58,11 @@ class ConnectedSyncStatus extends SyncStatus { double progress() => 0.0; } +class UnsupportedSyncStatus extends SyncStatus { + @override + double progress() => 1.0; +} + class LostConnectionSyncStatus extends SyncStatus { @override double progress() => 1.0; diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index fb470a58f1..1ce79352cc 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -101,7 +101,7 @@ class CWBitcoin extends Bitcoin { List getAddresses(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.walletAddresses.addressesByReceiveType - .map((BitcoinAddressRecord addr) => addr.address) + .map((BaseBitcoinAddressRecord addr) => addr.address) .toList(); } @@ -110,7 +110,7 @@ class CWBitcoin extends Bitcoin { List getSubAddresses(Object wallet) { final electrumWallet = wallet as ElectrumWallet; return electrumWallet.walletAddresses.addressesByReceiveType - .map((BitcoinAddressRecord addr) => ElectrumSubAddress( + .map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress( id: addr.index, name: addr.name, address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address, @@ -195,7 +195,7 @@ class CWBitcoin extends Bitcoin { @override List getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all; - List getSilentAddresses(Object wallet) { + List getSilentAddresses(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.walletAddresses.silentAddresses; } diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index 226b97f508..23bfb0db28 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -36,5 +36,9 @@ String syncStatusTitle(SyncStatus syncStatus) { return S.current.sync_status_failed_connect; } + if (syncStatus is UnsupportedSyncStatus) { + return S.current.sync_status_unsupported; + } + return ''; } diff --git a/model_generator.sh b/model_generator.sh index fa1ea6fac0..062dcf0e2c 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -5,6 +5,7 @@ cd cw_bitcoin; flutter pub get; flutter packages pub run build_runner build --de cd cw_haven; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. cd cw_nano; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. cd cw_bitcoin_cash; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. +cd cw_solana; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. cd cw_polygon; flutter pub get; cd .. cd cw_ethereum; flutter pub get; cd .. flutter packages pub run build_runner build --delete-conflicting-outputs diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 02024fe1a9..835f1909ef 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "بدء المزامنة", "sync_status_syncronized": "متزامن", "sync_status_syncronizing": "يتم المزامنة", + "sync_status_unsupported": "عقدة غير مدعومة", "syncing_wallet_alert_content": "قد لا يكتمل رصيدك وقائمة المعاملات الخاصة بك حتى تظهر عبارة “SYNCHRONIZED“ في الأعلى. انقر / اضغط لمعرفة المزيد.", "syncing_wallet_alert_title": "محفظتك تتم مزامنتها", "template": "قالب", @@ -733,6 +734,7 @@ "view_key_private": "مفتاح العرض (خاص)", "view_key_public": "مفتاح العرض (عام)", "view_transaction_on": "عرض العملية على", + "waitFewSecondForTxUpdate": "ﺕﻼﻣﺎﻌﻤﻟﺍ ﻞﺠﺳ ﻲﻓ ﺔﻠﻣﺎﻌﻤﻟﺍ ﺲﻜﻌﻨﺗ ﻰﺘﺣ ﻥﺍﻮﺛ ﻊﻀﺒﻟ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ", "wallet_keys": "سييد المحفظة / المفاتيح", "wallet_list_create_new_wallet": "إنشاء محفظة جديدة", "wallet_list_edit_wallet": "تحرير المحفظة", @@ -783,6 +785,5 @@ "you_pay": "انت تدفع", "you_will_get": "حول الى", "you_will_send": "تحويل من", - "yy": "YY", - "waitFewSecondForTxUpdate": "ﺕﻼﻣﺎﻌﻤﻟﺍ ﻞﺠﺳ ﻲﻓ ﺔﻠﻣﺎﻌﻤﻟﺍ ﺲﻜﻌﻨﺗ ﻰﺘﺣ ﻥﺍﻮﺛ ﻊﻀﺒﻟ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 807592f913..db73a2e15a 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "ЗАПОЧВАНЕ НА СИНХРОНИЗАЦИЯ", "sync_status_syncronized": "СИНХРОНИЗИРАНО", "sync_status_syncronizing": "СИНХРОНИЗИРАНЕ", + "sync_status_unsupported": "Неподдържан възел", "syncing_wallet_alert_content": "Списъкът ви с баланс и транзакции може да не е пълен, докато в горната част не пише „СИНХРОНИЗИРАН“. Кликнете/докоснете, за да научите повече.", "syncing_wallet_alert_title": "Вашият портфейл се синхронизира", "template": "Шаблон", @@ -733,6 +734,7 @@ "view_key_private": "View key (таен)", "view_key_public": "View key (публичен)", "view_transaction_on": "Вижте транзакция на ", + "waitFewSecondForTxUpdate": "Моля, изчакайте няколко секунди, докато транзакцията се отрази в историята на транзакциите", "wallet_keys": "Seed/keys на портфейла", "wallet_list_create_new_wallet": "Създаване на нов портфейл", "wallet_list_edit_wallet": "Редактиране на портфейла", @@ -783,6 +785,5 @@ "you_pay": "Вие плащате", "you_will_get": "Обръщане в", "you_will_send": "Обръщане от", - "yy": "гг", - "waitFewSecondForTxUpdate": "Моля, изчакайте няколко секунди, докато транзакцията се отрази в историята на транзакциите" -} + "yy": "гг" +} \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 74d7298da6..0f2242b334 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "SPOUŠTĚNÍ SYNCHRONIZACE", "sync_status_syncronized": "SYNCHRONIZOVÁNO", "sync_status_syncronizing": "SYNCHRONIZUJI", + "sync_status_unsupported": "Nepodporovaný uzel", "syncing_wallet_alert_content": "Váš seznam zůstatků a transakcí nemusí být úplný, dokud nebude nahoře uvedeno „SYNCHRONIZOVANÉ“. Kliknutím/klepnutím se dozvíte více.", "syncing_wallet_alert_title": "Vaše peněženka se synchronizuje", "template": "Šablona", @@ -733,6 +734,7 @@ "view_key_private": "Klíč pro zobrazení (soukromý)", "view_key_public": "Klíč pro zobrazení (veřejný)", "view_transaction_on": "Zobrazit transakci na ", + "waitFewSecondForTxUpdate": "Počkejte několik sekund, než se transakce projeví v historii transakcí", "wallet_keys": "Seed/klíče peněženky", "wallet_list_create_new_wallet": "Vytvořit novou peněženku", "wallet_list_edit_wallet": "Upravit peněženku", @@ -783,6 +785,5 @@ "you_pay": "Zaplatíte", "you_will_get": "Směnit na", "you_will_send": "Směnit z", - "yy": "YY", - "waitFewSecondForTxUpdate": "Počkejte několik sekund, než se transakce projeví v historii transakcí" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index d71f68fd4c..d21d57a983 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "STARTE SYNCHRONISIERUNG", "sync_status_syncronized": "SYNCHRONISIERT", "sync_status_syncronizing": "SYNCHRONISIERE", + "sync_status_unsupported": "Nicht unterstützter Knoten", "syncing_wallet_alert_content": "Ihr Kontostand und Ihre Transaktionsliste sind möglicherweise erst vollständig, wenn oben „SYNCHRONISIERT“ steht. Klicken/tippen Sie, um mehr zu erfahren.", "syncing_wallet_alert_title": "Ihr Wallet wird synchronisiert", "template": "Vorlage", @@ -735,6 +736,7 @@ "view_key_private": "View Key (geheim)", "view_key_public": "View Key (öffentlich)", "view_transaction_on": "Anzeigen der Transaktion auf ", + "waitFewSecondForTxUpdate": "Bitte warten Sie einige Sekunden, bis die Transaktion im Transaktionsverlauf angezeigt wird", "waiting_payment_confirmation": "Warte auf Zahlungsbestätigung", "wallet_keys": "Wallet-Seed/-Schlüssel", "wallet_list_create_new_wallet": "Neue Wallet erstellen", @@ -786,6 +788,5 @@ "you_pay": "Sie bezahlen", "you_will_get": "Konvertieren zu", "you_will_send": "Konvertieren von", - "yy": "YY", - "waitFewSecondForTxUpdate": "Bitte warten Sie einige Sekunden, bis die Transaktion im Transaktionsverlauf angezeigt wird" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index d943ed4cd8..c3acebf6d1 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "STARTING SYNC", "sync_status_syncronized": "SYNCHRONIZED", "sync_status_syncronizing": "SYNCHRONIZING", + "sync_status_unsupported": "UNSUPPORTED NODE", "syncing_wallet_alert_content": "Your balance and transaction list may not be complete until it says “SYNCHRONIZED” at the top. Click/tap to learn more.", "syncing_wallet_alert_title": "Your wallet is syncing", "template": "Template", @@ -733,6 +734,7 @@ "view_key_private": "View key (private)", "view_key_public": "View key (public)", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "Kindly wait for a few seconds for transaction to reflect in transactions history", "wallet_keys": "Wallet seed/keys", "wallet_list_create_new_wallet": "Create New Wallet", "wallet_list_edit_wallet": "Edit wallet", @@ -783,6 +785,5 @@ "you_pay": "You Pay", "you_will_get": "Convert to", "you_will_send": "Convert from", - "yy": "YY", - "waitFewSecondForTxUpdate": "Kindly wait for a few seconds for transaction to reflect in transactions history" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 9cea08be43..d2fd93dfff 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "EMPEZANDO A SINCRONIZAR", "sync_status_syncronized": "SINCRONIZADO", "sync_status_syncronizing": "SINCRONIZANDO", + "sync_status_unsupported": "Nodo no compatible", "syncing_wallet_alert_content": "Es posible que su lista de saldo y transacciones no esté completa hasta que diga \"SINCRONIZADO\" en la parte superior. Haga clic/toque para obtener más información.", "syncing_wallet_alert_title": "Tu billetera se está sincronizando", "template": "Plantilla", @@ -734,6 +735,7 @@ "view_key_private": "View clave (privado)", "view_key_public": "View clave (público)", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "Espere unos segundos para que la transacción se refleje en el historial de transacciones.", "wallet_keys": "Billetera semilla/claves", "wallet_list_create_new_wallet": "Crear nueva billetera", "wallet_list_edit_wallet": "Editar billetera", @@ -784,6 +786,5 @@ "you_pay": "Tú pagas", "you_will_get": "Convertir a", "you_will_send": "Convertir de", - "yy": "YY", - "waitFewSecondForTxUpdate": "Espere unos segundos para que la transacción se refleje en el historial de transacciones." -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index ac2d944dbb..61f15497c8 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "DÉBUT DE SYNCHRO", "sync_status_syncronized": "SYNCHRONISÉ", "sync_status_syncronizing": "SYNCHRONISATION EN COURS", + "sync_status_unsupported": "Nœud non pris en charge", "syncing_wallet_alert_content": "Votre solde et votre liste de transactions peuvent ne pas être à jour tant que la mention « SYNCHRONISÉ » n'apparaît en haut de l'écran. Cliquez/appuyez pour en savoir plus.", "syncing_wallet_alert_title": "Votre portefeuille (wallet) est en cours de synchronisation", "template": "Modèle", @@ -733,6 +734,7 @@ "view_key_private": "Clef d'audit (view key) (privée)", "view_key_public": "Clef d'audit (view key) (publique)", "view_transaction_on": "Voir la Transaction sur ", + "waitFewSecondForTxUpdate": "Veuillez attendre quelques secondes pour que la transaction soit reflétée dans l'historique des transactions.", "wallet_keys": "Phrase secrète (seed)/Clefs du portefeuille (wallet)", "wallet_list_create_new_wallet": "Créer un Nouveau Portefeuille (Wallet)", "wallet_list_edit_wallet": "Modifier le portefeuille", @@ -783,6 +785,5 @@ "you_pay": "Vous payez", "you_will_get": "Convertir vers", "you_will_send": "Convertir depuis", - "yy": "AA", - "waitFewSecondForTxUpdate": "Veuillez attendre quelques secondes pour que la transaction soit reflétée dans l'historique des transactions." -} + "yy": "AA" +} \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 557e6951ed..7b71df8539 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "KWAFI", "sync_status_syncronized": "KYAU", "sync_status_syncronizing": "KWAFI", + "sync_status_unsupported": "Ba a Taimako ba", "syncing_wallet_alert_content": "Ma'aunin ku da lissafin ma'amala bazai cika ba har sai an ce \"SYNCHRONIZED\" a saman. Danna/matsa don ƙarin koyo.", "syncing_wallet_alert_title": "Walat ɗin ku yana aiki tare", "template": "Samfura", @@ -735,6 +736,7 @@ "view_key_private": "Duba maɓallin (maɓallin kalmar sirri)", "view_key_public": "Maɓallin Duba (maɓallin jama'a)", "view_transaction_on": "Dubo aikace-aikacen akan", + "waitFewSecondForTxUpdate": "Da fatan za a jira ƴan daƙiƙa don ciniki don yin tunani a tarihin ma'amala", "wallet_keys": "Iri/maɓalli na walat", "wallet_list_create_new_wallet": "Ƙirƙiri Sabon Wallet", "wallet_list_edit_wallet": "Gyara walat", @@ -785,6 +787,5 @@ "you_pay": "Ka Bayar", "you_will_get": "Maida zuwa", "you_will_send": "Maida daga", - "yy": "YY", - "waitFewSecondForTxUpdate": "Da fatan za a jira ƴan daƙiƙa don ciniki don yin tunani a tarihin ma'amala" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 12383e2fb3..b90bb40d7e 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "सिताज़ा करना", "sync_status_syncronized": "सिंक्रनाइज़", "sync_status_syncronizing": "सिंक्रनाइज़ करने", + "sync_status_unsupported": "असमर्थित नोड", "syncing_wallet_alert_content": "आपकी शेष राशि और लेनदेन सूची तब तक पूरी नहीं हो सकती जब तक कि शीर्ष पर \"सिंक्रनाइज़्ड\" न लिखा हो। अधिक जानने के लिए क्लिक/टैप करें।", "syncing_wallet_alert_title": "आपका वॉलेट सिंक हो रहा है", "template": "खाका", @@ -735,6 +736,7 @@ "view_key_private": "कुंजी देखें(निजी)", "view_key_public": "कुंजी देखें (जनता)", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "लेन-देन इतिहास में लेन-देन प्रतिबिंबित होने के लिए कृपया कुछ सेकंड प्रतीक्षा करें", "wallet_keys": "बटुआ बीज / चाबियाँ", "wallet_list_create_new_wallet": "नया बटुआ बनाएँ", "wallet_list_edit_wallet": "बटुआ संपादित करें", @@ -785,6 +787,5 @@ "you_pay": "आप भुगतान करते हैं", "you_will_get": "में बदलें", "you_will_send": "से रूपांतरित करें", - "yy": "वाईवाई", - "waitFewSecondForTxUpdate": "लेन-देन इतिहास में लेन-देन प्रतिबिंबित होने के लिए कृपया कुछ सेकंड प्रतीक्षा करें" -} + "yy": "वाईवाई" +} \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index ac351b03ed..8d9cce1832 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "ZAPOČINJEMO SINKRONIZIRANJE", "sync_status_syncronized": "SINKRONIZIRANO", "sync_status_syncronizing": "SINKRONIZIRANJE", + "sync_status_unsupported": "Nepodržani čvor", "syncing_wallet_alert_content": "Vaš saldo i popis transakcija možda neće biti potpuni sve dok na vrhu ne piše \"SINKRONIZIRANO\". Kliknite/dodirnite da biste saznali više.", "syncing_wallet_alert_title": "Vaš novčanik se sinkronizira", "template": "Predložak", @@ -733,6 +734,7 @@ "view_key_private": "View key (privatni)", "view_key_public": "View key (javni)", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "Pričekajte nekoliko sekundi da se transakcija prikaže u povijesti transakcija", "wallet_keys": "Pristupni izraz/ključ novčanika", "wallet_list_create_new_wallet": "Izradi novi novčanik", "wallet_list_edit_wallet": "Uredi novčanik", @@ -783,6 +785,5 @@ "you_pay": "Vi plaćate", "you_will_get": "Razmijeni u", "you_will_send": "Razmijeni iz", - "yy": "GG", - "waitFewSecondForTxUpdate": "Pričekajte nekoliko sekundi da se transakcija prikaže u povijesti transakcija" -} + "yy": "GG" +} \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 2756fcca06..53c3446680 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -638,6 +638,7 @@ "sync_status_starting_sync": "MULAI SINKRONISASI", "sync_status_syncronized": "SUDAH TERSINKRONISASI", "sync_status_syncronizing": "SEDANG SINKRONISASI", + "sync_status_unsupported": "Node yang tidak didukung", "syncing_wallet_alert_content": "Saldo dan daftar transaksi Anda mungkin belum lengkap sampai tertulis “SYNCHRONIZED” di bagian atas. Klik/ketuk untuk mempelajari lebih lanjut.", "syncing_wallet_alert_title": "Dompet Anda sedang disinkronkan", "template": "Template", @@ -736,6 +737,7 @@ "view_key_private": "Kunci tampilan (privat)", "view_key_public": "Kunci tampilan (publik)", "view_transaction_on": "Lihat Transaksi di ", + "waitFewSecondForTxUpdate": "Mohon tunggu beberapa detik hingga transaksi terlihat di riwayat transaksi", "wallet_keys": "Seed/kunci dompet", "wallet_list_create_new_wallet": "Buat Dompet Baru", "wallet_list_edit_wallet": "Edit dompet", @@ -786,6 +788,5 @@ "you_pay": "Anda Membayar", "you_will_get": "Konversi ke", "you_will_send": "Konversi dari", - "yy": "YY", - "waitFewSecondForTxUpdate": "Mohon tunggu beberapa detik hingga transaksi terlihat di riwayat transaksi" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 83597d4c91..7b9b8d9fc4 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "INIZIO SINC", "sync_status_syncronized": "SINCRONIZZATO", "sync_status_syncronizing": "SINCRONIZZAZIONE", + "sync_status_unsupported": "Nodo non supportato", "syncing_wallet_alert_content": "Il saldo e l'elenco delle transazioni potrebbero non essere completi fino a quando non viene visualizzato \"SYNCHRONIZED\" in alto. Clicca/tocca per saperne di più.", "syncing_wallet_alert_title": "Il tuo portafoglio si sta sincronizzando", "template": "Modello", @@ -735,6 +736,7 @@ "view_key_private": "Chiave di visualizzazione (privata)", "view_key_public": "Chiave di visualizzazione (pubblica)", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "Attendi qualche secondo affinché la transazione venga riflessa nella cronologia delle transazioni", "waiting_payment_confirmation": "In attesa di conferma del pagamento", "wallet_keys": "Seme Portafoglio /chiavi", "wallet_list_create_new_wallet": "Crea Nuovo Portafoglio", @@ -786,6 +788,5 @@ "you_pay": "Tu paghi", "you_will_get": "Converti a", "you_will_send": "Conveti da", - "yy": "YY", - "waitFewSecondForTxUpdate": "Attendi qualche secondo affinché la transazione venga riflessa nella cronologia delle transazioni" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index eb74091a97..1e6006a5c8 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "同期の開始", "sync_status_syncronized": "同期された", "sync_status_syncronizing": "同期", + "sync_status_unsupported": "サポートされていないノード", "syncing_wallet_alert_content": "上部に「同期済み」と表示されるまで、残高と取引リストが完了していない可能性があります。詳細については、クリック/タップしてください。", "syncing_wallet_alert_title": "ウォレットは同期中です", "template": "テンプレート", @@ -734,6 +735,7 @@ "view_key_private": "ビューキー (プライベート)", "view_key_public": "ビューキー (パブリック)", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "取引履歴に取引が反映されるまで数秒お待ちください。", "wallet_keys": "ウォレットシード/キー", "wallet_list_create_new_wallet": "新しいウォレットを作成", "wallet_list_edit_wallet": "ウォレットを編集する", @@ -784,6 +786,5 @@ "you_pay": "あなたが支払う", "you_will_get": "に変換", "you_will_send": "から変換", - "yy": "YY", - "waitFewSecondForTxUpdate": "取引履歴に取引が反映されるまで数秒お待ちください。" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 9a8bf48838..3171b67484 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "동기화 시작", "sync_status_syncronized": "동기화", "sync_status_syncronizing": "동기화", + "sync_status_unsupported": "지원되지 않은 노드", "syncing_wallet_alert_content": "상단에 \"동기화됨\"이라고 표시될 때까지 잔액 및 거래 목록이 완전하지 않을 수 있습니다. 자세히 알아보려면 클릭/탭하세요.", "syncing_wallet_alert_title": "지갑 동기화 중", "template": "주형", @@ -734,6 +735,7 @@ "view_key_private": "키보기(은밀한)", "view_key_public": "키보기 (공공의)", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "거래 내역에 거래가 반영될 때까지 몇 초 정도 기다려 주세요.", "wallet_keys": "지갑 시드 / 키", "wallet_list_create_new_wallet": "새 월렛 만들기", "wallet_list_edit_wallet": "지갑 수정", @@ -785,6 +787,5 @@ "you_will_get": "로 변환하다", "you_will_send": "다음에서 변환", "YY": "YY", - "yy": "YY", - "waitFewSecondForTxUpdate": "거래 내역에 거래가 반영될 때까지 몇 초 정도 기다려 주세요." -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 711c3c8841..2eacf4180f 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "စင့်ခ်လုပ်ခြင်း။", "sync_status_syncronized": "ထပ်တူပြုထားသည်။", "sync_status_syncronizing": "ထပ်တူပြုခြင်း။", + "sync_status_unsupported": "node မထောက်ပံ့ node ကို", "syncing_wallet_alert_content": "သင်၏လက်ကျန်နှင့် ငွေပေးငွေယူစာရင်းသည် ထိပ်တွင် \"Synchronizeed\" ဟုပြောသည်အထိ မပြီးမြောက်နိုင်ပါ။ ပိုမိုလေ့လာရန် နှိပ်/နှိပ်ပါ။", "syncing_wallet_alert_title": "သင့်ပိုက်ဆံအိတ်ကို စင့်ခ်လုပ်နေပါသည်။", "template": "ပုံစံခွက်", @@ -733,6 +734,7 @@ "view_key_private": "သော့ကိုကြည့်ရန် (သီးသန့်)", "view_key_public": "သော့ကိုကြည့်ရန် (အများပြည်သူ)", "view_transaction_on": "ငွေလွှဲခြင်းကို ဖွင့်ကြည့်ပါ။", + "waitFewSecondForTxUpdate": "ငွေပေးငွေယူ မှတ်တမ်းတွင် ရောင်ပြန်ဟပ်ရန် စက္ကန့်အနည်းငယ်စောင့်ပါ။", "wallet_keys": "ပိုက်ဆံအိတ် အစေ့/သော့များ", "wallet_list_create_new_wallet": "Wallet အသစ်ဖန်တီးပါ။", "wallet_list_edit_wallet": "ပိုက်ဆံအိတ်ကို တည်းဖြတ်ပါ။", @@ -783,6 +785,5 @@ "you_pay": "သင်ပေးချေပါ။", "you_will_get": "သို့ပြောင်းပါ။", "you_will_send": "မှပြောင်းပါ။", - "yy": "YY", - "waitFewSecondForTxUpdate": "ငွေပေးငွေယူ မှတ်တမ်းတွင် ရောင်ပြန်ဟပ်ရန် စက္ကန့်အနည်းငယ်စောင့်ပါ။" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 7527eb10fa..1151517482 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "BEGINNEN MET SYNCHRONISEREN", "sync_status_syncronized": "SYNCHRONIZED", "sync_status_syncronizing": "SYNCHRONISEREN", + "sync_status_unsupported": "Niet ondersteund knooppunt", "syncing_wallet_alert_content": "Uw saldo- en transactielijst is mogelijk pas compleet als er bovenaan 'GESYNCHRONISEERD' staat. Klik/tik voor meer informatie.", "syncing_wallet_alert_title": "Uw portemonnee wordt gesynchroniseerd", "template": "Sjabloon", @@ -733,6 +734,7 @@ "view_key_private": "Bekijk sleutel (privaat)", "view_key_public": "Bekijk sleutel (openbaar)", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "Wacht een paar seconden totdat de transactie wordt weergegeven in de transactiegeschiedenis", "waiting_payment_confirmation": "In afwachting van betalingsbevestiging", "wallet_keys": "Portemonnee zaad/sleutels", "wallet_list_create_new_wallet": "Maak een nieuwe portemonnee", @@ -784,6 +786,5 @@ "you_pay": "U betaalt", "you_will_get": "Converteren naar", "you_will_send": "Converteren van", - "yy": "JJ", - "waitFewSecondForTxUpdate": "Wacht een paar seconden totdat de transactie wordt weergegeven in de transactiegeschiedenis" -} + "yy": "JJ" +} \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index e93e56f743..0e84e5c874 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "ROZPOCZĘCIE SYNCHRONIZACJI", "sync_status_syncronized": "ZSYNCHRONIZOWANO", "sync_status_syncronizing": "SYNCHRONIZACJA", + "sync_status_unsupported": "Nieobsługiwany węzeł", "syncing_wallet_alert_content": "Twoje saldo i lista transakcji mogą nie być kompletne, dopóki u góry nie pojawi się napis „SYNCHRONIZOWANY”. Kliknij/stuknij, aby dowiedzieć się więcej.", "syncing_wallet_alert_title": "Twój portfel się synchronizuje", "template": "Szablon", @@ -733,6 +734,7 @@ "view_key_private": "Prywatny Klucz Wglądu", "view_key_public": "Publiczny Klucz Wglądu", "view_transaction_on": "Zobacz transakcje na ", + "waitFewSecondForTxUpdate": "Poczekaj kilka sekund, aż transakcja zostanie odzwierciedlona w historii transakcji", "wallet_keys": "Klucze portfela", "wallet_list_create_new_wallet": "Utwórz nowy portfel", "wallet_list_edit_wallet": "Edytuj portfel", @@ -783,6 +785,5 @@ "you_pay": "Płacisz", "you_will_get": "Konwertuj na", "you_will_send": "Konwertuj z", - "yy": "RR", - "waitFewSecondForTxUpdate": "Poczekaj kilka sekund, aż transakcja zostanie odzwierciedlona w historii transakcji" -} + "yy": "RR" +} \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index a7c1ff7e53..73a2988ff8 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "INICIANDO SINCRONIZAÇÃO", "sync_status_syncronized": "SINCRONIZADO", "sync_status_syncronizing": "SINCRONIZANDO", + "sync_status_unsupported": "Nó não suportado", "syncing_wallet_alert_content": "Seu saldo e lista de transações podem não estar completos até que diga “SYNCHRONIZED” no topo. Clique/toque para saber mais.", "syncing_wallet_alert_title": "Sua carteira está sincronizando", "template": "Modelo", @@ -735,6 +736,7 @@ "view_key_private": "Chave de visualização (privada)", "view_key_public": "Chave de visualização (pública)", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "Aguarde alguns segundos para que a transação seja refletida no histórico de transações", "waiting_payment_confirmation": "Aguardando confirmação de pagamento", "wallet_keys": "Semente/chaves da carteira", "wallet_list_create_new_wallet": "Criar nova carteira", @@ -786,6 +788,5 @@ "you_pay": "Você paga", "you_will_get": "Converter para", "you_will_send": "Converter de", - "yy": "aa", - "waitFewSecondForTxUpdate": "Aguarde alguns segundos para que a transação seja refletida no histórico de transações" -} + "yy": "aa" +} \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index d339d2670f..0ce75cc87d 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "НАЧАЛО СИНХРОНИЗАЦИИ", "sync_status_syncronized": "СИНХРОНИЗИРОВАН", "sync_status_syncronizing": "СИНХРОНИЗАЦИЯ", + "sync_status_unsupported": "Неподдерживаемый узел", "syncing_wallet_alert_content": "Ваш баланс и список транзакций могут быть неполными, пока вверху не будет написано «СИНХРОНИЗИРОВАНО». Щелкните/коснитесь, чтобы узнать больше.", "syncing_wallet_alert_title": "Ваш кошелек синхронизируется", "template": "Шаблон", @@ -734,6 +735,7 @@ "view_key_private": "Приватный ключ просмотра", "view_key_public": "Публичный ключ просмотра", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "Пожалуйста, подождите несколько секунд, чтобы транзакция отразилась в истории транзакций.", "wallet_keys": "Мнемоническая фраза/ключи кошелька", "wallet_list_create_new_wallet": "Создать новый кошелёк", "wallet_list_edit_wallet": "Изменить кошелек", @@ -784,6 +786,5 @@ "you_pay": "Вы платите", "you_will_get": "Конвертировать в", "you_will_send": "Конвертировать из", - "yy": "ГГ", - "waitFewSecondForTxUpdate": "Пожалуйста, подождите несколько секунд, чтобы транзакция отразилась в истории транзакций." -} + "yy": "ГГ" +} \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index a4bb58d7de..f165b7e35f 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "กำลังเริ่มซิงโครไนซ์", "sync_status_syncronized": "ซิงโครไนซ์แล้ว", "sync_status_syncronizing": "กำลังซิงโครไนซ์", + "sync_status_unsupported": "โหนดที่ไม่ได้รับการสนับสนุน", "syncing_wallet_alert_content": "รายการยอดเงินและธุรกรรมของคุณอาจไม่สมบูรณ์จนกว่าจะมีข้อความว่า “ซิงโครไนซ์” ที่ด้านบน คลิก/แตะเพื่อเรียนรู้เพิ่มเติม่", "syncing_wallet_alert_title": "กระเป๋าสตางค์ของคุณกำลังซิงค์", "template": "แบบฟอร์ม", @@ -733,6 +734,7 @@ "view_key_private": "คีย์มุมมอง (ส่วนตัว)", "view_key_public": "คีย์มุมมอง (สาธารณะ)", "view_transaction_on": "ดูการทำธุรกรรมบน ", + "waitFewSecondForTxUpdate": "กรุณารอสักครู่เพื่อให้ธุรกรรมปรากฏในประวัติการทำธุรกรรม", "wallet_keys": "ซีดของกระเป๋า/คีย์", "wallet_list_create_new_wallet": "สร้างกระเป๋าใหม่", "wallet_list_edit_wallet": "แก้ไขกระเป๋าสตางค์", @@ -783,6 +785,5 @@ "you_pay": "คุณจ่าย", "you_will_get": "แปลงเป็น", "you_will_send": "แปลงจาก", - "yy": "ปี", - "waitFewSecondForTxUpdate": "กรุณารอสักครู่เพื่อให้ธุรกรรมปรากฏในประวัติการทำธุรกรรม" -} + "yy": "ปี" +} \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 2e9520dce5..b87794f2e3 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "Simula sa pag -sync", "sync_status_syncronized": "Naka -synchronize", "sync_status_syncronizing": "Pag -synchronize", + "sync_status_unsupported": "Hindi suportadong node", "syncing_wallet_alert_content": "Ang iyong balanse at listahan ng transaksyon ay maaaring hindi kumpleto hanggang sa sabihin nito na \"naka -synchronize\" sa tuktok. Mag -click/tap upang malaman ang higit pa.", "syncing_wallet_alert_title": "Ang iyong pitaka ay nag -sync", "template": "Template", @@ -733,6 +734,7 @@ "view_key_private": "Tingnan ang Key (Pribado)", "view_key_public": "Tingnan ang Key (Publiko)", "view_transaction_on": "Tingnan ang transaksyon sa", + "waitFewSecondForTxUpdate": "Mangyaring maghintay ng ilang segundo para makita ang transaksyon sa history ng mga transaksyon", "wallet_keys": "Mga buto/susi ng pitaka", "wallet_list_create_new_wallet": "Lumikha ng bagong pitaka", "wallet_list_edit_wallet": "I -edit ang Wallet", @@ -783,6 +785,5 @@ "you_pay": "Magbabayad ka", "you_will_get": "Mag -convert sa", "you_will_send": "I -convert mula sa", - "yy": "YY", - "waitFewSecondForTxUpdate": "Mangyaring maghintay ng ilang segundo para makita ang transaksyon sa history ng mga transaksyon" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 75148b2cab..80f9ced27f 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "SENKRONİZE BAŞLATILIYOR", "sync_status_syncronized": "SENKRONİZE EDİLDİ", "sync_status_syncronizing": "SENKRONİZE EDİLİYOR", + "sync_status_unsupported": "Desteklenmeyen düğüm", "syncing_wallet_alert_content": "Bakiyeniz ve işlem listeniz, en üstte \"SENKRONİZE EDİLDİ\" yazana kadar tamamlanmamış olabilir. Daha fazla bilgi edinmek için tıklayın/dokunun.", "syncing_wallet_alert_title": "Cüzdanınız senkronize ediliyor", "template": "Şablon", @@ -733,6 +734,7 @@ "view_key_private": "İzleme anahtarı (özel)", "view_key_public": "İzleme anahtarı (genel)", "view_transaction_on": "İşlemi şurada görüntüle ", + "waitFewSecondForTxUpdate": "İşlemin işlem geçmişine yansıması için lütfen birkaç saniye bekleyin", "wallet_keys": "Cüzdan tohumu/anahtarları", "wallet_list_create_new_wallet": "Yeni Cüzdan Oluştur", "wallet_list_edit_wallet": "Cüzdanı düzenle", @@ -783,6 +785,5 @@ "you_pay": "Şu kadar ödeyeceksin: ", "you_will_get": "Biçimine dönüştür:", "you_will_send": "Biçiminden dönüştür:", - "yy": "YY", - "waitFewSecondForTxUpdate": "İşlemin işlem geçmişine yansıması için lütfen birkaç saniye bekleyin" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index a461d10a5d..0363c4f48c 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "ПОЧАТОК СИНХРОНІЗАЦІЇ", "sync_status_syncronized": "СИНХРОНІЗОВАНИЙ", "sync_status_syncronizing": "СИНХРОНІЗАЦІЯ", + "sync_status_unsupported": "Непідтримуваний вузол", "syncing_wallet_alert_content": "Ваш баланс та список транзакцій може бути неповним, доки вгорі не буде написано «СИНХРОНІЗОВАНО». Натисніть/торкніться, щоб дізнатися більше.", "syncing_wallet_alert_title": "Ваш гаманець синхронізується", "template": "Шаблон", @@ -734,6 +735,7 @@ "view_key_private": "Приватний ключ перегляду", "view_key_public": "Публічний ключ перегляду", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "Будь ласка, зачекайте кілька секунд, поки транзакція відобразиться в історії транзакцій", "wallet_keys": "Мнемонічна фраза/ключі гаманця", "wallet_list_create_new_wallet": "Створити новий гаманець", "wallet_list_edit_wallet": "Редагувати гаманець", @@ -784,6 +786,5 @@ "you_pay": "Ви платите", "you_will_get": "Конвертувати в", "you_will_send": "Конвертувати з", - "yy": "YY", - "waitFewSecondForTxUpdate": "Будь ласка, зачекайте кілька секунд, поки транзакція відобразиться в історії транзакцій" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 7ba95a3c57..ed1cdef119 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "مطابقت پذیری شروع کر رہا ہے۔", "sync_status_syncronized": "مطابقت پذیر", "sync_status_syncronizing": "مطابقت پذیری", + "sync_status_unsupported": "غیر تعاون یافتہ نوڈ", "syncing_wallet_alert_content": "آپ کے بیلنس اور لین دین کی فہرست اس وقت تک مکمل نہیں ہو سکتی جب تک کہ یہ سب سے اوپر \"SYNCRONIZED\" نہ کہے۔ مزید جاننے کے لیے کلک/تھپتھپائیں۔", "syncing_wallet_alert_title": "آپ کا بٹوہ مطابقت پذیر ہو رہا ہے۔", "template": "سانچے", @@ -735,6 +736,7 @@ "view_key_private": "کلید دیکھیں (نجی)", "view_key_public": "کلید دیکھیں (عوامی)", "view_transaction_on": "لین دین دیکھیں آن", + "waitFewSecondForTxUpdate": "۔ﮟﯾﺮﮐ ﺭﺎﻈﺘﻧﺍ ﺎﮐ ﮉﻨﮑﯿﺳ ﺪﻨﭼ ﻡﺮﮐ ﮦﺍﺮﺑ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﯽﺳﺎﮑﻋ ﯽﮐ ﻦﯾﺩ ﻦﯿﻟ ﮟﯿﻣ ﺦﯾﺭﺎﺗ ﯽﮐ ﻦ", "wallet_keys": "بٹوے کے بیج / چابیاں", "wallet_list_create_new_wallet": "نیا والیٹ بنائیں", "wallet_list_edit_wallet": "بٹوے میں ترمیم کریں۔", @@ -785,6 +787,5 @@ "you_pay": "تم ادا کرو", "you_will_get": "میں تبدیل کریں۔", "you_will_send": "سے تبدیل کریں۔", - "yy": "YY", - "waitFewSecondForTxUpdate": "۔ﮟﯾﺮﮐ ﺭﺎﻈﺘﻧﺍ ﺎﮐ ﮉﻨﮑﯿﺳ ﺪﻨﭼ ﻡﺮﮐ ﮦﺍﺮﺑ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﯽﺳﺎﮑﻋ ﯽﮐ ﻦﯾﺩ ﻦﯿﻟ ﮟﯿﻣ ﺦﯾﺭﺎﺗ ﯽﮐ ﻦ" -} + "yy": "YY" +} \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 13af654809..824e87bb8e 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "Ń BẸ̀RẸ̀ RẸ́", "sync_status_syncronized": "TI MÚDỌ́GBA", "sync_status_syncronizing": "Ń MÚDỌ́GBA", + "sync_status_unsupported": "Ile-igbimọ ti ko ni atilẹyin", "syncing_wallet_alert_content": "Iwontunws.funfun rẹ ati atokọ idunadura le ma pari titi ti yoo fi sọ “SYNCHRONIZED” ni oke. Tẹ/tẹ ni kia kia lati ni imọ siwaju sii.", "syncing_wallet_alert_title": "Apamọwọ rẹ n muṣiṣẹpọ", "template": "Àwòṣe", @@ -734,6 +735,7 @@ "view_key_private": "Kọ́kọ́rọ́ ìwò (àdáni)", "view_key_public": "Kọ́kọ́rọ́ ìwò (kò àdáni)", "view_transaction_on": "Wo pàṣípààrọ̀ lórí ", + "waitFewSecondForTxUpdate": "Fi inurere duro fun awọn iṣeju diẹ fun idunadura lati ṣe afihan ninu itan-akọọlẹ iṣowo", "wallet_keys": "Hóró/kọ́kọ́rọ́ àpamọ́wọ́", "wallet_list_create_new_wallet": "Ṣe àpamọ́wọ́ títun", "wallet_list_edit_wallet": "Ṣatunkọ apamọwọ", @@ -784,6 +786,5 @@ "you_pay": "Ẹ sàn", "you_will_get": "Ṣe pàṣípààrọ̀ sí", "you_will_send": "Ṣe pàṣípààrọ̀ láti", - "yy": "Ọd", - "waitFewSecondForTxUpdate": "Fi inurere duro fun awọn iṣeju diẹ fun idunadura lati ṣe afihan ninu itan-akọọlẹ iṣowo" -} + "yy": "Ọd" +} \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 9c82981346..7b53264b61 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -635,6 +635,7 @@ "sync_status_starting_sync": "开始同步", "sync_status_syncronized": "已同步", "sync_status_syncronizing": "正在同步", + "sync_status_unsupported": "不支持的节点", "syncing_wallet_alert_content": "您的余额和交易列表可能不完整,直到顶部显示“已同步”。单击/点击以了解更多信息。", "syncing_wallet_alert_title": "您的钱包正在同步", "template": "模板", @@ -733,6 +734,7 @@ "view_key_private": "View 密钥(私钥)", "view_key_public": "View 密钥(公钥)", "view_transaction_on": "View Transaction on ", + "waitFewSecondForTxUpdate": "请等待几秒钟,交易才会反映在交易历史记录中", "wallet_keys": "钱包种子/密钥", "wallet_list_create_new_wallet": "创建新钱包", "wallet_list_edit_wallet": "编辑钱包", @@ -783,6 +785,5 @@ "you_pay": "你付钱", "you_will_get": "转换到", "you_will_send": "转换自", - "yy": "YY", - "waitFewSecondForTxUpdate": "请等待几秒钟,交易才会反映在交易历史记录中" -} + "yy": "YY" +} \ No newline at end of file diff --git a/tool/configure.dart b/tool/configure.dart index bf89743efb..2dd772bbe0 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -125,7 +125,7 @@ abstract class Bitcoin { List getAddresses(Object wallet); String getAddress(Object wallet); - List getSilentAddresses(Object wallet); + List getSilentAddresses(Object wallet); List getSubAddresses(Object wallet); From a5bc33827687126f016db0d85186e4ac58f137e2 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 27 Feb 2024 12:50:36 -0300 Subject: [PATCH 004/242] chore: pubspec.lock --- cw_bitcoin/pubspec.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index c7750f1afb..5678027397 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -71,7 +71,7 @@ packages: description: path: "." ref: master - resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 + resolved-ref: "932f13300ba501eca16642400a8f68eefaef2708" url: "https://github.com/cake-tech/bitbox-flutter.git" source: git version: "1.0.1" @@ -178,10 +178,10 @@ packages: dependency: transitive description: name: built_value - sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e url: "https://pub.dev" source: hosted - version: "8.9.0" + version: "8.9.1" characters: dependency: transitive description: From 2967809a0ebba3d6bfc11f3777dce7a27884235a Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 27 Feb 2024 14:46:50 -0300 Subject: [PATCH 005/242] chore: pubspec.lock --- cw_bitcoin/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 5678027397..ac12055b2f 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -80,7 +80,7 @@ packages: description: path: "." ref: cake-update-v2 - resolved-ref: "43b80bcf0ef6e7224603a6b8874b61efec3c6a4c" + resolved-ref: be980da9ed063da13db3907ca76d534298bb1d40 url: "https://github.com/cake-tech/bitcoin_base.git" source: git version: "4.0.0" From a44bd6b8f9c225e786c80b5ffddf57f26e8853b5 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 27 Feb 2024 19:51:53 -0300 Subject: [PATCH 006/242] fix: scan when switching, fix multiple unspents in same tx --- cw_bitcoin/lib/electrum_transaction_info.dart | 18 ++- cw_bitcoin/lib/electrum_wallet.dart | 149 ++++++++++-------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 11 +- cw_bitcoin/pubspec.lock | 2 +- 4 files changed, 103 insertions(+), 77 deletions(-) diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index 5a7f797f96..50896c837f 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -19,7 +19,7 @@ class ElectrumTransactionBundle { } class ElectrumTransactionInfo extends TransactionInfo { - BitcoinUnspent? unspent; + List? unspents; ElectrumTransactionInfo(this.type, {required String id, @@ -31,7 +31,7 @@ class ElectrumTransactionInfo extends TransactionInfo { required DateTime date, required int confirmations, String? to, - this.unspent}) { + this.unspents}) { this.id = id; this.height = height; this.amount = amount; @@ -160,10 +160,12 @@ class ElectrumTransactionInfo extends TransactionInfo { isPending: data['isPending'] as bool, confirmations: data['confirmations'] as int, to: data['to'] as String?, - unspent: data['unspent'] != null - ? BitcoinUnspent.fromJSON( - BitcoinAddressRecord.fromJSON(data['unspent']['address_record'] as String), - data['unspent'] as Map) + unspents: data['unspent'] != null + ? (data['unspent'] as List) + .map((unspent) => BitcoinUnspent.fromJSON( + BitcoinAddressRecord.fromJSON(unspent['address_record'] as String), + data['unspent'] as Map)) + .toList() : null, ); } @@ -210,11 +212,11 @@ class ElectrumTransactionInfo extends TransactionInfo { m['confirmations'] = confirmations; m['fee'] = fee; m['to'] = to; - m['unspent'] = unspent?.toJson() ?? {}; + m['unspent'] = unspents?.map((e) => e.toJson()) ?? []; return m; } String toString() { - return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, confirmations: $confirmations, to: $to, unspent: $unspent)'; + return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, confirmations: $confirmations, to: $to, unspent: $unspents)'; } } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index bd63c40972..455213a2e5 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -142,6 +142,9 @@ abstract class ElectrumWalletBase @observable bool nodeSupportsSilentPayments = true; + @observable + int? currentChainTip; + @override BitcoinWalletKeys get keys => BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); @@ -197,30 +200,37 @@ abstract class ElectrumWalletBase syncStatus = UnsupportedSyncStatus(); } - if (message is BitcoinUnspent) { - if (!unspentCoins.any((utx) => - utx.hash.contains(message.hash) && - utx.vout == message.vout && - utx.address.contains(message.address))) { - unspentCoins.add(message); - - if (unspentCoinsInfo.values.any((element) => - element.walletId.contains(id) && - element.hash.contains(message.hash) && - element.address.contains(message.address))) { - _addCoinInfo(message); - - await walletInfo.save(); - await save(); + if (message is Map) { + for (final map in message.entries) { + final txid = map.key; + final tx = map.value; + + if (tx.unspents != null) { + tx.unspents!.forEach((unspent) => walletAddresses.addSilentAddresses( + [unspent.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord])); + + final existingTxInfo = transactionHistory.transactions[txid]; + if (existingTxInfo != null) { + final newUnspents = tx.unspents! + .where((unspent) => !existingTxInfo.unspents!.any((element) => + element.hash.contains(unspent.hash) && element.vout == unspent.vout)) + .toList(); + + if (newUnspents.isNotEmpty) { + existingTxInfo.unspents ??= []; + existingTxInfo.unspents!.addAll(newUnspents); + existingTxInfo.amount += newUnspents.length > 1 + ? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent) + : newUnspents[0].value; + } + } else { + transactionHistory.addMany(message); + transactionHistory.save(); + } } - - balance[currency] = await _fetchBalances(); } - } - if (message is Map) { - transactionHistory.addMany(message); - await transactionHistory.save(); + updateUnspent(); } // check if is a SyncStatus type since "is SyncStatus" doesn't work here @@ -236,17 +246,16 @@ abstract class ElectrumWalletBase @override Future startSync() async { try { + syncStatus = AttemptingSyncStatus(); + if (hasSilentPaymentsScanning) { try { await _setInitialHeight(); } catch (_) {} - final currentChainTip = await electrumClient.getCurrentBlockChainTip(); if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); } - } else { - syncStatus = AttemptingSyncStatus(); } await updateTransactions(); @@ -258,9 +267,9 @@ abstract class ElectrumWalletBase Timer.periodic( const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates()); - // if (!hasSilentPaymentsScanning) { - syncStatus = SyncedSyncStatus(); - // } + if (!hasSilentPaymentsScanning || walletInfo.restoreHeight == currentChainTip) { + syncStatus = SyncedSyncStatus(); + } } catch (e, stacktrace) { print(stacktrace); print(e.toString()); @@ -312,10 +321,22 @@ abstract class ElectrumWalletBase leftAmount = leftAmount - utx.value; final address = _addressTypeFromStr(utx.address, network); - final privkey = generateECPrivate( - hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, - index: utx.bitcoinAddressRecord.index, - network: network); + + ECPrivate? privkey; + if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { + privkey = walletAddresses.primarySilentAddress!.b_spend.clone().tweakAdd( + BigintUtils.fromBytes(BytesUtils.fromHexString( + (utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord) + .silentPaymentTweak)), + ); + } else { + privkey = generateECPrivate( + hd: utx.bitcoinAddressRecord.isHidden + ? walletAddresses.sideHd + : walletAddresses.mainHd, + index: utx.bitcoinAddressRecord.index, + network: network); + } privateKeys.add(privkey); inputPrivKeyInfos.add(ECPrivateInfo(privkey, address.type == SegwitAddresType.p2tr)); @@ -661,18 +682,19 @@ abstract class ElectrumWalletBase Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); Future updateUnspent() async { + List updatedUnspentCoins = []; + // Update unspents stored from scanned silent payment transactions transactionHistory.transactions.values.forEach((tx) { - if (tx.unspent != null) { - if (!unspentCoins - .any((utx) => utx.hash.contains(tx.unspent!.hash) && utx.vout == tx.unspent!.vout)) { - unspentCoins.add(tx.unspent!); + if (tx.unspents != null) { + if (!unspentCoins.any((utx) => + tx.unspents!.any((element) => utx.hash.contains(element.hash)) && + tx.unspents!.any((element) => utx.vout == element.vout))) { + updatedUnspentCoins.addAll(tx.unspents!); } } }); - List updatedUnspentCoins = []; - final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); await Future.wait(walletAddresses.allAddresses.map((address) => electrumClient @@ -866,7 +888,7 @@ abstract class ElectrumWalletBase final Map historiesWithDetails = {}; final history = await electrumClient - .getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network)!); + .getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network)); if (history.isNotEmpty) { addressRecord.setAsUsed(); @@ -1048,8 +1070,8 @@ abstract class ElectrumWalletBase } if (walletInfo.restoreHeight == 0) { - final currentHeight = await electrumClient.getCurrentBlockChainTip(); - if (currentHeight != null) walletInfo.restoreHeight = currentHeight; + currentChainTip = await electrumClient.getCurrentBlockChainTip(); + if (currentChainTip != null) walletInfo.restoreHeight = currentChainTip!; } } } @@ -1102,13 +1124,18 @@ class SyncResponse { Future startRefresh(ScanData scanData) async { var cachedBlockchainHeight = scanData.chainTip; + Future connect() async { + final electrumClient = scanData.electrumClient; + if (!electrumClient.isConnected) { + final node = scanData.node; + await electrumClient.connectToUri(Uri.parse(node)); + } + return electrumClient; + } + Future getNodeHeightOrUpdate(int baseHeight) async { if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) { - final electrumClient = scanData.electrumClient; - if (!electrumClient.isConnected) { - final node = scanData.node; - await electrumClient.connectToUri(Uri.parse(node)); - } + final electrumClient = await connect(); cachedBlockchainHeight = await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; @@ -1151,16 +1178,8 @@ Future startRefresh(ScanData scanData) async { return; } - print(["Scanning from height:", syncHeight]); - try { - // Get all the tweaks from the block - final electrumClient = scanData.electrumClient; - if (!electrumClient.isConnected) { - final node = scanData.node; - print(node); - await electrumClient.connectToUri(Uri.parse(node)); - } + final electrumClient = await connect(); List? tweaks; try { @@ -1177,7 +1196,6 @@ Future startRefresh(ScanData scanData) async { for (var i = 0; i < tweaks.length; i++) { try { - // final txid = tweaks.keys.toList()[i]; final details = tweaks[i] as Map; final output_pubkeys = (details["output_pubkeys"] as List); final tweak = details["tweak"].toString(); @@ -1193,9 +1211,7 @@ Future startRefresh(ScanData scanData) async { final result = spb.scanOutputs( scanData.primarySilentAddress.b_scan, scanData.primarySilentAddress.B_spend, - output_pubkeys - .map((p) => ECPublic.fromBytes(BytesUtils.fromHexString(p.toString()).sublist(2))) - .toList(), + output_pubkeys.map((output) => output.toString()).toList(), precomputedLabels: scanData.labels, ); @@ -1206,8 +1222,7 @@ Future startRefresh(ScanData scanData) async { result.forEach((key, value) async { final t_k = value[0]; - final address = - ECPublic.fromHex(key).toTaprootAddress(tweak: false).toAddress(scanData.network); + final address = ECPublic.fromHex(key).toTaprootAddress().toAddress(scanData.network); final listUnspent = await electrumClient.getListUnspentWithAddress(address, scanData.network); @@ -1228,6 +1243,10 @@ Future startRefresh(ScanData scanData) async { } catch (_) {} }); + if (info == null) { + return; + } + // final tweak = value[0]; // String? label; // if (value.length > 1) label = value[1]; @@ -1237,20 +1256,16 @@ Future startRefresh(ScanData scanData) async { WalletType.bitcoin, id: tx.hash, height: syncHeight, - amount: tx.value, + amount: 0, // will be added later via unspent fee: 0, direction: TransactionDirection.incoming, isPending: false, date: DateTime.now(), - confirmations: currentChainTip - syncHeight, + confirmations: currentChainTip - syncHeight - 1, to: scanData.primarySilentAddress.toString(), - unspent: tx, + unspents: [tx], ); - // final status = json.decode((await http - // .get(Uri.parse("https://blockstream.info/testnet/api/tx/$txid/outspends"))) - // .body) as List; - // bool spent = false; // for (final s in status) { // if ((s["spent"] as bool) == true) { diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index ec2a27498c..2bfeeeea05 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -35,7 +35,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { }) : _addresses = ObservableList.of((initialAddresses ?? []).toSet()), primarySilentAddress = silentAddress, addressesByReceiveType = - ObservableList.of(([]).toSet()), + ObservableList.of(([]).toSet()), receiveAddresses = ObservableList.of((initialAddresses ?? []) .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed) .toSet()), @@ -408,6 +408,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { updateAddressesByMatch(); } + @action + void addSilentAddresses(Iterable addresses) { + final addressesSet = this.silentAddresses.toSet(); + addressesSet.addAll(addresses); + this.silentAddresses.clear(); + this.silentAddresses.addAll(addressesSet); + updateAddressesByMatch(); + } + void _validateSideHdAddresses(List addrWithTransactions) { addrWithTransactions.forEach((element) { if (element.address != diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index ac12055b2f..3584b0fe3e 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -80,7 +80,7 @@ packages: description: path: "." ref: cake-update-v2 - resolved-ref: be980da9ed063da13db3907ca76d534298bb1d40 + resolved-ref: "7634511b15e5a48bd18e3c19f81971628090c04f" url: "https://github.com/cake-tech/bitcoin_base.git" source: git version: "4.0.0" From 477aff098c63e6562d0d14e49ae1ae689d085829 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 27 Feb 2024 21:33:29 -0300 Subject: [PATCH 007/242] fix: initial scan --- cw_bitcoin/lib/electrum_wallet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 455213a2e5..1ab76bdcd8 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -253,7 +253,7 @@ abstract class ElectrumWalletBase await _setInitialHeight(); } catch (_) {} - if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { + if ((currentChainTip ?? 0) <= walletInfo.restoreHeight) { _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); } } From f58fca30201a70aa11b20085ffca54c864c72484 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 27 Feb 2024 21:36:56 -0300 Subject: [PATCH 008/242] fix: initial scan --- cw_bitcoin/lib/electrum_wallet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 1ab76bdcd8..cd8093f72c 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -253,7 +253,7 @@ abstract class ElectrumWalletBase await _setInitialHeight(); } catch (_) {} - if ((currentChainTip ?? 0) <= walletInfo.restoreHeight) { + if ((currentChainTip ?? 0) < walletInfo.restoreHeight) { _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); } } From 0016d436f313064260a6c6691b57744b975247c4 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Wed, 28 Feb 2024 12:48:42 -0300 Subject: [PATCH 009/242] fix: scanning issues --- cw_bitcoin/lib/electrum_wallet.dart | 34 +++++++++++++---------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index cd8093f72c..9f7e675646 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -37,7 +37,6 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; -import 'package:hex/hex.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; @@ -253,7 +252,7 @@ abstract class ElectrumWalletBase await _setInitialHeight(); } catch (_) {} - if ((currentChainTip ?? 0) < walletInfo.restoreHeight) { + if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); } } @@ -278,14 +277,17 @@ abstract class ElectrumWalletBase } @action - @override - Future connectToNode({required Node node}) async { + Future _electrumConnect(Node node, {bool? attemptedReconnect}) async { try { syncStatus = ConnectingSyncStatus(); await electrumClient.connectToUri(node.uri); - electrumClient.onConnectionStatusChange = (bool isConnected) { + electrumClient.onConnectionStatusChange = (bool isConnected) async { if (!isConnected) { syncStatus = LostConnectionSyncStatus(); + await electrumClient.close(); + if (attemptedReconnect == false) { + await _electrumConnect(node, attemptedReconnect: true); + } } }; syncStatus = ConnectedSyncStatus(); @@ -295,6 +297,10 @@ abstract class ElectrumWalletBase } } + @action + @override + Future connectToNode({required Node node}) => _electrumConnect(node); + Future _estimateTxFeeAndInputsToUse( int credentialsAmount, bool sendAll, @@ -807,14 +813,7 @@ abstract class ElectrumWalletBase final ins = []; for (final vin in original.inputs) { - try { - final id = HEX.encode(HEX.decode(vin.txId).reversed.toList()); - final txHex = await electrumClient.getTransactionHex(hash: id); - final tx = BtcTransaction.fromRaw(txHex); - ins.add(tx); - } catch (_) { - ins.add(BtcTransaction.fromRaw(await electrumClient.getTransactionHex(hash: vin.txId))); - } + ins.add(BtcTransaction.fromRaw(await electrumClient.getTransactionHex(hash: vin.txId))); } return ElectrumTransactionBundle(original, @@ -1065,10 +1064,6 @@ abstract class ElectrumWalletBase } Future _setInitialHeight() async { - if (walletInfo.isRecovery) { - return; - } - if (walletInfo.restoreHeight == 0) { currentChainTip = await electrumClient.getCurrentBlockChainTip(); if (currentChainTip != null) walletInfo.restoreHeight = currentChainTip!; @@ -1191,10 +1186,11 @@ Future startRefresh(ScanData scanData) async { } if (tweaks == null) { - return scanData.sendPort.send(false); + scanData.sendPort.send(SyncResponse(syncHeight, + SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); } - for (var i = 0; i < tweaks.length; i++) { + for (var i = 0; i < tweaks!.length; i++) { try { final details = tweaks[i] as Map; final output_pubkeys = (details["output_pubkeys"] as List); From 18697fbd0ff6d801f49acfe4bde6edca0bc70173 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 1 Mar 2024 20:38:53 -0300 Subject: [PATCH 010/242] fix: sync, storing silent unspents --- cw_bitcoin/lib/bitcoin_address_record.dart | 13 +-- cw_bitcoin/lib/bitcoin_unspent.dart | 8 +- cw_bitcoin/lib/bitcoin_wallet.dart | 26 +----- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 2 +- cw_bitcoin/lib/electrum.dart | 4 +- .../lib/electrum_transaction_history.dart | 26 +++--- cw_bitcoin/lib/electrum_transaction_info.dart | 10 +- cw_bitcoin/lib/electrum_wallet.dart | 91 ++++++++++--------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 45 ++++++--- cw_bitcoin/pubspec.lock | 16 ++-- cw_bitcoin/pubspec.yaml | 8 +- cw_bitcoin_cash/pubspec.yaml | 8 +- scripts/android/app_env.fish | 75 +++++++++++++++ 13 files changed, 192 insertions(+), 140 deletions(-) create mode 100644 scripts/android/app_env.fish diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index afd7c34e16..f5731f0f12 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -124,10 +124,9 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { super.balance = 0, super.name = '', super.isUsed = false, - required super.type, required this.silentPaymentTweak, required super.network, - }); + }) : super(type: SilentPaymentsAddresType.p2sp); factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) { @@ -141,18 +140,14 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { txCount: decoded['txCount'] as int? ?? 0, name: decoded['name'] as String? ?? '', balance: decoded['balance'] as int? ?? 0, - type: decoded['type'] != null && decoded['type'] != '' - ? BitcoinAddressType.values - .firstWhere((type) => type.toString() == decoded['type'] as String) - : SegwitAddresType.p2wpkh, network: (decoded['network'] as String?) == null ? network : BasedUtxoNetwork.fromName(decoded['network'] as String), - silentPaymentTweak: decoded['silentPaymentTweak'] as String, + silentPaymentTweak: decoded['silent_payment_tweak'] as String?, ); } - final String silentPaymentTweak; + final String? silentPaymentTweak; @override String toJSON() => json.encode({ @@ -165,6 +160,6 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { 'balance': balance, 'type': type.toString(), 'network': network?.value, - 'silentPaymentTweak': silentPaymentTweak, + 'silent_payment_tweak': silentPaymentTweak, }); } diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index ce3c0da166..b2c1d90c4c 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -1,10 +1,9 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_core/unspent_transaction_output.dart'; class BitcoinUnspent extends Unspent { BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout, - {this.silentPaymentTweak, this.type}) + {this.silentPaymentTweak}) : bitcoinAddressRecord = addressRecord, super(addressRecord.address, hash, value, vout, null); @@ -15,9 +14,6 @@ class BitcoinUnspent extends Unspent { json['value'] as int, json['tx_pos'] as int, silentPaymentTweak: json['silent_payment_tweak'] as String?, - type: json['type'] == null - ? null - : BitcoinAddressType.values.firstWhere((e) => e.toString() == json['type']), ); Map toJson() { @@ -27,12 +23,10 @@ class BitcoinUnspent extends Unspent { 'value': value, 'tx_pos': vout, 'silent_payment_tweak': silentPaymentTweak, - 'type': type.toString(), }; return json; } final BaseBitcoinAddressRecord bitcoinAddressRecord; String? silentPaymentTweak; - BitcoinAddressType? type; } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index f24142493e..fd45584caf 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -32,7 +32,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { Map? initialChangeAddressIndex, List? initialSilentAddresses, int initialSilentAddressIndex = 0, - SilentPaymentOwner? silentAddress, }) : super( mnemonic: mnemonic, password: password, @@ -54,10 +53,13 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialChangeAddressIndex: initialChangeAddressIndex, initialSilentAddresses: initialSilentAddresses, initialSilentAddressIndex: initialSilentAddressIndex, - silentAddress: silentAddress, mainHd: hd, sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"), network: networkParam ?? network, + masterHd: bitcoin.HDWallet.fromSeed( + seedBytes, + network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, + ), ); hasSilentPaymentsScanning = addressPageType == SilentPaymentsAddresType.p2sp.toString(); @@ -97,16 +99,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialAddresses: initialAddresses, initialSilentAddresses: initialSilentAddresses, initialSilentAddressIndex: initialSilentAddressIndex, - silentAddress: await SilentPaymentOwner.fromPrivateKeys( - b_scan: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( - seedBytes, - network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, - ).derivePath(SCAN_PATH).privKey!), - b_spend: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( - seedBytes, - network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, - ).derivePath(SPEND_PATH).privKey!), - hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'), initialBalance: initialBalance, seedBytes: seedBytes, initialRegularAddressIndex: initialRegularAddressIndex, @@ -134,16 +126,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialAddresses: snp.addresses, initialSilentAddresses: snp.silentAddresses, initialSilentAddressIndex: snp.silentAddressIndex, - silentAddress: await SilentPaymentOwner.fromPrivateKeys( - b_scan: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( - seedBytes, - network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, - ).derivePath(SCAN_PATH).privKey!), - b_spend: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( - seedBytes, - network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, - ).derivePath(SPEND_PATH).privKey!), - hrp: snp.network == BitcoinNetwork.testnet ? 'tsp' : 'sp'), initialBalance: snp.balance, seedBytes: seedBytes, initialRegularAddressIndex: snp.regularAddressIndex, diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index 48960ce3d1..486e69b111 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -20,7 +20,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S super.initialChangeAddressIndex, super.initialSilentAddresses, super.initialSilentAddressIndex = 0, - super.silentAddress, + super.masterHd, }) : super(walletInfo); @override diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 236dddaa26..d9e068d654 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -36,8 +36,8 @@ class ElectrumClient { _tasks = {}, unterminatedString = ''; - static const connectionTimeout = Duration(seconds: 5); - static const aliveTimerDuration = Duration(seconds: 4); + static const connectionTimeout = Duration(seconds: 300); + static const aliveTimerDuration = Duration(seconds: 300); bool get isConnected => _isConnected; Socket? socket; diff --git a/cw_bitcoin/lib/electrum_transaction_history.dart b/cw_bitcoin/lib/electrum_transaction_history.dart index d478c3b12d..a7de414e4d 100644 --- a/cw_bitcoin/lib/electrum_transaction_history.dart +++ b/cw_bitcoin/lib/electrum_transaction_history.dart @@ -11,13 +11,11 @@ part 'electrum_transaction_history.g.dart'; const transactionsHistoryFileName = 'transactions.json'; -class ElectrumTransactionHistory = ElectrumTransactionHistoryBase - with _$ElectrumTransactionHistory; +class ElectrumTransactionHistory = ElectrumTransactionHistoryBase with _$ElectrumTransactionHistory; abstract class ElectrumTransactionHistoryBase extends TransactionHistoryBase with Store { - ElectrumTransactionHistoryBase( - {required this.walletInfo, required String password}) + ElectrumTransactionHistoryBase({required this.walletInfo, required String password}) : _password = password, _height = 0 { transactions = ObservableMap(); @@ -30,8 +28,7 @@ abstract class ElectrumTransactionHistoryBase Future init() async => await _load(); @override - void addOne(ElectrumTransactionInfo transaction) => - transactions[transaction.id] = transaction; + void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction; @override void addMany(Map transactions) => @@ -40,11 +37,13 @@ abstract class ElectrumTransactionHistoryBase @override Future save() async { try { - final dirPath = - await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); final path = '$dirPath/$transactionsHistoryFileName'; - final data = - json.encode({'height': _height, 'transactions': transactions}); + final txjson = {}; + for (final tx in transactions.entries) { + txjson[tx.key] = tx.value.toJson(); + } + final data = json.encode({'height': _height, 'transactions': txjson}); await writeData(path: path, password: _password, data: data); } catch (e) { print('Error while save bitcoin transaction history: ${e.toString()}'); @@ -57,8 +56,7 @@ abstract class ElectrumTransactionHistoryBase } Future> _read() async { - final dirPath = - await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); final path = '$dirPath/$transactionsHistoryFileName'; final content = await read(path: path, password: _password); return json.decode(content) as Map; @@ -84,7 +82,5 @@ abstract class ElectrumTransactionHistoryBase } } - void _update(ElectrumTransactionInfo transaction) => - transactions[transaction.id] = transaction; - + void _update(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction; } diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index 50896c837f..5564ce6724 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -160,11 +160,11 @@ class ElectrumTransactionInfo extends TransactionInfo { isPending: data['isPending'] as bool, confirmations: data['confirmations'] as int, to: data['to'] as String?, - unspents: data['unspent'] != null - ? (data['unspent'] as List) + unspents: data['unspents'] != null + ? (data['unspents'] as List) .map((unspent) => BitcoinUnspent.fromJSON( - BitcoinAddressRecord.fromJSON(unspent['address_record'] as String), - data['unspent'] as Map)) + BitcoinAddressRecord.fromJSON(unspent['address_record'].toString()), + unspent as Map)) .toList() : null, ); @@ -212,7 +212,7 @@ class ElectrumTransactionInfo extends TransactionInfo { m['confirmations'] = confirmations; m['fee'] = fee; m['to'] = to; - m['unspent'] = unspents?.map((e) => e.toJson()) ?? []; + m['unspents'] = unspents?.map((e) => e.toJson()).toList() ?? []; return m; } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 9f7e675646..8d68f6c8f0 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -183,7 +183,7 @@ abstract class ElectrumWalletBase startRefresh, ScanData( sendPort: receivePort.sendPort, - primarySilentAddress: walletAddresses.primarySilentAddress!, + silentAddress: walletAddresses.silentAddress!, network: network, height: height, chainTip: currentChainTip, @@ -211,25 +211,36 @@ abstract class ElectrumWalletBase final existingTxInfo = transactionHistory.transactions[txid]; if (existingTxInfo != null) { final newUnspents = tx.unspents! - .where((unspent) => !existingTxInfo.unspents!.any((element) => - element.hash.contains(unspent.hash) && element.vout == unspent.vout)) + .where((unspent) => !(existingTxInfo.unspents?.any((element) => + element.hash.contains(unspent.hash) && element.vout == unspent.vout) ?? + false)) .toList(); if (newUnspents.isNotEmpty) { existingTxInfo.unspents ??= []; existingTxInfo.unspents!.addAll(newUnspents); - existingTxInfo.amount += newUnspents.length > 1 + + final amount = newUnspents.length > 1 ? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent) : newUnspents[0].value; + + if (existingTxInfo.direction == TransactionDirection.incoming) { + existingTxInfo.amount += amount; + } else { + existingTxInfo.amount = amount; + existingTxInfo.direction = TransactionDirection.incoming; + } + transactionHistory.addOne(existingTxInfo); } } else { transactionHistory.addMany(message); - transactionHistory.save(); } + + await transactionHistory.save(); + await updateUnspent(); + await save(); } } - - updateUnspent(); } // check if is a SyncStatus type since "is SyncStatus" doesn't work here @@ -284,7 +295,6 @@ abstract class ElectrumWalletBase electrumClient.onConnectionStatusChange = (bool isConnected) async { if (!isConnected) { syncStatus = LostConnectionSyncStatus(); - await electrumClient.close(); if (attemptedReconnect == false) { await _electrumConnect(node, attemptedReconnect: true); } @@ -330,10 +340,10 @@ abstract class ElectrumWalletBase ECPrivate? privkey; if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { - privkey = walletAddresses.primarySilentAddress!.b_spend.clone().tweakAdd( + privkey = walletAddresses.silentAddress!.b_spend.clone().tweakAdd( BigintUtils.fromBytes(BytesUtils.fromHexString( (utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord) - .silentPaymentTweak)), + .silentPaymentTweak!)), ); } else { privkey = generateECPrivate( @@ -693,11 +703,7 @@ abstract class ElectrumWalletBase // Update unspents stored from scanned silent payment transactions transactionHistory.transactions.values.forEach((tx) { if (tx.unspents != null) { - if (!unspentCoins.any((utx) => - tx.unspents!.any((element) => utx.hash.contains(element.hash)) && - tx.unspents!.any((element) => utx.vout == element.vout))) { - updatedUnspentCoins.addAll(tx.unspents!); - } + updatedUnspentCoins.addAll(tx.unspents!); } }); @@ -799,8 +805,7 @@ abstract class ElectrumWalletBase (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body); time = status["block_time"] as int?; - final tip = await electrumClient.getCurrentBlockChainTip() ?? 0; - confirmations = tip - (status["block_height"] as int? ?? 0); + confirmations = currentChainTip! - (status["block_height"] as int? ?? 0); } else { final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); @@ -843,13 +848,13 @@ abstract class ElectrumWalletBase try { final Map historiesWithDetails = {}; final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); - final currentHeight = await electrumClient.getCurrentBlockChainTip() ?? 0; + currentChainTip ??= await electrumClient.getCurrentBlockChainTip() ?? 0; await Future.wait(ADDRESS_TYPES.map((type) { final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type); return Future.wait(addressesByType.map((addressRecord) async { - final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight); + final history = await _fetchAddressHistory(addressRecord, addressesSet, currentChainTip!); if (history.isNotEmpty) { addressRecord.txCount = history.length; @@ -866,7 +871,7 @@ abstract class ElectrumWalletBase matchedAddresses.toList(), addressRecord.isHidden, (address, addressesSet) => - _fetchAddressHistory(address, addressesSet, currentHeight) + _fetchAddressHistory(address, addressesSet, currentChainTip!) .then((history) => history.isNotEmpty ? address.address : null), type: type); } @@ -1064,16 +1069,14 @@ abstract class ElectrumWalletBase } Future _setInitialHeight() async { - if (walletInfo.restoreHeight == 0) { - currentChainTip = await electrumClient.getCurrentBlockChainTip(); - if (currentChainTip != null) walletInfo.restoreHeight = currentChainTip!; - } + currentChainTip = await electrumClient.getCurrentBlockChainTip(); + if (currentChainTip != null) walletInfo.restoreHeight = currentChainTip!; } } class ScanData { final SendPort sendPort; - final SilentPaymentOwner primarySilentAddress; + final SilentPaymentOwner silentAddress; final int height; final String node; final BasedUtxoNetwork network; @@ -1084,7 +1087,7 @@ class ScanData { ScanData({ required this.sendPort, - required this.primarySilentAddress, + required this.silentAddress, required this.height, required this.node, required this.network, @@ -1097,7 +1100,7 @@ class ScanData { factory ScanData.fromHeight(ScanData scanData, int newHeight) { return ScanData( sendPort: scanData.sendPort, - primarySilentAddress: scanData.primarySilentAddress, + silentAddress: scanData.silentAddress, height: newHeight, node: scanData.node, network: scanData.network, @@ -1119,7 +1122,7 @@ class SyncResponse { Future startRefresh(ScanData scanData) async { var cachedBlockchainHeight = scanData.chainTip; - Future connect() async { + Future getElectrumConnection() async { final electrumClient = scanData.electrumClient; if (!electrumClient.isConnected) { final node = scanData.node; @@ -1130,7 +1133,7 @@ Future startRefresh(ScanData scanData) async { Future getNodeHeightOrUpdate(int baseHeight) async { if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) { - final electrumClient = await connect(); + final electrumClient = await getElectrumConnection(); cachedBlockchainHeight = await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; @@ -1174,7 +1177,7 @@ Future startRefresh(ScanData scanData) async { } try { - final electrumClient = await connect(); + final electrumClient = await getElectrumConnection(); List? tweaks; try { @@ -1205,9 +1208,13 @@ Future startRefresh(ScanData scanData) async { final spb = SilentPaymentBuilder(receiverTweak: tweak); final result = spb.scanOutputs( - scanData.primarySilentAddress.b_scan, - scanData.primarySilentAddress.B_spend, - output_pubkeys.map((output) => output.toString()).toList(), + scanData.silentAddress.b_scan, + scanData.silentAddress.B_spend, + output_pubkeys + .map((output) => + BytesUtils.toHexString(BytesUtils.fromHexString(output.toString()).sublist(2)) + .toString()) + .toList(), precomputedLabels: scanData.labels, ); @@ -1226,15 +1233,12 @@ Future startRefresh(ScanData scanData) async { BitcoinUnspent? info; await Future.forEach>(listUnspent, (unspent) async { try { - final addressRecord = BitcoinSilentPaymentAddressRecord( - address, - index: 0, - isHidden: true, - isUsed: true, - network: scanData.network, - silentPaymentTweak: t_k, - type: SegwitAddresType.p2tr, - ); + final addressRecord = BitcoinSilentPaymentAddressRecord(address, + index: 0, + isHidden: true, + isUsed: true, + network: scanData.network, + silentPaymentTweak: t_k); info = BitcoinUnspent.fromJSON(addressRecord, unspent); } catch (_) {} }); @@ -1258,7 +1262,7 @@ Future startRefresh(ScanData scanData) async { isPending: false, date: DateTime.now(), confirmations: currentChainTip - syncHeight - 1, - to: scanData.primarySilentAddress.toString(), + to: scanData.silentAddress.toString(), unspents: [tx], ); @@ -1310,7 +1314,6 @@ Future startRefresh(ScanData scanData) async { }); } catch (_) {} } - // break; // Finished scanning block, add 1 to height and continue to next block in loop syncHeight += 1; diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 2bfeeeea05..5a839e3a58 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -30,10 +30,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { Map? initialRegularAddressIndex, Map? initialChangeAddressIndex, List? initialSilentAddresses, - int initialSilentAddressIndex = 0, - SilentPaymentOwner? silentAddress, + int initialSilentAddressIndex = 1, + bitcoin.HDWallet? masterHd, }) : _addresses = ObservableList.of((initialAddresses ?? []).toSet()), - primarySilentAddress = silentAddress, addressesByReceiveType = ObservableList.of(([]).toSet()), receiveAddresses = ObservableList.of((initialAddresses ?? []) @@ -51,6 +50,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { (initialSilentAddresses ?? []).toSet()), currentSilentAddressIndex = initialSilentAddressIndex, super(walletInfo) { + if (masterHd != null) { + silentAddress = SilentPaymentOwner.fromPrivateKeys( + b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privKey!), + b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!), + hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'); + + if (silentAddresses.length == 0) + silentAddresses.add(BitcoinSilentPaymentAddressRecord(silentAddress.toString(), + index: 1, isHidden: false, name: "", silentPaymentTweak: null, network: network)); + } + updateAddressesByMatch(); } @@ -70,7 +80,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final bitcoin.HDWallet mainHd; final bitcoin.HDWallet sideHd; - final SilentPaymentOwner? primarySilentAddress; + @observable + SilentPaymentOwner? silentAddress; @observable BitcoinAddressType _addressPageType = SegwitAddresType.p2wpkh; @@ -92,7 +103,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return activeSilentAddress!; } - return primarySilentAddress!.toString(); + return silentAddress.toString(); } String receiveAddress; @@ -123,7 +134,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @override set address(String addr) { if (addressPageType == SilentPaymentsAddresType.p2sp) { - activeSilentAddress = addr; + final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr); + + if (selected.silentPaymentTweak != null && silentAddress != null) { + activeSilentAddress = + silentAddress!.toLabeledSilentPaymentAddress(selected.index).toString(); + } else { + activeSilentAddress = silentAddress!.toString(); + } return; } @@ -225,27 +243,28 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { for (int i = 0; i < silentAddresses.length; i++) { final silentAddressRecord = silentAddresses[i]; final silentPaymentTweak = silentAddressRecord.silentPaymentTweak; - labels[G - .tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak))) - .toHex()] = silentPaymentTweak; + + if (silentPaymentTweak != null) + labels[G + .tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak))) + .toHex()] = silentPaymentTweak; } return labels; } @action BaseBitcoinAddressRecord generateNewAddress({String label = ''}) { - if (addressPageType == SilentPaymentsAddresType.p2sp) { + if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) { currentSilentAddressIndex += 1; final address = BitcoinSilentPaymentAddressRecord( - primarySilentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(), + silentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(), index: currentSilentAddressIndex, isHidden: false, name: label, silentPaymentTweak: - BytesUtils.toHexString(primarySilentAddress!.generateLabel(currentSilentAddressIndex)), + BytesUtils.toHexString(silentAddress!.generateLabel(currentSilentAddressIndex)), network: network, - type: SilentPaymentsAddresType.p2sp, ); silentAddresses.add(address); diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 3584b0fe3e..464e23843a 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -78,11 +78,9 @@ packages: bitcoin_base: dependency: "direct main" description: - path: "." - ref: cake-update-v2 - resolved-ref: "7634511b15e5a48bd18e3c19f81971628090c04f" - url: "https://github.com/cake-tech/bitcoin_base.git" - source: git + path: "/home/rafael/Working/bitcoin_base" + relative: false + source: path version: "4.0.0" bitcoin_flutter: dependency: "direct main" @@ -96,11 +94,9 @@ packages: blockchain_utils: dependency: "direct main" description: - path: "." - ref: cake-update-v1 - resolved-ref: "6a0b891db4d90c647ebf5fc3a9132e614c70e1c6" - url: "https://github.com/cake-tech/blockchain_utils" - source: git + path: "/home/rafael/Working/blockchain_utils" + relative: false + source: path version: "1.6.0" boolean_selector: dependency: transitive diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index bfa6c72e60..eafa008817 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -31,13 +31,9 @@ dependencies: unorm_dart: ^0.2.0 cryptography: ^2.0.5 bitcoin_base: - git: - url: https://github.com/cake-tech/bitcoin_base.git - ref: cake-update-v2 + path: /home/rafael/Working/bitcoin_base blockchain_utils: - git: - url: https://github.com/cake-tech/blockchain_utils - ref: cake-update-v1 + path: /home/rafael/Working/blockchain_utils dev_dependencies: flutter_test: diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index a967681503..c80df0458d 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -30,13 +30,9 @@ dependencies: url: https://github.com/cake-tech/bitbox-flutter.git ref: master bitcoin_base: - git: - url: https://github.com/cake-tech/bitcoin_base.git - ref: cake-update-v2 + path: /home/rafael/Working/bitcoin_base blockchain_utils: - git: - url: https://github.com/cake-tech/blockchain_utils - ref: cake-update-v1 + path: /home/rafael/Working/blockchain_utils dev_dependencies: flutter_test: diff --git a/scripts/android/app_env.fish b/scripts/android/app_env.fish new file mode 100644 index 0000000000..be82ab9e67 --- /dev/null +++ b/scripts/android/app_env.fish @@ -0,0 +1,75 @@ +#!/usr/bin/env fish + +set APP_ANDROID_NAME "" +set APP_ANDROID_VERSION "" +set APP_ANDROID_BUILD_VERSION "" +set APP_ANDROID_ID "" +set APP_ANDROID_PACKAGE "" +set APP_ANDROID_SCHEME "" + +set MONERO_COM "monero.com" +set CAKEWALLET "cakewallet" +set HAVEN "haven" + +set -l TYPES $MONERO_COM $CAKEWALLET $HAVEN +set APP_ANDROID_TYPE $argv[1] + +set MONERO_COM_NAME "Monero.com" +set MONERO_COM_VERSION "1.10.0" +set MONERO_COM_BUILD_NUMBER 72 +set MONERO_COM_BUNDLE_ID "com.monero.app" +set MONERO_COM_PACKAGE "com.monero.app" +set MONERO_COM_SCHEME "monero.com" + +set CAKEWALLET_NAME "Cake Wallet" +set CAKEWALLET_VERSION "4.13.0" +set CAKEWALLET_BUILD_NUMBER 189 +set CAKEWALLET_BUNDLE_ID "com.cakewallet.cake_wallet" +set CAKEWALLET_PACKAGE "com.cakewallet.cake_wallet" +set CAKEWALLET_SCHEME "cakewallet" + +set HAVEN_NAME "Haven" +set HAVEN_VERSION "1.0.0" +set HAVEN_BUILD_NUMBER 1 +set HAVEN_BUNDLE_ID "com.cakewallet.haven" +set HAVEN_PACKAGE "com.cakewallet.haven" + +if not contains $APP_ANDROID_TYPE $TYPES + echo "Wrong app type." + return 1 + exit 1 +end + +switch $APP_ANDROID_TYPE + case $MONERO_COM + set APP_ANDROID_NAME $MONERO_COM_NAME + set APP_ANDROID_VERSION $MONERO_COM_VERSION + set APP_ANDROID_BUILD_NUMBER $MONERO_COM_BUILD_NUMBER + set APP_ANDROID_BUNDLE_ID $MONERO_COM_BUNDLE_ID + set APP_ANDROID_PACKAGE $MONERO_COM_PACKAGE + set APP_ANDROID_SCHEME $MONERO_COM_SCHEME + ;; + case $CAKEWALLET + set APP_ANDROID_NAME $CAKEWALLET_NAME + set APP_ANDROID_VERSION $CAKEWALLET_VERSION + set APP_ANDROID_BUILD_NUMBER $CAKEWALLET_BUILD_NUMBER + set APP_ANDROID_BUNDLE_ID $CAKEWALLET_BUNDLE_ID + set APP_ANDROID_PACKAGE $CAKEWALLET_PACKAGE + set APP_ANDROID_SCHEME $CAKEWALLET_SCHEME + ;; + case $HAVEN + set APP_ANDROID_NAME $HAVEN_NAME + set APP_ANDROID_VERSION $HAVEN_VERSION + set APP_ANDROID_BUILD_NUMBER $HAVEN_BUILD_NUMBER + set APP_ANDROID_BUNDLE_ID $HAVEN_BUNDLE_ID + set APP_ANDROID_PACKAGE $HAVEN_PACKAGE + ;; +end + +export APP_ANDROID_TYPE +export APP_ANDROID_NAME +export APP_ANDROID_VERSION +export APP_ANDROID_BUILD_NUMBER +export APP_ANDROID_BUNDLE_ID +export APP_ANDROID_PACKAGE +export APP_ANDROID_SCHEME From 2db156ef16f33c3fe543f4737984a1bfa9eac9d3 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 1 Mar 2024 20:52:41 -0300 Subject: [PATCH 011/242] chore: deps --- cw_bitcoin/pubspec.lock | 16 ++++++++++------ cw_bitcoin/pubspec.yaml | 8 ++++++-- cw_bitcoin_cash/pubspec.yaml | 8 ++++++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 464e23843a..91af2d8071 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -78,9 +78,11 @@ packages: bitcoin_base: dependency: "direct main" description: - path: "/home/rafael/Working/bitcoin_base" - relative: false - source: path + path: "." + ref: cake-update-v2 + resolved-ref: f45e34d18ddff52764101a2ec96dcbc2be730555 + url: "https://github.com/cake-tech/bitcoin_base" + source: git version: "4.0.0" bitcoin_flutter: dependency: "direct main" @@ -94,9 +96,11 @@ packages: blockchain_utils: dependency: "direct main" description: - path: "/home/rafael/Working/blockchain_utils" - relative: false - source: path + path: "." + ref: cake-update-v1 + resolved-ref: "7864de88e9a0b598a61b1e50d26f6f4477a6411c" + url: "https://github.com/cake-tech/blockchain_utils" + source: git version: "1.6.0" boolean_selector: dependency: transitive diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index eafa008817..f6ee88e299 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -31,9 +31,13 @@ dependencies: unorm_dart: ^0.2.0 cryptography: ^2.0.5 bitcoin_base: - path: /home/rafael/Working/bitcoin_base + git: + url: https://github.com/cake-tech/bitcoin_base + ref: cake-update-v2 blockchain_utils: - path: /home/rafael/Working/blockchain_utils + git: + url: https://github.com/cake-tech/blockchain_utils + ref: cake-update-v1 dev_dependencies: flutter_test: diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index c80df0458d..4d500f048d 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -30,9 +30,13 @@ dependencies: url: https://github.com/cake-tech/bitbox-flutter.git ref: master bitcoin_base: - path: /home/rafael/Working/bitcoin_base + git: + url: https://github.com/cake-tech/bitcoin_base + ref: cake-update-v2 blockchain_utils: - path: /home/rafael/Working/blockchain_utils + git: + url: https://github.com/cake-tech/blockchain_utils + ref: cake-update-v1 dev_dependencies: flutter_test: From 23a368385388773a52b09a6351cbe0b07fc58057 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 5 Mar 2024 11:54:40 -0300 Subject: [PATCH 012/242] fix: label issues, clear spent utxo --- cw_bitcoin/lib/electrum_transaction_info.dart | 2 +- cw_bitcoin/lib/electrum_wallet.dart | 30 +++++++++++++++---- cw_bitcoin/lib/electrum_wallet_addresses.dart | 4 ++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index 5564ce6724..6254dc3ef2 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -163,7 +163,7 @@ class ElectrumTransactionInfo extends TransactionInfo { unspents: data['unspents'] != null ? (data['unspents'] as List) .map((unspent) => BitcoinUnspent.fromJSON( - BitcoinAddressRecord.fromJSON(unspent['address_record'].toString()), + BitcoinSilentPaymentAddressRecord.fromJSON(unspent['address_record'].toString()), unspent as Map)) .toList() : null, diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 8d68f6c8f0..0220c440e3 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -328,6 +328,7 @@ abstract class ElectrumWalletBase List vinOutpoints = []; List inputPrivKeyInfos = []; List inputPubKeys = []; + bool spendsSilentPayment = false; for (int i = 0; i < unspentCoins.length; i++) { final utx = unspentCoins[i]; @@ -345,6 +346,7 @@ abstract class ElectrumWalletBase (utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord) .silentPaymentTweak!)), ); + spendsSilentPayment = true; } else { privkey = generateECPrivate( hd: utx.bitcoinAddressRecord.isHidden @@ -481,7 +483,12 @@ abstract class ElectrumWalletBase } } - return EstimatedTxResult(utxos: utxos, privateKeys: privateKeys, fee: fee, amount: amount); + return EstimatedTxResult( + utxos: utxos, + privateKeys: privateKeys, + fee: fee, + amount: amount, + spendsSilentPayment: spendsSilentPayment); } @override @@ -559,6 +566,14 @@ abstract class ElectrumWalletBase network: network) ..addListener((transaction) async { transactionHistory.addOne(transaction); + if (estimatedTx.spendsSilentPayment) { + transactionHistory.transactions.values.forEach((tx) { + tx.unspents?.removeWhere( + (unspent) => estimatedTx.utxos.any((e) => e.utxo.txHash == unspent.hash)); + transactionHistory.addOne(tx); + }); + } + await updateBalance(); }); } catch (e) { @@ -1196,7 +1211,7 @@ Future startRefresh(ScanData scanData) async { for (var i = 0; i < tweaks!.length; i++) { try { final details = tweaks[i] as Map; - final output_pubkeys = (details["output_pubkeys"] as List); + final outputPubkeys = (details["output_pubkeys"] as List); final tweak = details["tweak"].toString(); // TODO: if tx already scanned & stored skip @@ -1210,7 +1225,7 @@ Future startRefresh(ScanData scanData) async { final result = spb.scanOutputs( scanData.silentAddress.b_scan, scanData.silentAddress.B_spend, - output_pubkeys + outputPubkeys .map((output) => BytesUtils.toHexString(BytesUtils.fromHexString(output.toString()).sublist(2)) .toString()) @@ -1235,7 +1250,7 @@ Future startRefresh(ScanData scanData) async { try { final addressRecord = BitcoinSilentPaymentAddressRecord(address, index: 0, - isHidden: true, + isHidden: false, isUsed: true, network: scanData.network, silentPaymentTweak: t_k); @@ -1332,12 +1347,17 @@ Future startRefresh(ScanData scanData) async { class EstimatedTxResult { EstimatedTxResult( - {required this.utxos, required this.privateKeys, required this.fee, required this.amount}); + {required this.utxos, + required this.privateKeys, + required this.fee, + required this.amount, + required this.spendsSilentPayment}); final List utxos; final List privateKeys; final int fee; final int amount; + final bool spendsSilentPayment; } BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network) { diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 5a839e3a58..cddb8f1f36 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -244,10 +244,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final silentAddressRecord = silentAddresses[i]; final silentPaymentTweak = silentAddressRecord.silentPaymentTweak; - if (silentPaymentTweak != null) + if (silentPaymentTweak != null && + SilentPaymentAddress.regex.hasMatch(silentAddressRecord.address)) { labels[G .tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak))) .toHex()] = silentPaymentTweak; + } } return labels; } From 0832e216381a594a7df718e31955b87149d7519c Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 5 Mar 2024 12:19:18 -0300 Subject: [PATCH 013/242] chore: deps --- pubspec_base.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec_base.yaml b/pubspec_base.yaml index d4bf981cd2..2a9321186e 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -109,8 +109,8 @@ dependencies: solana: ^0.30.1 bitcoin_base: git: - url: https://github.com/cake-tech/bitcoin_base.git - ref: cake-update-v1 + url: https://github.com/cake-tech/bitcoin_base + ref: cake-update-v2 dev_dependencies: flutter_test: From e8abd86d3c76cba543b7b804a0754691f1e6c499 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 5 Mar 2024 12:33:03 -0300 Subject: [PATCH 014/242] fix: build --- lib/bitcoin/cw_bitcoin.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 3e86ad3815..b27605856d 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -181,7 +181,7 @@ class CWBitcoin extends Bitcoin { } @override - ReceivePageOption getSelectedAddressType(Object wallet) { + BitcoinReceivePageOption getSelectedAddressType(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType); } @@ -210,6 +210,7 @@ class CWBitcoin extends Bitcoin { default: return SegwitAddresType.p2wpkh; } + } List getSilentAddresses(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; From 4308e3e12303fc063444683c21ed7ccb43afc88a Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 5 Mar 2024 13:53:06 -0300 Subject: [PATCH 015/242] fix: missing types --- lib/bitcoin/cw_bitcoin.dart | 8 ++++++++ lib/src/screens/dashboard/pages/address_page.dart | 4 ++-- tool/configure.dart | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index b27605856d..3543ef5cc9 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -216,4 +216,12 @@ class CWBitcoin extends Bitcoin { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.walletAddresses.silentAddresses; } + + bool isBitcoinReceivePageOption(ReceivePageOption option) { + return option is BitcoinReceivePageOption; + } + + BitcoinAddressType getOptionToType(ReceivePageOption option) { + return (option as BitcoinReceivePageOption).toType(); + } } diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart index 044866f5ce..4d08cde8bb 100644 --- a/lib/src/screens/dashboard/pages/address_page.dart +++ b/lib/src/screens/dashboard/pages/address_page.dart @@ -198,8 +198,8 @@ class AddressPage extends BasePage { } reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { - if (option is BitcoinReceivePageOption) { - addressListViewModel.setAddressType(option.toType()); + if (bitcoin!.isBitcoinReceivePageOption(option)) { + addressListViewModel.setAddressType(bitcoin!.getOptionToType(option)); return; } diff --git a/tool/configure.dart b/tool/configure.dart index 10b573655e..6d5d8e7bb5 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -149,6 +149,8 @@ abstract class Bitcoin { BitcoinAddressType getBitcoinAddressType(ReceivePageOption option); bool hasSelectedSilentPayments(Object wallet); List getBitcoinReceivePageOptions(); + bool isBitcoinReceivePageOption(ReceivePageOption option); + BitcoinAddressType getOptionToType(ReceivePageOption option); } """; From dd532b4fe0e175f85e3d5c4692b2b45810404ac7 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 11 Mar 2024 09:46:20 -0300 Subject: [PATCH 016/242] feat: new electrs API & changes, fixes for last block scanning --- cw_bitcoin/lib/bitcoin_address_record.dart | 7 +- cw_bitcoin/lib/electrum.dart | 7 +- cw_bitcoin/lib/electrum_balance.dart | 9 +- cw_bitcoin/lib/electrum_wallet.dart | 287 +++++++++--------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 12 +- cw_bitcoin/pubspec.lock | 2 +- cw_core/lib/sync_status.dart | 7 + lib/core/sync_status_title.dart | 4 + lib/entities/default_settings_migration.dart | 4 +- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 35 files changed, 211 insertions(+), 154 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index f5731f0f12..c6d5cfe2c2 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -126,7 +126,8 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { super.isUsed = false, required this.silentPaymentTweak, required super.network, - }) : super(type: SilentPaymentsAddresType.p2sp); + required super.type, + }) : super(); factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) { @@ -144,6 +145,10 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { ? network : BasedUtxoNetwork.fromName(decoded['network'] as String), silentPaymentTweak: decoded['silent_payment_tweak'] as String?, + type: decoded['type'] != null && decoded['type'] != '' + ? BitcoinAddressType.values + .firstWhere((type) => type.toString() == decoded['type'] as String) + : SilentPaymentsAddresType.p2sp, ); } diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index d9e068d654..4aefa3bd11 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -279,8 +279,11 @@ class ElectrumClient { Future> getHeader({required int height}) async => await call(method: 'blockchain.block.get_header', params: [height]) as Map; - Future> getTweaks({required int height}) async => - await callWithTimeout(method: 'blockchain.block.tweaks', params: [height]) as List; + Future> getTweaks({required int height, required int count}) async => + await callWithTimeout( + method: 'blockchain.block.tweaks', + params: [height, count], + timeout: 10000) as Map; Future estimatefee({required int p}) => call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) { diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart index 165ea447e8..45de7de6d5 100644 --- a/cw_bitcoin/lib/electrum_balance.dart +++ b/cw_bitcoin/lib/electrum_balance.dart @@ -3,8 +3,11 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_core/balance.dart'; class ElectrumBalance extends Balance { - const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen}) - : super(confirmed, unconfirmed); + ElectrumBalance({ + required this.confirmed, + required this.unconfirmed, + required this.frozen, + }) : super(confirmed, unconfirmed); static ElectrumBalance? fromJSON(String? jsonSource) { if (jsonSource == null) { @@ -19,7 +22,7 @@ class ElectrumBalance extends Balance { frozen: decoded['frozen'] as int? ?? 0); } - final int confirmed; + int confirmed; final int unconfirmed; final int frozen; diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 0220c440e3..e0bdfde285 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -44,6 +44,8 @@ import 'package:http/http.dart' as http; part 'electrum_wallet.g.dart'; +const SCANNING_BLOCK_COUNT = 50; + class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; abstract class ElectrumWalletBase @@ -72,8 +74,12 @@ abstract class ElectrumWalletBase _scripthashesUpdateSubject = {}, balance = ObservableMap.of(currency != null ? { - currency: - initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0) + currency: initialBalance ?? + ElectrumBalance( + confirmed: 0, + unconfirmed: 0, + frozen: 0, + ) } : {}), this.unspentCoinsInfo = unspentCoinsInfo, @@ -194,9 +200,9 @@ abstract class ElectrumWalletBase )); await for (var message in receivePort) { - if (message is bool) { + if (message is bool && message == false) { nodeSupportsSilentPayments = message; - syncStatus = UnsupportedSyncStatus(); + syncStatus = TimedOutSyncStatus(); } if (message is Map) { @@ -238,7 +244,7 @@ abstract class ElectrumWalletBase await transactionHistory.save(); await updateUnspent(); - await save(); + await updateBalance(); } } } @@ -320,7 +326,6 @@ abstract class ElectrumWalletBase {int? inputsCount, bool? hasSilentPayment}) async { final utxos = []; - List privateKeys = []; var leftAmount = credentialsAmount; var allInputsAmount = 0; @@ -340,13 +345,15 @@ abstract class ElectrumWalletBase final address = _addressTypeFromStr(utx.address, network); ECPrivate? privkey; + bool? isSilentPayment = false; if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { - privkey = walletAddresses.silentAddress!.b_spend.clone().tweakAdd( - BigintUtils.fromBytes(BytesUtils.fromHexString( - (utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord) - .silentPaymentTweak!)), - ); + privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( + BigintUtils.fromBytes(BytesUtils.fromHexString( + (utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord) + .silentPaymentTweak!)), + ); spendsSilentPayment = true; + isSilentPayment = true; } else { privkey = generateECPrivate( hd: utx.bitcoinAddressRecord.isHidden @@ -356,7 +363,6 @@ abstract class ElectrumWalletBase network: network); } - privateKeys.add(privkey); inputPrivKeyInfos.add(ECPrivateInfo(privkey, address.type == SegwitAddresType.p2tr)); inputPubKeys.add(privkey.getPublic()); vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); @@ -368,6 +374,7 @@ abstract class ElectrumWalletBase value: BigInt.from(utx.value), vout: utx.vout, scriptType: _getScriptType(address), + isSilentPayment: isSilentPayment, ), ownerDetails: UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: address), @@ -485,7 +492,7 @@ abstract class ElectrumWalletBase return EstimatedTxResult( utxos: utxos, - privateKeys: privateKeys, + inputPrivKeyInfos: inputPrivKeyInfos, fee: fee, amount: amount, spendsSilentPayment: spendsSilentPayment); @@ -535,8 +542,13 @@ abstract class ElectrumWalletBase } final estimatedTx = await _estimateTxFeeAndInputsToUse( - credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials, - hasSilentPayment: hasSilentPayment); + credentialsAmount, + sendAll, + outputAddresses, + outputs, + transactionCredentials, + hasSilentPayment: hasSilentPayment, + ); final txb = BitcoinTransactionBuilder( utxos: estimatedTx.utxos, @@ -545,17 +557,21 @@ abstract class ElectrumWalletBase network: network); final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) { - final key = estimatedTx.privateKeys - .firstWhereOrNull((element) => element.getPublic().toHex() == publicKey); + final key = estimatedTx.inputPrivKeyInfos + .firstWhereOrNull((element) => element.privkey.getPublic().toHex() == publicKey); if (key == null) { throw Exception("Cannot find private key"); } if (utxo.utxo.isP2tr()) { - return key.signTapRoot(txDigest, sighash: sighash); + return key.privkey.signTapRoot( + txDigest, + sighash: sighash, + tweak: utxo.utxo.isSilentPayment != true, + ); } else { - return key.signInput(txDigest, sigHash: sighash); + return key.privkey.signInput(txDigest, sigHash: sighash); } }); @@ -820,7 +836,9 @@ abstract class ElectrumWalletBase (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body); time = status["block_time"] as int?; - confirmations = currentChainTip! - (status["block_height"] as int? ?? 0); + final height = status["block_height"] as int? ?? 0; + confirmations = + height > 0 ? (await electrumClient.getCurrentBlockChainTip())! - height + 1 : 0; } else { final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); @@ -1056,6 +1074,19 @@ abstract class ElectrumWalletBase Future updateBalance() async { balance[currency] = await _fetchBalances(); + + // Update balance stored from scanned silent payment transactions + try { + transactionHistory.transactions.values.forEach((tx) { + if (tx.unspents != null) { + balance[currency]!.confirmed += tx.unspents! + .where((unspent) => unspent.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) + .map((e) => e.value) + .reduce((value, element) => value + element); + } + }); + } catch (_) {} + await save(); } @@ -1194,9 +1225,9 @@ Future startRefresh(ScanData scanData) async { try { final electrumClient = await getElectrumConnection(); - List? tweaks; + Map? tweaks; try { - tweaks = await electrumClient.getTweaks(height: syncHeight); + tweaks = await electrumClient.getTweaks(height: syncHeight, count: SCANNING_BLOCK_COUNT); } catch (e) { if (e is RequestFailedTimeoutException) { return scanData.sendPort.send(false); @@ -1208,133 +1239,103 @@ Future startRefresh(ScanData scanData) async { SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); } - for (var i = 0; i < tweaks!.length; i++) { + final blockHeights = tweaks!.keys; + for (var i = 0; i < blockHeights.length; i++) { try { - final details = tweaks[i] as Map; - final outputPubkeys = (details["output_pubkeys"] as List); - final tweak = details["tweak"].toString(); - - // TODO: if tx already scanned & stored skip - // if (scanData.transactionHistoryIds.contains(txid)) { - // // already scanned tx, continue to next tx - // pos++; - // continue; - // } - - final spb = SilentPaymentBuilder(receiverTweak: tweak); - final result = spb.scanOutputs( - scanData.silentAddress.b_scan, - scanData.silentAddress.B_spend, - outputPubkeys - .map((output) => - BytesUtils.toHexString(BytesUtils.fromHexString(output.toString()).sublist(2)) - .toString()) - .toList(), - precomputedLabels: scanData.labels, - ); + final blockHeight = blockHeights.elementAt(i).toString(); + final blockTweaks = tweaks[blockHeight] as Map; + + for (var j = 0; j < blockTweaks.keys.length; j++) { + final txid = blockTweaks.keys.elementAt(j); + final details = blockTweaks[txid] as Map; + final outputPubkeys = (details["output_pubkeys"] as Map); + final tweak = details["tweak"].toString(); + + final spb = SilentPaymentBuilder(receiverTweak: tweak); + final addToWallet = spb.scanOutputs( + scanData.silentAddress.b_scan, + scanData.silentAddress.B_spend, + outputPubkeys.values + .map((o) => getScriptFromOutput( + o["pubkey"].toString(), int.parse(o["amount"].toString()))) + .toList(), + precomputedLabels: scanData.labels, + ); - if (result.isEmpty) { - // no results tx, continue to next tx - continue; - } + if (addToWallet.isEmpty) { + // no results tx, continue to next tx + continue; + } - result.forEach((key, value) async { - final t_k = value[0]; - final address = ECPublic.fromHex(key).toTaprootAddress().toAddress(scanData.network); + addToWallet.forEach((key, value) async { + final t_k = value.tweak; + + final addressRecord = BitcoinSilentPaymentAddressRecord( + value.output.address.toAddress(scanData.network), + index: 0, + isHidden: false, + isUsed: true, + network: scanData.network, + silentPaymentTweak: t_k, + type: SegwitAddresType.p2tr, + ); - final listUnspent = - await electrumClient.getListUnspentWithAddress(address, scanData.network); + int? amount; + int? pos; + outputPubkeys.entries.firstWhere((k) { + final matches = k.value["pubkey"] == key; + if (matches) { + amount = int.parse(k.value["amount"].toString()); + pos = int.parse(k.key.toString()); + return true; + } + return false; + }); + + final json = { + 'address_record': addressRecord.toJSON(), + 'tx_hash': txid, + 'value': amount!, + 'tx_pos': pos!, + 'silent_payment_tweak': t_k, + }; + + final tx = BitcoinUnspent.fromJSON(addressRecord, json); + + final txInfo = ElectrumTransactionInfo( + WalletType.bitcoin, + id: tx.hash, + height: syncHeight, + amount: 0, // will be added later via unspent + fee: 0, + direction: TransactionDirection.incoming, + isPending: false, + date: DateTime.now(), + confirmations: currentChainTip - syncHeight - 1, + to: value.label != null + ? SilentPaymentAddress( + version: scanData.silentAddress.version, + B_scan: scanData.silentAddress.B_scan + .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(value.tweak))), + B_spend: scanData.silentAddress.B_spend, + hrp: scanData.silentAddress.hrp, + ).toString() + : scanData.silentAddress.toString(), + unspents: [tx], + ); - BitcoinUnspent? info; - await Future.forEach>(listUnspent, (unspent) async { - try { - final addressRecord = BitcoinSilentPaymentAddressRecord(address, - index: 0, - isHidden: false, - isUsed: true, - network: scanData.network, - silentPaymentTweak: t_k); - info = BitcoinUnspent.fromJSON(addressRecord, unspent); - } catch (_) {} + scanData.sendPort.send({txInfo.id: txInfo}); }); + } + } catch (e, s) { + print([e, s]); + } - if (info == null) { - return; - } - - // final tweak = value[0]; - // String? label; - // if (value.length > 1) label = value[1]; - - final tx = info!; - final txInfo = ElectrumTransactionInfo( - WalletType.bitcoin, - id: tx.hash, - height: syncHeight, - amount: 0, // will be added later via unspent - fee: 0, - direction: TransactionDirection.incoming, - isPending: false, - date: DateTime.now(), - confirmations: currentChainTip - syncHeight - 1, - to: scanData.silentAddress.toString(), - unspents: [tx], - ); - - // bool spent = false; - // for (final s in status) { - // if ((s["spent"] as bool) == true) { - // spent = true; - - // scanData.sendPort.send({txid: txInfo}); - - // final sentTxId = s["txid"] as String; - // final sentTx = json.decode( - // (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$sentTxId"))) - // .body); - - // int amount = 0; - // for (final out in (sentTx["vout"] as List)) { - // amount += out["value"] as int; - // } - - // final height = s["status"]["block_height"] as int; - - // scanData.sendPort.send({ - // sentTxId: ElectrumTransactionInfo( - // WalletType.bitcoin, - // id: sentTxId, - // height: height, - // amount: amount, - // fee: 0, - // direction: TransactionDirection.outgoing, - // isPending: false, - // date: DateTime.fromMillisecondsSinceEpoch( - // (s["status"]["block_time"] as int) * 1000), - // confirmations: currentChainTip - height, - // ) - // }); - // } - // } - - // if (spent) { - // return; - // } - - // found utxo for tx, send unspent coin to main isolate - // scanData.sendPort.send(txInfo); - - // also send tx data for tx history - scanData.sendPort.send({txInfo.id: txInfo}); - }); - } catch (_) {} + // Finished scanning block, add 1 to height and continue to next block in loop + syncHeight += 1; + scanData.sendPort.send(SyncResponse(syncHeight, + SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); } - - // Finished scanning block, add 1 to height and continue to next block in loop - syncHeight += 1; - currentChainTip = await getNodeHeightOrUpdate(syncHeight); - scanData.sendPort.send(SyncResponse(syncHeight, - SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); } catch (e, stacktrace) { print(stacktrace); print(e.toString()); @@ -1348,13 +1349,13 @@ Future startRefresh(ScanData scanData) async { class EstimatedTxResult { EstimatedTxResult( {required this.utxos, - required this.privateKeys, + required this.inputPrivKeyInfos, required this.fee, required this.amount, required this.spendsSilentPayment}); final List utxos; - final List privateKeys; + final List inputPrivKeyInfos; final int fee; final int amount; final bool spendsSilentPayment; diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index cddb8f1f36..270ad855fc 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -57,8 +57,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'); if (silentAddresses.length == 0) - silentAddresses.add(BitcoinSilentPaymentAddressRecord(silentAddress.toString(), - index: 1, isHidden: false, name: "", silentPaymentTweak: null, network: network)); + silentAddresses.add(BitcoinSilentPaymentAddressRecord( + silentAddress.toString(), + index: 1, + isHidden: false, + name: "", + silentPaymentTweak: null, + network: network, + type: SilentPaymentsAddresType.p2sp, + )); } updateAddressesByMatch(); @@ -267,6 +274,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { silentPaymentTweak: BytesUtils.toHexString(silentAddress!.generateLabel(currentSilentAddressIndex)), network: network, + type: SilentPaymentsAddresType.p2sp, ); silentAddresses.add(address); diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 91af2d8071..8d6f6268b7 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -80,7 +80,7 @@ packages: description: path: "." ref: cake-update-v2 - resolved-ref: f45e34d18ddff52764101a2ec96dcbc2be730555 + resolved-ref: fb4c0a0b6cf24628ddad7d3cdc58e4c918eff714 url: "https://github.com/cake-tech/bitcoin_base" source: git version: "4.0.0" diff --git a/cw_core/lib/sync_status.dart b/cw_core/lib/sync_status.dart index f562e7ce32..dd2d9ca67b 100644 --- a/cw_core/lib/sync_status.dart +++ b/cw_core/lib/sync_status.dart @@ -63,6 +63,13 @@ class UnsupportedSyncStatus extends SyncStatus { double progress() => 1.0; } +class TimedOutSyncStatus extends SyncStatus { + @override + double progress() => 1.0; + @override + String toString() => 'Timed out'; +} + class LostConnectionSyncStatus extends SyncStatus { @override double progress() => 1.0; diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index 23bfb0db28..c743caf55d 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -40,5 +40,9 @@ String syncStatusTitle(SyncStatus syncStatus) { return S.current.sync_status_unsupported; } + if (syncStatus is TimedOutSyncStatus) { + return S.current.sync_status_timed_out; + } + return ''; } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index fb3e9e80ce..d12140cb99 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -23,8 +23,8 @@ import 'package:collection/collection.dart'; const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081'; const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002'; -const publicBitcoinTestnetElectrumAddress = 'electrum.blockstream.info'; -const publicBitcoinTestnetElectrumPort = '60002'; +const publicBitcoinTestnetElectrumAddress = '198.58.111.154'; +const publicBitcoinTestnetElectrumPort = '50002'; const publicBitcoinTestnetElectrumUri = '$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort'; const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index b02e3604f6..452b20933b 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "بدء المزامنة", "sync_status_syncronized": "متزامن", "sync_status_syncronizing": "يتم المزامنة", + "sync_status_timed_out": "نفد وقته", "sync_status_unsupported": "عقدة غير مدعومة", "syncing_wallet_alert_content": "قد لا يكتمل رصيدك وقائمة المعاملات الخاصة بك حتى تظهر عبارة “SYNCHRONIZED“ في الأعلى. انقر / اضغط لمعرفة المزيد.", "syncing_wallet_alert_title": "محفظتك تتم مزامنتها", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 89c4cc24b6..1f02060ec1 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "ЗАПОЧВАНЕ НА СИНХРОНИЗАЦИЯ", "sync_status_syncronized": "СИНХРОНИЗИРАНО", "sync_status_syncronizing": "СИНХРОНИЗИРАНЕ", + "sync_status_timed_out": "ВРЕМЕТО ИЗТЕЧЕ", "sync_status_unsupported": "Неподдържан възел", "syncing_wallet_alert_content": "Списъкът ви с баланс и транзакции може да не е пълен, докато в горната част не пише „СИНХРОНИЗИРАН“. Кликнете/докоснете, за да научите повече.", "syncing_wallet_alert_title": "Вашият портфейл се синхронизира", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index e536a73380..cf045754d4 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "SPOUŠTĚNÍ SYNCHRONIZACE", "sync_status_syncronized": "SYNCHRONIZOVÁNO", "sync_status_syncronizing": "SYNCHRONIZUJI", + "sync_status_timed_out": "ČAS VYPRŠEL", "sync_status_unsupported": "Nepodporovaný uzel", "syncing_wallet_alert_content": "Váš seznam zůstatků a transakcí nemusí být úplný, dokud nebude nahoře uvedeno „SYNCHRONIZOVANÉ“. Kliknutím/klepnutím se dozvíte více.", "syncing_wallet_alert_title": "Vaše peněženka se synchronizuje", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index f1179fc8a2..2ab5e6c9fd 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "STARTE SYNCHRONISIERUNG", "sync_status_syncronized": "SYNCHRONISIERT", "sync_status_syncronizing": "SYNCHRONISIERE", + "sync_status_timed_out": "Zeitlich abgestimmt", "sync_status_unsupported": "Nicht unterstützter Knoten", "syncing_wallet_alert_content": "Ihr Kontostand und Ihre Transaktionsliste sind möglicherweise erst vollständig, wenn oben „SYNCHRONISIERT“ steht. Klicken/tippen Sie, um mehr zu erfahren.", "syncing_wallet_alert_title": "Ihr Wallet wird synchronisiert", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 5b77a4c611..78d7dd7777 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "STARTING SYNC", "sync_status_syncronized": "SYNCHRONIZED", "sync_status_syncronizing": "SYNCHRONIZING", + "sync_status_timed_out": "TIMED OUT", "sync_status_unsupported": "UNSUPPORTED NODE", "syncing_wallet_alert_content": "Your balance and transaction list may not be complete until it says “SYNCHRONIZED” at the top. Click/tap to learn more.", "syncing_wallet_alert_title": "Your wallet is syncing", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index c27c8829ba..f1c27763c7 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "EMPEZANDO A SINCRONIZAR", "sync_status_syncronized": "SINCRONIZADO", "sync_status_syncronizing": "SINCRONIZANDO", + "sync_status_timed_out": "CADUCADO", "sync_status_unsupported": "Nodo no compatible", "syncing_wallet_alert_content": "Es posible que su lista de saldo y transacciones no esté completa hasta que diga \"SINCRONIZADO\" en la parte superior. Haga clic/toque para obtener más información.", "syncing_wallet_alert_title": "Tu billetera se está sincronizando", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 8068c67a92..7f2ed7799b 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "DÉBUT DE SYNCHRO", "sync_status_syncronized": "SYNCHRONISÉ", "sync_status_syncronizing": "SYNCHRONISATION EN COURS", + "sync_status_timed_out": "FIN DU TEMPS", "sync_status_unsupported": "Nœud non pris en charge", "syncing_wallet_alert_content": "Votre solde et votre liste de transactions peuvent ne pas être à jour tant que la mention « SYNCHRONISÉ » n'apparaît en haut de l'écran. Cliquez/appuyez pour en savoir plus.", "syncing_wallet_alert_title": "Votre portefeuille (wallet) est en cours de synchronisation", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 5b79f8e9e3..5a52af0010 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -638,6 +638,7 @@ "sync_status_starting_sync": "KWAFI", "sync_status_syncronized": "KYAU", "sync_status_syncronizing": "KWAFI", + "sync_status_timed_out": "ATED Out", "sync_status_unsupported": "Ba a Taimako ba", "syncing_wallet_alert_content": "Ma'aunin ku da lissafin ma'amala bazai cika ba har sai an ce \"SYNCHRONIZED\" a saman. Danna/matsa don ƙarin koyo.", "syncing_wallet_alert_title": "Walat ɗin ku yana aiki tare", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 35b5d5d01b..1aa9d3ca0e 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -638,6 +638,7 @@ "sync_status_starting_sync": "सिताज़ा करना", "sync_status_syncronized": "सिंक्रनाइज़", "sync_status_syncronizing": "सिंक्रनाइज़ करने", + "sync_status_timed_out": "समय समााप्त", "sync_status_unsupported": "असमर्थित नोड", "syncing_wallet_alert_content": "आपकी शेष राशि और लेनदेन सूची तब तक पूरी नहीं हो सकती जब तक कि शीर्ष पर \"सिंक्रनाइज़्ड\" न लिखा हो। अधिक जानने के लिए क्लिक/टैप करें।", "syncing_wallet_alert_title": "आपका वॉलेट सिंक हो रहा है", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 4bbcdd4700..b8bc9f3b68 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "ZAPOČINJEMO SINKRONIZIRANJE", "sync_status_syncronized": "SINKRONIZIRANO", "sync_status_syncronizing": "SINKRONIZIRANJE", + "sync_status_timed_out": "ISTEKLO", "sync_status_unsupported": "Nepodržani čvor", "syncing_wallet_alert_content": "Vaš saldo i popis transakcija možda neće biti potpuni sve dok na vrhu ne piše \"SINKRONIZIRANO\". Kliknite/dodirnite da biste saznali više.", "syncing_wallet_alert_title": "Vaš novčanik se sinkronizira", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 04e85b5b88..a6f11c1ff7 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -639,6 +639,7 @@ "sync_status_starting_sync": "MULAI SINKRONISASI", "sync_status_syncronized": "SUDAH TERSINKRONISASI", "sync_status_syncronizing": "SEDANG SINKRONISASI", + "sync_status_timed_out": "WAKTU HABIS", "sync_status_unsupported": "Node yang tidak didukung", "syncing_wallet_alert_content": "Saldo dan daftar transaksi Anda mungkin belum lengkap sampai tertulis “SYNCHRONIZED” di bagian atas. Klik/ketuk untuk mempelajari lebih lanjut.", "syncing_wallet_alert_title": "Dompet Anda sedang disinkronkan", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 07be979386..7bdba86d14 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -638,6 +638,7 @@ "sync_status_starting_sync": "INIZIO SINC", "sync_status_syncronized": "SINCRONIZZATO", "sync_status_syncronizing": "SINCRONIZZAZIONE", + "sync_status_timed_out": "FUORI TEMPO", "sync_status_unsupported": "Nodo non supportato", "syncing_wallet_alert_content": "Il saldo e l'elenco delle transazioni potrebbero non essere completi fino a quando non viene visualizzato \"SYNCHRONIZED\" in alto. Clicca/tocca per saperne di più.", "syncing_wallet_alert_title": "Il tuo portafoglio si sta sincronizzando", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 0b54614018..84c780b4bc 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "同期の開始", "sync_status_syncronized": "同期された", "sync_status_syncronizing": "同期", + "sync_status_timed_out": "タイムアウトしました", "sync_status_unsupported": "サポートされていないノード", "syncing_wallet_alert_content": "上部に「同期済み」と表示されるまで、残高と取引リストが完了していない可能性があります。詳細については、クリック/タップしてください。", "syncing_wallet_alert_title": "ウォレットは同期中です", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index a30498bf20..4adf10d43c 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "동기화 시작", "sync_status_syncronized": "동기화", "sync_status_syncronizing": "동기화", + "sync_status_timed_out": "시간 초과", "sync_status_unsupported": "지원되지 않은 노드", "syncing_wallet_alert_content": "상단에 \"동기화됨\"이라고 표시될 때까지 잔액 및 거래 목록이 완전하지 않을 수 있습니다. 자세히 알아보려면 클릭/탭하세요.", "syncing_wallet_alert_title": "지갑 동기화 중", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index d4e85a1953..92a1784aa3 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "စင့်ခ်လုပ်ခြင်း။", "sync_status_syncronized": "ထပ်တူပြုထားသည်။", "sync_status_syncronizing": "ထပ်တူပြုခြင်း။", + "sync_status_timed_out": "ထွက်အချိန်ကုန်", "sync_status_unsupported": "node မထောက်ပံ့ node ကို", "syncing_wallet_alert_content": "သင်၏လက်ကျန်နှင့် ငွေပေးငွေယူစာရင်းသည် ထိပ်တွင် \"Synchronizeed\" ဟုပြောသည်အထိ မပြီးမြောက်နိုင်ပါ။ ပိုမိုလေ့လာရန် နှိပ်/နှိပ်ပါ။", "syncing_wallet_alert_title": "သင့်ပိုက်ဆံအိတ်ကို စင့်ခ်လုပ်နေပါသည်။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 89c3054418..dd5445fdf0 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "BEGINNEN MET SYNCHRONISEREN", "sync_status_syncronized": "SYNCHRONIZED", "sync_status_syncronizing": "SYNCHRONISEREN", + "sync_status_timed_out": "Uitgeput", "sync_status_unsupported": "Niet ondersteund knooppunt", "syncing_wallet_alert_content": "Uw saldo- en transactielijst is mogelijk pas compleet als er bovenaan 'GESYNCHRONISEERD' staat. Klik/tik voor meer informatie.", "syncing_wallet_alert_title": "Uw portemonnee wordt gesynchroniseerd", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 3f301b84c0..4f5e6c279c 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "ROZPOCZĘCIE SYNCHRONIZACJI", "sync_status_syncronized": "ZSYNCHRONIZOWANO", "sync_status_syncronizing": "SYNCHRONIZACJA", + "sync_status_timed_out": "PRZEKROCZONO LIMIT CZASU", "sync_status_unsupported": "Nieobsługiwany węzeł", "syncing_wallet_alert_content": "Twoje saldo i lista transakcji mogą nie być kompletne, dopóki u góry nie pojawi się napis „SYNCHRONIZOWANY”. Kliknij/stuknij, aby dowiedzieć się więcej.", "syncing_wallet_alert_title": "Twój portfel się synchronizuje", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 1fa193b487..1621b2d963 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -638,6 +638,7 @@ "sync_status_starting_sync": "INICIANDO SINCRONIZAÇÃO", "sync_status_syncronized": "SINCRONIZADO", "sync_status_syncronizing": "SINCRONIZANDO", + "sync_status_timed_out": "TEMPO ESGOTADO", "sync_status_unsupported": "Nó não suportado", "syncing_wallet_alert_content": "Seu saldo e lista de transações podem não estar completos até que diga “SYNCHRONIZED” no topo. Clique/toque para saber mais.", "syncing_wallet_alert_title": "Sua carteira está sincronizando", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index b8a58defec..0e8c6ab855 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "НАЧАЛО СИНХРОНИЗАЦИИ", "sync_status_syncronized": "СИНХРОНИЗИРОВАН", "sync_status_syncronizing": "СИНХРОНИЗАЦИЯ", + "sync_status_timed_out": "ВРЕМЯ ВЫШЛО", "sync_status_unsupported": "Неподдерживаемый узел", "syncing_wallet_alert_content": "Ваш баланс и список транзакций могут быть неполными, пока вверху не будет написано «СИНХРОНИЗИРОВАНО». Щелкните/коснитесь, чтобы узнать больше.", "syncing_wallet_alert_title": "Ваш кошелек синхронизируется", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 3cb70a13d7..e5ce652e6f 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "กำลังเริ่มซิงโครไนซ์", "sync_status_syncronized": "ซิงโครไนซ์แล้ว", "sync_status_syncronizing": "กำลังซิงโครไนซ์", + "sync_status_timed_out": "หมดเวลา", "sync_status_unsupported": "โหนดที่ไม่ได้รับการสนับสนุน", "syncing_wallet_alert_content": "รายการยอดเงินและธุรกรรมของคุณอาจไม่สมบูรณ์จนกว่าจะมีข้อความว่า “ซิงโครไนซ์” ที่ด้านบน คลิก/แตะเพื่อเรียนรู้เพิ่มเติม่", "syncing_wallet_alert_title": "กระเป๋าสตางค์ของคุณกำลังซิงค์", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index f7d10bc0c5..f032293301 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "Simula sa pag -sync", "sync_status_syncronized": "Naka -synchronize", "sync_status_syncronizing": "Pag -synchronize", + "sync_status_timed_out": "Nag -time out", "sync_status_unsupported": "Hindi suportadong node", "syncing_wallet_alert_content": "Ang iyong balanse at listahan ng transaksyon ay maaaring hindi kumpleto hanggang sa sabihin nito na \"naka -synchronize\" sa tuktok. Mag -click/tap upang malaman ang higit pa.", "syncing_wallet_alert_title": "Ang iyong pitaka ay nag -sync", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index fcd63448f8..bceec8f61c 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "SENKRONİZE BAŞLATILIYOR", "sync_status_syncronized": "SENKRONİZE EDİLDİ", "sync_status_syncronizing": "SENKRONİZE EDİLİYOR", + "sync_status_timed_out": "ZAMAN AŞIMINA UĞRADI", "sync_status_unsupported": "Desteklenmeyen düğüm", "syncing_wallet_alert_content": "Bakiyeniz ve işlem listeniz, en üstte \"SENKRONİZE EDİLDİ\" yazana kadar tamamlanmamış olabilir. Daha fazla bilgi edinmek için tıklayın/dokunun.", "syncing_wallet_alert_title": "Cüzdanınız senkronize ediliyor", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 07088a2ba0..e5ed5c049d 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "ПОЧАТОК СИНХРОНІЗАЦІЇ", "sync_status_syncronized": "СИНХРОНІЗОВАНИЙ", "sync_status_syncronizing": "СИНХРОНІЗАЦІЯ", + "sync_status_timed_out": "ТАЙМ-АУТ", "sync_status_unsupported": "Непідтримуваний вузол", "syncing_wallet_alert_content": "Ваш баланс та список транзакцій може бути неповним, доки вгорі не буде написано «СИНХРОНІЗОВАНО». Натисніть/торкніться, щоб дізнатися більше.", "syncing_wallet_alert_title": "Ваш гаманець синхронізується", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index b624e272a6..70b72dc949 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -638,6 +638,7 @@ "sync_status_starting_sync": "مطابقت پذیری شروع کر رہا ہے۔", "sync_status_syncronized": "مطابقت پذیر", "sync_status_syncronizing": "مطابقت پذیری", + "sync_status_timed_out": "وقت ختم", "sync_status_unsupported": "غیر تعاون یافتہ نوڈ", "syncing_wallet_alert_content": "آپ کے بیلنس اور لین دین کی فہرست اس وقت تک مکمل نہیں ہو سکتی جب تک کہ یہ سب سے اوپر \"SYNCRONIZED\" نہ کہے۔ مزید جاننے کے لیے کلک/تھپتھپائیں۔", "syncing_wallet_alert_title": "آپ کا بٹوہ مطابقت پذیر ہو رہا ہے۔", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 9612d561cd..a8b4b7ab71 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -637,6 +637,7 @@ "sync_status_starting_sync": "Ń BẸ̀RẸ̀ RẸ́", "sync_status_syncronized": "TI MÚDỌ́GBA", "sync_status_syncronizing": "Ń MÚDỌ́GBA", + "sync_status_timed_out": "Ti akoko jade", "sync_status_unsupported": "Ile-igbimọ ti ko ni atilẹyin", "syncing_wallet_alert_content": "Iwontunws.funfun rẹ ati atokọ idunadura le ma pari titi ti yoo fi sọ “SYNCHRONIZED” ni oke. Tẹ/tẹ ni kia kia lati ni imọ siwaju sii.", "syncing_wallet_alert_title": "Apamọwọ rẹ n muṣiṣẹpọ", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 26cd880b95..10d184cb5a 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -636,6 +636,7 @@ "sync_status_starting_sync": "开始同步", "sync_status_syncronized": "已同步", "sync_status_syncronizing": "正在同步", + "sync_status_timed_out": "时间到", "sync_status_unsupported": "不支持的节点", "syncing_wallet_alert_content": "您的余额和交易列表可能不完整,直到顶部显示“已同步”。单击/点击以了解更多信息。", "syncing_wallet_alert_title": "您的钱包正在同步", From 8ed3e41b2b5821cea7cbb26d9a52f471e9cd6f1b Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 1 Apr 2024 19:19:31 -0300 Subject: [PATCH 017/242] feat: Scan Silent Payments homepage toggle --- cw_bitcoin/lib/electrum_wallet.dart | 35 +++++- lib/bitcoin/cw_bitcoin.dart | 10 ++ .../screens/dashboard/pages/balance_page.dart | 104 +++++++++++++----- .../dashboard/balance_view_model.dart | 17 +++ res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 3 +- res/values/strings_ko.arb | 3 +- res/values/strings_my.arb | 3 +- res/values/strings_nl.arb | 3 +- res/values/strings_pl.arb | 3 +- res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 3 +- res/values/strings_th.arb | 3 +- res/values/strings_tl.arb | 3 +- res/values/strings_tr.arb | 3 +- res/values/strings_uk.arb | 3 +- res/values/strings_ur.arb | 3 +- res/values/strings_yo.arb | 3 +- res/values/strings_zh.arb | 3 +- 30 files changed, 172 insertions(+), 46 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index cf9a39206b..5a1cbda2f0 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -142,6 +142,33 @@ abstract class ElectrumWalletBase bool hasSilentPaymentsScanning = false; @observable bool nodeSupportsSilentPayments = true; + @observable + bool silentPaymentsScanningActive = false; + + @action + void setSilentPaymentsScanning(bool value) { + hasSilentPaymentsScanning = value; + + if (value) { + _setInitialHeight().then((_) { + if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { + _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); + } + }); + } else { + _isolate?.then((runningIsolate) => runningIsolate.kill(priority: Isolate.immediate)); + + if (electrumClient.isConnected) { + syncStatus = SyncedSyncStatus(); + } else { + if (electrumClient.uri != null) { + electrumClient.connectToUri(electrumClient.uri!).then((_) { + startSync(); + }); + } + } + } + } @observable int? currentChainTip; @@ -260,7 +287,7 @@ abstract class ElectrumWalletBase try { syncStatus = AttemptingSyncStatus(); - if (hasSilentPaymentsScanning) { + if (silentPaymentsScanningActive) { try { await _setInitialHeight(); } catch (_) {} @@ -279,7 +306,7 @@ abstract class ElectrumWalletBase Timer.periodic( const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates()); - if (!hasSilentPaymentsScanning || walletInfo.restoreHeight == currentChainTip) { + if (!silentPaymentsScanningActive || walletInfo.restoreHeight == currentChainTip) { syncStatus = SyncedSyncStatus(); } } catch (e, stacktrace) { @@ -1335,7 +1362,9 @@ abstract class ElectrumWalletBase Future _setInitialHeight() async { currentChainTip = await electrumClient.getCurrentBlockChainTip(); - if (currentChainTip != null) walletInfo.restoreHeight = currentChainTip!; + if (currentChainTip != null && walletInfo.restoreHeight == 0) { + walletInfo.restoreHeight = currentChainTip!; + } } static BasedUtxoNetwork _getNetwork(bitcoin.NetworkType networkType, CryptoCurrency? currency) { diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index e8329a4f79..8341b76e21 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -263,4 +263,14 @@ class CWBitcoin extends Bitcoin { BitcoinAddressType getOptionToType(ReceivePageOption option) { return (option as BitcoinReceivePageOption).toType(); } + + bool getScanningActive(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.silentPaymentsScanningActive; + } + + void setScanningActive(Object wallet, bool active) { + final bitcoinWallet = wallet as ElectrumWallet; + bitcoinWallet.setSilentPaymentsScanning(active); + } } diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index bb3ec70dc0..8bda1834a2 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_wi import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart'; import 'package:cake_wallet/src/widgets/introducing_card.dart'; +import 'package:cake_wallet/src/widgets/standard_switch.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; @@ -193,21 +194,29 @@ class CryptoBalanceWidget extends StatelessWidget { itemBuilder: (__, index) { final balance = dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index); - return BalanceRowWidget( - availableBalanceLabel: - '${dashboardViewModel.balanceViewModel.availableBalanceLabel}', - availableBalance: balance.availableBalance, - availableFiatBalance: balance.fiatAvailableBalance, - additionalBalanceLabel: - '${dashboardViewModel.balanceViewModel.additionalBalanceLabel}', - additionalBalance: balance.additionalBalance, - additionalFiatBalance: balance.fiatAdditionalBalance, - frozenBalance: balance.frozenBalance, - frozenFiatBalance: balance.fiatFrozenBalance, - currency: balance.asset, - hasAdditionalBalance: - dashboardViewModel.balanceViewModel.hasAdditionalBalance, - ); + return Observer(builder: (_) { + return BalanceRowWidget( + availableBalanceLabel: + '${dashboardViewModel.balanceViewModel.availableBalanceLabel}', + availableBalance: balance.availableBalance, + availableFiatBalance: balance.fiatAvailableBalance, + additionalBalanceLabel: + '${dashboardViewModel.balanceViewModel.additionalBalanceLabel}', + additionalBalance: balance.additionalBalance, + additionalFiatBalance: balance.fiatAdditionalBalance, + frozenBalance: balance.frozenBalance, + frozenFiatBalance: balance.fiatFrozenBalance, + currency: balance.asset, + hasAdditionalBalance: + dashboardViewModel.balanceViewModel.hasAdditionalBalance, + hasSilentPayments: dashboardViewModel.balanceViewModel.hasSilentPayments, + silentPaymentsScanningActive: + dashboardViewModel.balanceViewModel.silentPaymentsScanningActive, + setSilentPaymentsScanning: () => dashboardViewModel.balanceViewModel + .setSilentPaymentsScanning( + !dashboardViewModel.balanceViewModel.silentPaymentsScanningActive), + ); + }); }, ); }, @@ -231,6 +240,9 @@ class BalanceRowWidget extends StatelessWidget { required this.frozenFiatBalance, required this.currency, required this.hasAdditionalBalance, + required this.hasSilentPayments, + required this.silentPaymentsScanningActive, + required this.setSilentPaymentsScanning, super.key, }); @@ -244,6 +256,9 @@ class BalanceRowWidget extends StatelessWidget { final String frozenFiatBalance; final CryptoCurrency currency; final bool hasAdditionalBalance; + final bool hasSilentPayments; + final bool silentPaymentsScanningActive; + final void Function() setSilentPaymentsScanning; // void _showBalanceDescription(BuildContext context) { // showPopUp( @@ -339,19 +354,19 @@ class BalanceRowWidget extends StatelessWidget { height: 40, width: 40, displayOnError: Container( - height: 30.0, - width: 30.0, - child: Center( - child: Text( - currency.title.substring(0, min(currency.title.length, 2)), - style: TextStyle(fontSize: 11), - ), - ), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.grey.shade400, - ), + height: 30.0, + width: 30.0, + child: Center( + child: Text( + currency.title.substring(0, min(currency.title.length, 2)), + style: TextStyle(fontSize: 11), ), + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey.shade400, + ), + ), ), const SizedBox(height: 10), Text( @@ -410,9 +425,7 @@ class BalanceRowWidget extends StatelessWidget { fontSize: 20, fontFamily: 'Lato', fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .balanceAmountColor, + color: Theme.of(context).extension()!.balanceAmountColor, height: 1, ), maxLines: 1, @@ -476,6 +489,37 @@ class BalanceRowWidget extends StatelessWidget { ), ], ), + if (hasSilentPayments) ...[ + Padding( + padding: const EdgeInsets.only(right: 8, top: 8), + child: Divider( + color: Theme.of(context).extension()!.labelTextColor, + thickness: 1, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AutoSizeText( + S.of(context).silent_payments_scanning, + style: TextStyle( + fontSize: 14, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).extension()!.assetTitleColor, + height: 1, + ), + maxLines: 1, + textAlign: TextAlign.center, + ), + Padding( + padding: const EdgeInsets.only(right: 8), + child: StandardSwitch( + value: silentPaymentsScanningActive, onTaped: setSilentPaymentsScanning), + ) + ], + ), + ] ], ), ), diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index eee53516e0..08bdd82522 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; @@ -45,6 +46,8 @@ abstract class BalanceViewModelBase with Store { : isReversing = false, isShowCard = appStore.wallet!.walletInfo.isShowIntroCakePayCard, wallet = appStore.wallet! { + silentPaymentsScanningActive = hasSilentPayments && bitcoin!.getScanningActive(wallet); + reaction((_) => appStore.wallet, _onWalletChange); } @@ -60,6 +63,20 @@ abstract class BalanceViewModelBase with Store { @observable WalletBase, TransactionInfo> wallet; + @computed + bool get hasSilentPayments => wallet.type == WalletType.bitcoin; + + @observable + bool silentPaymentsScanningActive = false; + + @action + void setSilentPaymentsScanning(bool active) { + if (hasSilentPayments) { + bitcoin!.setScanningActive(wallet, active); + silentPaymentsScanningActive = active; + } + } + @computed double get price { final price = fiatConvertationStore.prices[appStore.wallet!.currency]; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index cc284affe9..a66f85da4f 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -610,6 +610,7 @@ "sign_up": "اشتراك", "signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ", "signup_for_card_accept_terms": "قم بالتسجيل للحصول على البطاقة وقبول الشروط.", + "silent_payments_scanning": "المدفوعات الصامتة المسح الضوئي", "slidable": "قابل للانزلاق", "sort_by": "ترتيب حسب", "spend_key_private": "مفتاح الإنفاق (خاص)", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index a0174385d9..cc48a56e98 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -610,6 +610,7 @@ "sign_up": "Регистрация", "signTransaction": "Подпишете транзакция", "signup_for_card_accept_terms": "Регистрайте се за картата и приемете условията.", + "silent_payments_scanning": "Безшумни плащания за сканиране", "slidable": "Плъзгащ се", "sort_by": "Сортирай по", "spend_key_private": "Spend key (таен)", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index eb56a8070a..f3076cf6c5 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -610,6 +610,7 @@ "sign_up": "Registrovat se", "signTransaction": "Podepsat transakci", "signup_for_card_accept_terms": "Zaregistrujte se pro kartu a souhlaste s podmínkami.", + "silent_payments_scanning": "Skenování tichých plateb", "slidable": "Posuvné", "sort_by": "Seřazeno podle", "spend_key_private": "Klíč pro platby (soukromý)", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index e8b929a52e..cf4cbb7fa2 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -611,6 +611,7 @@ "sign_up": "Anmelden", "signTransaction": "Transaktion unterzeichnen", "signup_for_card_accept_terms": "Melden Sie sich für die Karte an und akzeptieren Sie die Bedingungen.", + "silent_payments_scanning": "Stille Zahlungen scannen", "slidable": "Verschiebbar", "sort_by": "Sortiere nach", "spend_key_private": "Spend Key (geheim)", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 65b3a4378d..d31435b484 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -610,6 +610,7 @@ "sign_up": "Sign Up", "signTransaction": "Sign Transaction", "signup_for_card_accept_terms": "Sign up for the card and accept the terms.", + "silent_payments_scanning": "Silent Payments Scanning", "slidable": "Slidable", "sort_by": "Sort by", "spend_key_private": "Spend key (private)", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index d85682524f..6aa1cfbd81 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -611,6 +611,7 @@ "sign_up": "Registrarse", "signTransaction": "Firmar transacción", "signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.", + "silent_payments_scanning": "Escaneo de pagos silenciosos", "slidable": "deslizable", "sort_by": "Ordenar por", "spend_key_private": "Spend clave (privado)", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index e24bdcb675..7b7ce56df7 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -610,6 +610,7 @@ "sign_up": "S'inscrire", "signTransaction": "Signer une transaction", "signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.", + "silent_payments_scanning": "Payments silencieux SCANNING", "slidable": "Glissable", "sort_by": "Trier par", "spend_key_private": "Clef de dépense (spend key) (privée)", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index bb0787defc..6c3de1e037 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -612,6 +612,7 @@ "sign_up": "Shiga", "signTransaction": "Sa hannu Ma'amala", "signup_for_card_accept_terms": "Yi rajista don katin kuma karɓi sharuɗɗan.", + "silent_payments_scanning": "Silent biya scanning", "slidable": "Mai iya zamewa", "sort_by": "Kasa", "spend_key_private": "makullin biya (maɓallin kalmar sirri)", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index ed71470f26..82445dfa3f 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -612,6 +612,7 @@ "sign_up": "साइन अप करें", "signTransaction": "लेन-देन पर हस्ताक्षर करें", "signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।", + "silent_payments_scanning": "मूक भुगतान स्कैनिंग", "slidable": "फिसलने लायक", "sort_by": "इसके अनुसार क्रमबद्ध करें", "spend_key_private": "खर्च करना (निजी)", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 84bde90ec8..4c64a9a349 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -610,6 +610,7 @@ "sign_up": "Prijavite se", "signTransaction": "Potpišite transakciju", "signup_for_card_accept_terms": "Prijavite se za karticu i prihvatite uvjete.", + "silent_payments_scanning": "Skeniranje tihih plaćanja", "slidable": "Klizna", "sort_by": "Poredaj po", "spend_key_private": "Spend key (privatni)", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b5a4391685..90a92a8f24 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -613,6 +613,7 @@ "sign_up": "Daftar", "signTransaction": "Tandatangani Transaksi", "signup_for_card_accept_terms": "Daftar untuk kartu dan terima syarat dan ketentuan.", + "silent_payments_scanning": "Pemindaian pembayaran diam", "slidable": "Dapat digeser", "sort_by": "Sortir dengan", "spend_key_private": "Kunci pengeluaran (privat)", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 83f5843e22..9795950484 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -612,6 +612,7 @@ "sign_up": "Registrati", "signTransaction": "Firma la transazione", "signup_for_card_accept_terms": "Registrati per la carta e accetta i termini.", + "silent_payments_scanning": "Scansione di pagamenti silenziosi", "slidable": "Scorrevole", "sort_by": "Ordina per", "spend_key_private": "Chiave di spesa (privata)", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 0c2bafb77d..b3a3cefe0f 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -611,6 +611,7 @@ "sign_up": "サインアップ", "signTransaction": "トランザクションに署名する", "signup_for_card_accept_terms": "カードにサインアップして、利用規約に同意してください。", + "silent_payments_scanning": "サイレントペイメントスキャン", "slidable": "スライド可能", "sort_by": "並び替え", "spend_key_private": "キーを使う (プライベート)", @@ -804,4 +805,4 @@ "you_will_get": "に変換", "you_will_send": "から変換", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index d8f39277e3..b2908056f7 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -611,6 +611,7 @@ "sign_up": "가입", "signTransaction": "거래 서명", "signup_for_card_accept_terms": "카드에 가입하고 약관에 동의합니다.", + "silent_payments_scanning": "조용한 지불 스캔", "slidable": "슬라이딩 가능", "sort_by": "정렬 기준", "spend_key_private": "지출 키 (은밀한)", @@ -805,4 +806,4 @@ "you_will_send": "다음에서 변환", "YY": "YY", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 286f8ffd9d..1453da4cc8 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -610,6 +610,7 @@ "sign_up": "ဆိုင်းအပ်", "signTransaction": "ငွေလွှဲဝင်ပါ။", "signup_for_card_accept_terms": "ကတ်အတွက် စာရင်းသွင်းပြီး စည်းကမ်းချက်များကို လက်ခံပါ။", + "silent_payments_scanning": "အသံတိတ်ငွေပေးချေမှု scanning", "slidable": "လျှောချနိုင်သည်။", "sort_by": "အလိုက်စဥ်သည်", "spend_key_private": "သော့သုံးရန် (သီးသန့်)", @@ -803,4 +804,4 @@ "you_will_get": "သို့ပြောင်းပါ။", "you_will_send": "မှပြောင်းပါ။", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 57f9943e3c..b82019e6bd 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -610,6 +610,7 @@ "sign_up": "Aanmelden", "signTransaction": "Transactie ondertekenen", "signup_for_card_accept_terms": "Meld je aan voor de kaart en accepteer de voorwaarden.", + "silent_payments_scanning": "Stille betalingen scannen", "slidable": "Verschuifbaar", "sort_by": "Sorteer op", "spend_key_private": "Sleutel uitgeven (privaat)", @@ -804,4 +805,4 @@ "you_will_get": "Converteren naar", "you_will_send": "Converteren van", "yy": "JJ" -} +} \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 703f85c0fe..c1e677284f 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -610,6 +610,7 @@ "sign_up": "Zarejestruj się", "signTransaction": "Podpisz transakcję", "signup_for_card_accept_terms": "Zarejestruj się, aby otrzymać kartę i zaakceptuj warunki.", + "silent_payments_scanning": "Skanowanie cichych płatności", "slidable": "Przesuwne", "sort_by": "Sortuj według", "spend_key_private": "Klucz prywatny", @@ -803,4 +804,4 @@ "you_will_get": "Konwertuj na", "you_will_send": "Konwertuj z", "yy": "RR" -} +} \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index cddce9a2a0..77e105b04e 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -612,6 +612,7 @@ "sign_up": "Inscrever-se", "signTransaction": "Assinar transação", "signup_for_card_accept_terms": "Cadastre-se no cartão e aceite os termos.", + "silent_payments_scanning": "Scanear Pagamentos Silenciosos", "slidable": "Deslizável", "sort_by": "Ordenar por", "spend_key_private": "Chave de gastos (privada)", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 14f3e27e89..c793ef6b12 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -611,6 +611,7 @@ "sign_up": "Зарегистрироваться", "signTransaction": "Подписать транзакцию", "signup_for_card_accept_terms": "Подпишитесь на карту и примите условия.", + "silent_payments_scanning": "Сканирование безмолвных платежей", "slidable": "Скользящий", "sort_by": "Сортировать по", "spend_key_private": "Приватный ключ траты", @@ -804,4 +805,4 @@ "you_will_get": "Конвертировать в", "you_will_send": "Конвертировать из", "yy": "ГГ" -} +} \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index d369683d0c..1839cc930d 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -610,6 +610,7 @@ "sign_up": "สมัครสมาชิก", "signTransaction": "ลงนามในการทำธุรกรรม", "signup_for_card_accept_terms": "ลงทะเบียนสำหรับบัตรและยอมรับเงื่อนไข", + "silent_payments_scanning": "การสแกนการชำระเงินแบบเงียบ", "slidable": "เลื่อนได้", "sort_by": "เรียงตาม", "spend_key_private": "คีย์จ่าย (ส่วนตัว)", @@ -803,4 +804,4 @@ "you_will_get": "แปลงเป็น", "you_will_send": "แปลงจาก", "yy": "ปี" -} +} \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index c78955a9f4..4d3ef91c04 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -610,6 +610,7 @@ "sign_up": "Mag -sign up", "signTransaction": "Mag-sign Transaksyon", "signup_for_card_accept_terms": "Mag -sign up para sa card at tanggapin ang mga termino.", + "silent_payments_scanning": "Tahimik na pag -scan ng mga pagbabayad", "slidable": "Slidable", "sort_by": "Pag -uri -uriin sa pamamagitan ng", "spend_key_private": "Gumastos ng susi (pribado)", @@ -803,4 +804,4 @@ "you_will_get": "Mag -convert sa", "you_will_send": "I -convert mula sa", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index add6cd4517..55fbf6036b 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -610,6 +610,7 @@ "sign_up": "Kaydol", "signTransaction": "İşlem İmzala", "signup_for_card_accept_terms": "Kart için kaydol ve koşulları kabul et.", + "silent_payments_scanning": "Sessiz Ödemeler Taraması", "slidable": "kaydırılabilir", "sort_by": "Göre sırala", "spend_key_private": "Harcama anahtarı (özel)", @@ -803,4 +804,4 @@ "you_will_get": "Biçimine dönüştür:", "you_will_send": "Biçiminden dönüştür:", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 8a4ca8e387..d48c5eb3db 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -611,6 +611,7 @@ "sign_up": "Зареєструватися", "signTransaction": "Підписати транзакцію", "signup_for_card_accept_terms": "Зареєструйтеся на картку та прийміть умови.", + "silent_payments_scanning": "Мовчазні платежі сканування", "slidable": "Розсувний", "sort_by": "Сортувати за", "spend_key_private": "Приватний ключ витрати", @@ -804,4 +805,4 @@ "you_will_get": "Конвертувати в", "you_will_send": "Конвертувати з", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index deaa83b2db..b5255bc150 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -612,6 +612,7 @@ "sign_up": "سائن اپ", "signTransaction": "۔ﮟﯾﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", "signup_for_card_accept_terms": "کارڈ کے لیے سائن اپ کریں اور شرائط کو قبول کریں۔", + "silent_payments_scanning": "خاموش ادائیگی اسکیننگ", "slidable": "سلائیڈ ایبل", "sort_by": "ترتیب دیں", "spend_key_private": "خرچ کی کلید (نجی)", @@ -805,4 +806,4 @@ "you_will_get": "میں تبدیل کریں۔", "you_will_send": "سے تبدیل کریں۔", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 01e946df18..3435f9ca13 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -611,6 +611,7 @@ "sign_up": "Forúkọ sílẹ̀", "signTransaction": "Wole Idunadura", "signup_for_card_accept_terms": "Ẹ f'orúkọ sílẹ̀ láti gba káàdì àti àjọrò.", + "silent_payments_scanning": "Awọn sisanwo ipalọlọ", "slidable": "Slidable", "sort_by": "Sa pelu", "spend_key_private": "Kọ́kọ́rọ́ sísan (àdáni)", @@ -804,4 +805,4 @@ "you_will_get": "Ṣe pàṣípààrọ̀ sí", "you_will_send": "Ṣe pàṣípààrọ̀ láti", "yy": "Ọd" -} +} \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index c48bc60dad..22e74ce5b5 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -610,6 +610,7 @@ "sign_up": "注册", "signTransaction": "签署交易", "signup_for_card_accept_terms": "注册卡并接受条款。", + "silent_payments_scanning": "无声付款扫描", "slidable": "可滑动", "sort_by": "排序方式", "spend_key_private": "Spend 密钥 (私钥)", @@ -803,4 +804,4 @@ "you_will_get": "转换到", "you_will_send": "转换自", "yy": "YY" -} +} \ No newline at end of file From d6de17c7102080ec1dd86f15c624a0e2bc92654f Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 1 Apr 2024 19:36:18 -0300 Subject: [PATCH 018/242] chore: build configure --- tool/configure.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tool/configure.dart b/tool/configure.dart index 62179263dd..96891247c0 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -155,6 +155,8 @@ abstract class Bitcoin { bool isBitcoinReceivePageOption(ReceivePageOption option); BitcoinAddressType getOptionToType(ReceivePageOption option); bool hasTaprootInput(PendingTransaction pendingTransaction); + bool getScanningActive(Object wallet); + void setScanningActive(Object wallet, bool active); } """; From 226c47c6e37dcefc42a1cee26bc5e5786fb29066 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 5 Apr 2024 17:27:42 -0300 Subject: [PATCH 019/242] feat: generic fixes, testnet UI improvements, useSSL on bitcoin nodes --- assets/images/tbtc.png | Bin 0 -> 3983 bytes cw_bitcoin/lib/bitcoin_wallet.dart | 28 +- cw_bitcoin/lib/electrum.dart | 14 +- cw_bitcoin/lib/electrum_wallet.dart | 300 ++++++++++-------- cw_bitcoin/lib/electrum_wallet_snapshot.dart | 2 +- cw_core/lib/crypto_currency.dart | 2 + cw_core/lib/currency_for_wallet_type.dart | 8 +- cw_core/lib/wallet_base.dart | 2 +- cw_core/lib/wallet_type.dart | 5 +- lib/bitcoin/cw_bitcoin.dart | 7 + .../screens/dashboard/pages/balance_page.dart | 80 +++-- lib/src/screens/nodes/widgets/node_form.dart | 111 +++---- lib/src/screens/receive/receive_page.dart | 34 +- lib/src/screens/rescan/rescan_page.dart | 20 +- lib/src/screens/send/widgets/send_card.dart | 3 +- .../settings/connection_sync_page.dart | 4 +- .../screens/wallet_list/wallet_list_page.dart | 11 +- lib/src/widgets/address_text_field.dart | 6 +- lib/src/widgets/blockchain_height_widget.dart | 29 +- .../dashboard/balance_view_model.dart | 13 - .../dashboard/dashboard_view_model.dart | 29 +- lib/view_model/rescan_view_model.dart | 4 + .../wallet_address_list_view_model.dart | 28 +- .../wallet_list/wallet_list_item.dart | 2 + .../wallet_list/wallet_list_view_model.dart | 1 + res/values/strings_ar.arb | 4 + res/values/strings_bg.arb | 4 + res/values/strings_cs.arb | 4 + res/values/strings_de.arb | 4 + res/values/strings_en.arb | 4 + res/values/strings_es.arb | 4 + res/values/strings_fr.arb | 4 + res/values/strings_ha.arb | 4 + res/values/strings_hi.arb | 4 + res/values/strings_hr.arb | 4 + res/values/strings_id.arb | 4 + res/values/strings_it.arb | 4 + res/values/strings_ja.arb | 4 + res/values/strings_ko.arb | 4 + res/values/strings_my.arb | 4 + res/values/strings_nl.arb | 4 + res/values/strings_pl.arb | 4 + res/values/strings_pt.arb | 8 +- res/values/strings_ru.arb | 4 + res/values/strings_th.arb | 4 + res/values/strings_tl.arb | 4 + res/values/strings_tr.arb | 4 + res/values/strings_uk.arb | 4 + res/values/strings_ur.arb | 4 + res/values/strings_yo.arb | 4 + res/values/strings_zh.arb | 4 + tool/configure.dart | 8 +- 52 files changed, 533 insertions(+), 326 deletions(-) create mode 100644 assets/images/tbtc.png diff --git a/assets/images/tbtc.png b/assets/images/tbtc.png new file mode 100644 index 0000000000000000000000000000000000000000..bd4323edfce97bfadd2a2cf764af04af74b368b8 GIT binary patch literal 3983 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 literal 0 HcmV?d00001 diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 3be7a8b679..e0218eff55 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -33,19 +33,21 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { List? initialSilentAddresses, int initialSilentAddressIndex = 0, }) : super( - mnemonic: mnemonic, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - networkType: networkParam == null - ? bitcoin.bitcoin - : networkParam == BitcoinNetwork.mainnet - ? bitcoin.bitcoin - : bitcoin.testnet, - initialAddresses: initialAddresses, - initialBalance: initialBalance, - seedBytes: seedBytes, - currency: CryptoCurrency.btc) { + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + networkType: networkParam == null + ? bitcoin.bitcoin + : networkParam == BitcoinNetwork.mainnet + ? bitcoin.bitcoin + : bitcoin.testnet, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: + networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc, + ) { walletAddresses = BitcoinWalletAddresses( walletInfo, initialAddresses: initialAddresses, diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index bea6c49574..5f6363c755 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -50,18 +50,24 @@ class ElectrumClient { String unterminatedString; Uri? uri; + bool? useSSL; - Future connectToUri(Uri uri) async { + Future connectToUri(Uri uri, {bool? useSSL}) async { this.uri = uri; - await connect(host: uri.host, port: uri.port); + this.useSSL = useSSL; + await connect(host: uri.host, port: uri.port, useSSL: useSSL); } - Future connect({required String host, required int port}) async { + Future connect({required String host, required int port, bool? useSSL}) async { try { await socket?.close(); } catch (_) {} - socket = await SecureSocket.connect(host, port, timeout: connectionTimeout); + if (useSSL == true) { + socket = await SecureSocket.connect(host, port, timeout: connectionTimeout); + } else { + socket = await Socket.connect(host, port, timeout: connectionTimeout); + } _setIsConnected(true); socket!.listen((Uint8List event) { diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 5a1cbda2f0..4cd2d9f226 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -44,8 +44,6 @@ import 'package:http/http.dart' as http; part 'electrum_wallet.g.dart'; -const SCANNING_BLOCK_COUNT = 50; - class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; abstract class ElectrumWalletBase @@ -89,6 +87,10 @@ abstract class ElectrumWalletBase this.electrumClient = electrumClient ?? ElectrumClient(); this.walletInfo = walletInfo; transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password); + + reaction((_) => syncStatus, (SyncStatus syncStatus) { + silentPaymentsScanningActive = syncStatus is SyncingSyncStatus; + }); } static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) => @@ -146,10 +148,10 @@ abstract class ElectrumWalletBase bool silentPaymentsScanningActive = false; @action - void setSilentPaymentsScanning(bool value) { - hasSilentPaymentsScanning = value; + void setSilentPaymentsScanning(bool active) { + silentPaymentsScanningActive = active; - if (value) { + if (active) { _setInitialHeight().then((_) { if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); @@ -218,16 +220,11 @@ abstract class ElectrumWalletBase chainTip: currentChainTip, electrumClient: ElectrumClient(), transactionHistoryIds: transactionHistory.transactions.keys.toList(), - node: electrumClient.uri.toString(), + node: node!, labels: walletAddresses.labels, )); await for (var message in receivePort) { - if (message is bool && message == false) { - nodeSupportsSilentPayments = message; - syncStatus = TimedOutSyncStatus(); - } - if (message is Map) { for (final map in message.entries) { final txid = map.key; @@ -274,6 +271,10 @@ abstract class ElectrumWalletBase // check if is a SyncStatus type since "is SyncStatus" doesn't work here if (message is SyncResponse) { + if (message.syncStatus is UnsupportedSyncStatus) { + nodeSupportsSilentPayments = false; + } + syncStatus = message.syncStatus; walletInfo.restoreHeight = message.height; await walletInfo.save(); @@ -316,11 +317,15 @@ abstract class ElectrumWalletBase } } + Node? node; + @action Future _electrumConnect(Node node, {bool? attemptedReconnect}) async { + this.node = node; + try { syncStatus = ConnectingSyncStatus(); - await electrumClient.connectToUri(node.uri); + await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); electrumClient.onConnectionStatusChange = (bool isConnected) async { if (!isConnected) { syncStatus = LostConnectionSyncStatus(); @@ -346,6 +351,52 @@ abstract class ElectrumWalletBase bool _isBelowDust(int amount) => amount <= _getDustAmount() && network != BitcoinNetwork.testnet; + void _createSilentPayments( + List outputs, + List inputPubKeys, + List vinOutpoints, + List inputPrivKeyInfos, + ) { + List silentPaymentDestinations = []; + + for (final out in outputs) { + final address = out.address; + final amount = out.value; + + if (address is SilentPaymentAddress) { + silentPaymentDestinations.add( + SilentPaymentDestination.fromAddress(address.toAddress(network), amount.toInt()), + ); + } + } + + if (silentPaymentDestinations.isNotEmpty) { + final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); + final sendingOutputs = spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations); + + final outputsAdded = []; + + for (var i = 0; i < outputs.length; i++) { + final out = outputs[i]; + + final silentOutputs = sendingOutputs[out.address.toAddress(network)]; + if (silentOutputs != null) { + final silentOutput = + silentOutputs.firstWhereOrNull((element) => !outputsAdded.contains(element)); + + if (silentOutput != null) { + outputs[i] = BitcoinOutput( + address: silentOutput.address, + value: BigInt.from(silentOutput.amount), + ); + + outputsAdded.add(silentOutput); + } + } + } + } + } + Future estimateSendAllTx( List outputs, int feeRate, { @@ -416,6 +467,10 @@ abstract class ElectrumWalletBase throw BitcoinTransactionNoInputsException(); } + if (hasSilentPayment == true) { + _createSilentPayments(outputs, inputPubKeys, vinOutpoints, inputPrivKeyInfos); + } + int estimatedSize; if (network is BitcoinCashNetwork) { estimatedSize = ForkedTransactionBuilder.estimateTransactionSize( @@ -433,45 +488,6 @@ abstract class ElectrumWalletBase ); } - if (hasSilentPayment == true) { - List silentPaymentDestinations = []; - - for (final out in outputs) { - final address = out.address; - final amount = out.value; - - if (address is SilentPaymentAddress) { - final silentPaymentDestination = - SilentPaymentDestination.fromAddress(address.toAddress(network), amount.toInt()); - silentPaymentDestinations.add(silentPaymentDestination); - } - } - - final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); - final sendingOutputs = spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations); - - var outputsAdded = []; - - for (var i = 0; i < outputs.length; i++) { - final out = outputs[i]; - - final silentOutputs = sendingOutputs[out.address.toAddress(network)]; - if (silentOutputs != null) { - final silentOutput = - silentOutputs.firstWhereOrNull((element) => !outputsAdded.contains(element)); - - if (silentOutput != null) { - outputs[i] = BitcoinOutput( - address: silentOutput.address, - value: BigInt.from(silentOutput.amount), - ); - - outputsAdded.add(silentOutput); - } - } - } - } - int fee = feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize); if (fee == 0) { @@ -518,7 +534,9 @@ abstract class ElectrumWalletBase bool hasSilentPayment = false, }) async { final utxos = []; + List vinOutpoints = []; List inputPrivKeyInfos = []; + List inputPubKeys = []; int allInputsAmount = 0; bool spendsSilentPayment = false; @@ -553,6 +571,10 @@ abstract class ElectrumWalletBase ); } + inputPrivKeyInfos.add(ECPrivateInfo(privkey, address.type == SegwitAddresType.p2tr)); + inputPubKeys.add(privkey.getPublic()); + vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); + utxos.add( UtxoWithAddress( utxo: BitcoinUtxo( @@ -579,6 +601,10 @@ abstract class ElectrumWalletBase throw BitcoinTransactionNoInputsException(); } + if (hasSilentPayment == true) { + _createSilentPayments(outputs, inputPubKeys, vinOutpoints, inputPrivKeyInfos); + } + final spendingAllCoins = sendingCoins.length == utxos.length; // How much is being spent - how much is being sent @@ -952,8 +978,10 @@ abstract class ElectrumWalletBase await transactionHistory.changePassword(password); } + @action @override Future rescan({required int height, int? chainTip, ScanData? scanData}) async { + silentPaymentsScanningActive = true; _setListeners(height); } @@ -1388,7 +1416,7 @@ class ScanData { final SendPort sendPort; final SilentPaymentOwner silentAddress; final int height; - final String node; + final Node node; final BasedUtxoNetwork network; final int chainTip; final ElectrumClient electrumClient; @@ -1436,7 +1464,7 @@ Future startRefresh(ScanData scanData) async { final electrumClient = scanData.electrumClient; if (!electrumClient.isConnected) { final node = scanData.node; - await electrumClient.connectToUri(Uri.parse(node)); + await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); } return electrumClient; } @@ -1489,21 +1517,31 @@ Future startRefresh(ScanData scanData) async { try { final electrumClient = await getElectrumConnection(); + final scanningBlockCount = scanData.network == BitcoinNetwork.testnet ? 50 : 10; + Map? tweaks; try { - tweaks = await electrumClient.getTweaks(height: syncHeight, count: SCANNING_BLOCK_COUNT); + tweaks = await electrumClient.getTweaks(height: syncHeight, count: scanningBlockCount); } catch (e) { if (e is RequestFailedTimeoutException) { - return scanData.sendPort.send(false); + return scanData.sendPort.send( + SyncResponse(syncHeight, TimedOutSyncStatus()), + ); } } if (tweaks == null) { - scanData.sendPort.send(SyncResponse(syncHeight, - SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); + return scanData.sendPort.send( + SyncResponse(syncHeight, UnsupportedSyncStatus()), + ); + } + + if (tweaks.isEmpty) { + syncHeight += scanningBlockCount; + continue; } - final blockHeights = tweaks!.keys; + final blockHeights = tweaks.keys; for (var i = 0; i < blockHeights.length; i++) { try { final blockHeight = blockHeights.elementAt(i).toString(); @@ -1515,81 +1553,83 @@ Future startRefresh(ScanData scanData) async { final outputPubkeys = (details["output_pubkeys"] as Map); final tweak = details["tweak"].toString(); - final spb = SilentPaymentBuilder(receiverTweak: tweak); - final addToWallet = spb.scanOutputs( - scanData.silentAddress.b_scan, - scanData.silentAddress.B_spend, - outputPubkeys.values - .map((o) => getScriptFromOutput( - o["pubkey"].toString(), int.parse(o["amount"].toString()))) - .toList(), - precomputedLabels: scanData.labels, - ); - - if (addToWallet.isEmpty) { - // no results tx, continue to next tx - continue; - } - - addToWallet.forEach((key, value) async { - final t_k = value.tweak; - - final addressRecord = BitcoinSilentPaymentAddressRecord( - value.output.address.toAddress(scanData.network), - index: 0, - isHidden: false, - isUsed: true, - network: scanData.network, - silentPaymentTweak: t_k, - type: SegwitAddresType.p2tr, + try { + final spb = SilentPaymentBuilder(receiverTweak: tweak); + final addToWallet = spb.scanOutputs( + scanData.silentAddress.b_scan, + scanData.silentAddress.B_spend, + outputPubkeys.values + .map((o) => getScriptFromOutput( + o["pubkey"].toString(), int.parse(o["amount"].toString()))) + .toList(), + precomputedLabels: scanData.labels, ); - int? amount; - int? pos; - outputPubkeys.entries.firstWhere((k) { - final matches = k.value["pubkey"] == key; - if (matches) { - amount = int.parse(k.value["amount"].toString()); - pos = int.parse(k.key.toString()); - return true; - } - return false; - }); - - final json = { - 'address_record': addressRecord.toJSON(), - 'tx_hash': txid, - 'value': amount!, - 'tx_pos': pos!, - 'silent_payment_tweak': t_k, - }; - - final tx = BitcoinUnspent.fromJSON(addressRecord, json); - - final txInfo = ElectrumTransactionInfo( - WalletType.bitcoin, - id: tx.hash, - height: syncHeight, - amount: 0, // will be added later via unspent - fee: 0, - direction: TransactionDirection.incoming, - isPending: false, - date: DateTime.now(), - confirmations: currentChainTip - syncHeight - 1, - to: value.label != null - ? SilentPaymentAddress( - version: scanData.silentAddress.version, - B_scan: scanData.silentAddress.B_scan - .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(value.tweak))), - B_spend: scanData.silentAddress.B_spend, - hrp: scanData.silentAddress.hrp, - ).toString() - : scanData.silentAddress.toString(), - unspents: [tx], - ); + if (addToWallet.isEmpty) { + // no results tx, continue to next tx + continue; + } - scanData.sendPort.send({txInfo.id: txInfo}); - }); + addToWallet.forEach((key, value) async { + final t_k = value.tweak; + + final addressRecord = BitcoinSilentPaymentAddressRecord( + value.output.address.toAddress(scanData.network), + index: 0, + isHidden: false, + isUsed: true, + network: scanData.network, + silentPaymentTweak: t_k, + type: SegwitAddresType.p2tr, + ); + + int? amount; + int? pos; + outputPubkeys.entries.firstWhere((k) { + final matches = k.value["pubkey"] == key; + if (matches) { + amount = int.parse(k.value["amount"].toString()); + pos = int.parse(k.key.toString()); + return true; + } + return false; + }); + + final json = { + 'address_record': addressRecord.toJSON(), + 'tx_hash': txid, + 'value': amount!, + 'tx_pos': pos!, + 'silent_payment_tweak': t_k, + }; + + final tx = BitcoinUnspent.fromJSON(addressRecord, json); + + final txInfo = ElectrumTransactionInfo( + WalletType.bitcoin, + id: tx.hash, + height: syncHeight, + amount: 0, // will be added later via unspent + fee: 0, + direction: TransactionDirection.incoming, + isPending: false, + date: DateTime.now(), + confirmations: currentChainTip - syncHeight - 1, + to: value.label != null + ? SilentPaymentAddress( + version: scanData.silentAddress.version, + B_scan: scanData.silentAddress.B_scan.tweakAdd( + BigintUtils.fromBytes(BytesUtils.fromHexString(value.tweak))), + B_spend: scanData.silentAddress.B_spend, + hrp: scanData.silentAddress.hrp, + ).toString() + : scanData.silentAddress.toString(), + unspents: [tx], + ); + + scanData.sendPort.send({txInfo.id: txInfo}); + }); + } catch (_) {} } } catch (e, s) { print([e, s]); diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 50e7d85694..8d9b6101b7 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -53,7 +53,7 @@ class ElectrumWalletSnapshot { .map((addr) => BitcoinSilentPaymentAddressRecord.fromJSON(addr, network: network)) .toList(); - final balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? + final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ?? ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index f1c1cd8aea..94dbeb61af 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -29,6 +29,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.bch, CryptoCurrency.bnb, CryptoCurrency.btc, + CryptoCurrency.tbtc, CryptoCurrency.dai, CryptoCurrency.dash, CryptoCurrency.eos, @@ -127,6 +128,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const bch = CryptoCurrency(title: 'BCH', fullName: 'Bitcoin Cash', raw: 2, name: 'bch', iconPath: 'assets/images/bch_icon.png', decimals: 8); static const bnb = CryptoCurrency(title: 'BNB', tag: 'BSC', fullName: 'Binance Coin', raw: 3, name: 'bnb', iconPath: 'assets/images/bnb_icon.png', decimals: 8); static const btc = CryptoCurrency(title: 'BTC', fullName: 'Bitcoin', raw: 4, name: 'btc', iconPath: 'assets/images/btc.png', decimals: 8); + static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 4, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8); static const dai = CryptoCurrency(title: 'DAI', tag: 'ETH', fullName: 'Dai', raw: 5, name: 'dai', iconPath: 'assets/images/dai_icon.png', decimals: 18); static const dash = CryptoCurrency(title: 'DASH', fullName: 'Dash', raw: 6, name: 'dash', iconPath: 'assets/images/dash_icon.png', decimals: 8); static const eos = CryptoCurrency(title: 'EOS', fullName: 'EOS', raw: 7, name: 'eos', iconPath: 'assets/images/eos_icon.png', decimals: 4); diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index 58ee37669a..1baca7b02f 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -1,9 +1,12 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_type.dart'; -CryptoCurrency currencyForWalletType(WalletType type) { +CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) { switch (type) { case WalletType.bitcoin: + if (isTestnet == true) { + return CryptoCurrency.tbtc; + } return CryptoCurrency.btc; case WalletType.monero: return CryptoCurrency.xmr; @@ -24,6 +27,7 @@ CryptoCurrency currencyForWalletType(WalletType type) { case WalletType.solana: return CryptoCurrency.sol; default: - throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); + throw Exception( + 'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); } } diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 49f1bdc943..21b8779894 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -24,7 +24,7 @@ abstract class WalletBase walletInfo.type; - CryptoCurrency get currency => currencyForWalletType(type); + CryptoCurrency get currency => currencyForWalletType(type, isTestnet: isTestnet); String get id => walletInfo.id; diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index a63ddf37cd..9c7f6e0f97 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -161,11 +161,14 @@ String walletTypeToDisplayName(WalletType type) { } } -CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { +CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false}) { switch (type) { case WalletType.monero: return CryptoCurrency.xmr; case WalletType.bitcoin: + if (isTestnet) { + return CryptoCurrency.tbtc; + } return CryptoCurrency.btc; case WalletType.litecoin: return CryptoCurrency.ltc; diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 8341b76e21..e623957d6f 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -264,6 +264,8 @@ class CWBitcoin extends Bitcoin { return (option as BitcoinReceivePageOption).toType(); } + @override + @computed bool getScanningActive(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.silentPaymentsScanningActive; @@ -273,4 +275,9 @@ class CWBitcoin extends Bitcoin { final bitcoinWallet = wallet as ElectrumWallet; bitcoinWallet.setSilentPaymentsScanning(active); } + + bool isTestnet(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.isTestnet ?? false; + } } diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index 8bda1834a2..2c77ffceaa 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -211,10 +211,12 @@ class CryptoBalanceWidget extends StatelessWidget { dashboardViewModel.balanceViewModel.hasAdditionalBalance, hasSilentPayments: dashboardViewModel.balanceViewModel.hasSilentPayments, silentPaymentsScanningActive: - dashboardViewModel.balanceViewModel.silentPaymentsScanningActive, - setSilentPaymentsScanning: () => dashboardViewModel.balanceViewModel - .setSilentPaymentsScanning( - !dashboardViewModel.balanceViewModel.silentPaymentsScanningActive), + dashboardViewModel.silentPaymentsScanningActive, + setSilentPaymentsScanning: () => + dashboardViewModel.setSilentPaymentsScanning( + !dashboardViewModel.silentPaymentsScanningActive, + ), + isTestnet: dashboardViewModel.isTestnet, ); }); }, @@ -243,6 +245,7 @@ class BalanceRowWidget extends StatelessWidget { required this.hasSilentPayments, required this.silentPaymentsScanningActive, required this.setSilentPaymentsScanning, + required this.isTestnet, super.key, }); @@ -258,6 +261,7 @@ class BalanceRowWidget extends StatelessWidget { final bool hasAdditionalBalance; final bool hasSilentPayments; final bool silentPaymentsScanningActive; + final bool isTestnet; final void Function() setSilentPaymentsScanning; // void _showBalanceDescription(BuildContext context) { @@ -333,14 +337,24 @@ class BalanceRowWidget extends StatelessWidget { maxLines: 1, textAlign: TextAlign.start), SizedBox(height: 6), - Text('${availableFiatBalance}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.textColor, - height: 1)), + if (isTestnet) + Text(S.current.testnet_coins_no_value, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).extension()!.textColor, + height: 1)), + if (!isTestnet) + Text('${availableFiatBalance}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.textColor, + height: 1)), ], ), ), @@ -432,17 +446,18 @@ class BalanceRowWidget extends StatelessWidget { textAlign: TextAlign.center, ), SizedBox(height: 4), - Text( - frozenFiatBalance, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context).extension()!.textColor, - height: 1, + if (!isTestnet) + Text( + frozenFiatBalance, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).extension()!.textColor, + height: 1, + ), ), - ), ], ), ), @@ -476,17 +491,18 @@ class BalanceRowWidget extends StatelessWidget { textAlign: TextAlign.center, ), SizedBox(height: 4), - Text( - '${additionalFiatBalance}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context).extension()!.textColor, - height: 1, + if (!isTestnet) + Text( + '${additionalFiatBalance}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).extension()!.textColor, + height: 1, + ), ), - ), ], ), if (hasSilentPayments) ...[ diff --git a/lib/src/screens/nodes/widgets/node_form.dart b/lib/src/screens/nodes/widgets/node_form.dart index ab8dcafdf2..e4f32b0d9c 100644 --- a/lib/src/screens/nodes/widgets/node_form.dart +++ b/lib/src/screens/nodes/widgets/node_form.dart @@ -19,7 +19,7 @@ class NodeForm extends StatelessWidget { _portController = TextEditingController(text: editingNode?.uri.port.toString()), _loginController = TextEditingController(text: editingNode?.login), _passwordController = TextEditingController(text: editingNode?.password), - _socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress){ + _socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress) { if (editingNode != null) { nodeViewModel ..setAddress((editingNode!.uri.host.toString())) @@ -60,7 +60,8 @@ class NodeForm extends StatelessWidget { _portController.addListener(() => nodeViewModel.port = _portController.text); _loginController.addListener(() => nodeViewModel.login = _loginController.text); _passwordController.addListener(() => nodeViewModel.password = _passwordController.text); - _socksAddressController.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text); + _socksAddressController + .addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text); } final NodeCreateOrEditViewModel nodeViewModel; @@ -103,6 +104,25 @@ class NodeForm extends StatelessWidget { ], ), SizedBox(height: 10.0), + Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Observer( + builder: (_) => StandardCheckbox( + value: nodeViewModel.useSSL, + gradientBackground: true, + borderColor: Theme.of(context).dividerColor, + iconColor: Colors.white, + onChanged: (value) => nodeViewModel.useSSL = value, + caption: S.of(context).use_ssl, + ), + ) + ], + ), + ), if (nodeViewModel.hasAuthCredentials) ...[ Row( children: [ @@ -123,25 +143,6 @@ class NodeForm extends StatelessWidget { )) ], ), - Padding( - padding: EdgeInsets.only(top: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Observer( - builder: (_) => StandardCheckbox( - value: nodeViewModel.useSSL, - gradientBackground: true, - borderColor: Theme.of(context).dividerColor, - iconColor: Colors.white, - onChanged: (value) => nodeViewModel.useSSL = value, - caption: S.of(context).use_ssl, - ), - ) - ], - ), - ), Padding( padding: EdgeInsets.only(top: 20), child: Row( @@ -163,44 +164,44 @@ class NodeForm extends StatelessWidget { ), Observer( builder: (_) => Column( - children: [ - Padding( - padding: EdgeInsets.only(top: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - StandardCheckbox( - value: nodeViewModel.useSocksProxy, - gradientBackground: true, - borderColor: Theme.of(context).dividerColor, - iconColor: Colors.white, - onChanged: (value) { - if (!value) { - _socksAddressController.text = ''; - } - nodeViewModel.useSocksProxy = value; - }, - caption: 'SOCKS Proxy', - ), - ], - ), - ), - if (nodeViewModel.useSocksProxy) ...[ - SizedBox(height: 10.0), - Row( - children: [ - Expanded( - child: BaseTextFormField( + children: [ + Padding( + padding: EdgeInsets.only(top: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + StandardCheckbox( + value: nodeViewModel.useSocksProxy, + gradientBackground: true, + borderColor: Theme.of(context).dividerColor, + iconColor: Colors.white, + onChanged: (value) { + if (!value) { + _socksAddressController.text = ''; + } + nodeViewModel.useSocksProxy = value; + }, + caption: 'SOCKS Proxy', + ), + ], + ), + ), + if (nodeViewModel.useSocksProxy) ...[ + SizedBox(height: 10.0), + Row( + children: [ + Expanded( + child: BaseTextFormField( controller: _socksAddressController, hintText: '[:]', validator: SocksProxyNodeAddressValidator(), )) - ], - ), - ] - ], - )), + ], + ), + ] + ], + )), ] ], ), diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 3f3e546b3b..e856d32008 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -102,7 +102,7 @@ class ReceivePage extends BasePage { return (addressListViewModel.type == WalletType.monero || addressListViewModel.type == WalletType.haven || addressListViewModel.type == WalletType.nano || - isElectrumWallet) + isElectrumWallet) ? KeyboardActions( config: KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.IOS, @@ -164,21 +164,21 @@ class ReceivePage extends BasePage { } if (item is WalletAddressListHeader) { - cell = HeaderTile( - title: S.of(context).addresses, - walletAddressListViewModel: addressListViewModel, - showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled, - showSearchButton: true, - trailingButtonTap: () => - Navigator.of(context).pushNamed(Routes.newSubaddress), - trailingIcon: Icon( - Icons.add, - size: 20, - color: Theme.of(context) - .extension()! - .iconsColor, - )); - } + cell = HeaderTile( + title: S.of(context).addresses, + walletAddressListViewModel: addressListViewModel, + showTrailingButton: + !addressListViewModel.isAutoGenerateSubaddressEnabled, + showSearchButton: true, + trailingButtonTap: () => + Navigator.of(context).pushNamed(Routes.newSubaddress), + trailingIcon: Icon( + Icons.add, + size: 20, + color: + Theme.of(context).extension()!.iconsColor, + )); + } if (item is WalletAddressListItem) { cell = Observer(builder: (_) { @@ -219,7 +219,7 @@ class ReceivePage extends BasePage { child: cell, ); })), - if (!addressListViewModel.hasSilentAddresses) + if (!addressListViewModel.isSilentPayments) Padding( padding: EdgeInsets.fromLTRB(24, 24, 24, 32), child: Text(S.of(context).electrum_address_disclaimer, diff --git a/lib/src/screens/rescan/rescan_page.dart b/lib/src/screens/rescan/rescan_page.dart index 3a0ba24730..f4dc865f03 100644 --- a/lib/src/screens/rescan/rescan_page.dart +++ b/lib/src/screens/rescan/rescan_page.dart @@ -11,7 +11,8 @@ class RescanPage extends BasePage { : _blockchainHeightWidgetKey = GlobalKey(); @override - String get title => S.current.rescan; + String get title => + _rescanViewModel.isSilentPaymentsScan ? S.current.silent_payments_scanning : S.current.rescan; final GlobalKey _blockchainHeightWidgetKey; final RescanViewModel _rescanViewModel; @@ -19,20 +20,19 @@ class RescanPage extends BasePage { Widget body(BuildContext context) { return Padding( padding: EdgeInsets.only(left: 24, right: 24, bottom: 24), - child: - Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - BlockchainHeightWidget(key: _blockchainHeightWidgetKey, - onHeightOrDateEntered: (value) => - _rescanViewModel.isButtonEnabled = value), + child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + BlockchainHeightWidget( + key: _blockchainHeightWidgetKey, + onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value, + isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan, + ), Observer( builder: (_) => LoadingPrimaryButton( - isLoading: - _rescanViewModel.state == RescanWalletState.rescaning, + isLoading: _rescanViewModel.state == RescanWalletState.rescaning, text: S.of(context).rescan, onPressed: () async { await _rescanViewModel.rescanCurrentWallet( - restoreHeight: - _blockchainHeightWidgetKey.currentState!.height); + restoreHeight: _blockchainHeightWidgetKey.currentState!.height); Navigator.of(context).pop(); }, color: Theme.of(context).primaryColor, diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 6853105067..d369978147 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -2,7 +2,6 @@ import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -167,7 +166,7 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin Navigator.of(context).pushNamed(Routes.rescan), ), if (DeviceInfo.instance.isMobile && FeatureFlag.isBackgroundSyncEnabled) ...[ diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index b57473cba9..2029b9f487 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -96,6 +96,7 @@ class WalletListBody extends StatefulWidget { class WalletListBodyState extends State { final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24); final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); + final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24); final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); @@ -161,7 +162,10 @@ class WalletListBodyState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ wallet.isEnabled - ? _imageFor(type: wallet.type) + ? _imageFor( + type: wallet.type, + isTestnet: wallet.isTestnet, + ) : nonWalletTypeIcon, SizedBox(width: 10), Flexible( @@ -296,9 +300,12 @@ class WalletListBodyState extends State { ); } - Image _imageFor({required WalletType type}) { + Image _imageFor({required WalletType type, bool? isTestnet}) { switch (type) { case WalletType.bitcoin: + if (isTestnet == true) { + return tBitcoinIcon; + } return bitcoinIcon; case WalletType.monero: return moneroIcon; diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index f98e0439b1..0467b18a28 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -1,6 +1,5 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/utils/device_info.dart'; -import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; @@ -134,7 +133,8 @@ class AddressTextField extends StatelessWidget { ), )), ], - if (this.options.contains(AddressTextFieldOption.qrCode)) ...[ + if (this.options.contains(AddressTextFieldOption.qrCode) && + DeviceInfo.instance.isMobile) ...[ Container( width: prefixIconWidth, height: prefixIconHeight, @@ -194,7 +194,7 @@ class AddressTextField extends StatelessWidget { Future _presentQRScanner(BuildContext context) async { bool isCameraPermissionGranted = - await PermissionHandler.checkPermission(Permission.camera, context); + await PermissionHandler.checkPermission(Permission.camera, context); if (!isCameraPermissionGranted) return; final code = await presentQRScanner(); if (code.isEmpty) { diff --git a/lib/src/widgets/blockchain_height_widget.dart b/lib/src/widgets/blockchain_height_widget.dart index 221f874468..fcfeedda32 100644 --- a/lib/src/widgets/blockchain_height_widget.dart +++ b/lib/src/widgets/blockchain_height_widget.dart @@ -12,13 +12,15 @@ class BlockchainHeightWidget extends StatefulWidget { this.onHeightChange, this.focusNode, this.onHeightOrDateEntered, - this.hasDatePicker = true}) - : super(key: key); + this.hasDatePicker = true, + this.isSilentPaymentsScan = false, + }) : super(key: key); final Function(int)? onHeightChange; final Function(bool)? onHeightOrDateEntered; final FocusNode? focusNode; final bool hasDatePicker; + final bool isSilentPaymentsScan; @override State createState() => BlockchainHeightState(); @@ -64,9 +66,10 @@ class BlockchainHeightState extends State { child: BaseTextFormField( focusNode: widget.focusNode, controller: restoreHeightController, - keyboardType: TextInputType.numberWithOptions( - signed: false, decimal: false), - hintText: S.of(context).widgets_restore_from_blockheight, + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false), + hintText: widget.isSilentPaymentsScan + ? S.of(context).silent_payments_scan_from_height + : S.of(context).widgets_restore_from_blockheight, ))) ], ), @@ -78,8 +81,7 @@ class BlockchainHeightState extends State { style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.w500, - color: - Theme.of(context).extension()!.titleColor), + color: Theme.of(context).extension()!.titleColor), ), ), Row( @@ -91,7 +93,9 @@ class BlockchainHeightState extends State { child: IgnorePointer( child: BaseTextFormField( controller: dateController, - hintText: S.of(context).widgets_restore_from_date, + hintText: widget.isSilentPaymentsScan + ? S.of(context).silent_payments_scan_from_date + : S.of(context).widgets_restore_from_date, )), ), )) @@ -100,13 +104,12 @@ class BlockchainHeightState extends State { Padding( padding: EdgeInsets.only(left: 40, right: 40, top: 24), child: Text( - S.of(context).restore_from_date_or_blockheight, + widget.isSilentPaymentsScan + ? S.of(context).silent_payments_scan_from_date_or_blockheight + : S.of(context).restore_from_date_or_blockheight, textAlign: TextAlign.center, style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.normal, - color: Theme.of(context).hintColor - ), + fontSize: 12, fontWeight: FontWeight.normal, color: Theme.of(context).hintColor), ), ) ] diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 08bdd82522..d72b339039 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -46,8 +46,6 @@ abstract class BalanceViewModelBase with Store { : isReversing = false, isShowCard = appStore.wallet!.walletInfo.isShowIntroCakePayCard, wallet = appStore.wallet! { - silentPaymentsScanningActive = hasSilentPayments && bitcoin!.getScanningActive(wallet); - reaction((_) => appStore.wallet, _onWalletChange); } @@ -66,17 +64,6 @@ abstract class BalanceViewModelBase with Store { @computed bool get hasSilentPayments => wallet.type == WalletType.bitcoin; - @observable - bool silentPaymentsScanningActive = false; - - @action - void setSilentPaymentsScanning(bool active) { - if (hasSilentPayments) { - bitcoin!.setScanningActive(wallet, active); - silentPaymentsScanningActive = active; - } - } - @computed double get price { final price = fiatConvertationStore.prices[appStore.wallet!.currency]; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index b08a0c3c7e..b4cd026fec 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -201,6 +201,14 @@ abstract class DashboardViewModelBase with Store { return true; }); + + if (hasSilentPayments) { + silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet); + + reaction((_) => wallet.syncStatus, (SyncStatus syncStatus) { + silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet); + }); + } } @observable @@ -287,14 +295,33 @@ abstract class DashboardViewModelBase with Store { @observable WalletBase, TransactionInfo> wallet; + @computed + bool get isTestnet => wallet.type == WalletType.bitcoin && bitcoin!.isTestnet(wallet); + + @computed bool get hasRescan => - (wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet)) || + wallet.type == WalletType.bitcoin || wallet.type == WalletType.monero || wallet.type == WalletType.haven; + @computed + bool get hasSilentPayments => hasRescan && wallet.type == WalletType.bitcoin; + final KeyService keyService; final SharedPreferences sharedPreferences; + @observable + bool silentPaymentsScanningActive = false; + + @action + void setSilentPaymentsScanning(bool active) { + silentPaymentsScanningActive = active; + + if (hasSilentPayments) { + bitcoin!.setScanningActive(wallet, active); + } + } + BalanceViewModel balanceViewModel; AppStore appStore; diff --git a/lib/view_model/rescan_view_model.dart b/lib/view_model/rescan_view_model.dart index e263f4a121..b10e29e698 100644 --- a/lib/view_model/rescan_view_model.dart +++ b/lib/view_model/rescan_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -21,6 +22,9 @@ abstract class RescanViewModelBase with Store { @observable bool isButtonEnabled; + @computed + bool get isSilentPaymentsScan => bitcoin!.hasSelectedSilentPayments(_wallet); + @action Future rescanCurrentWallet({required int restoreHeight}) async { state = RescanWalletState.rescaning; diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 5e843ad789..c1c275a875 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -183,6 +183,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo }) : _baseItems = [], selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type), _cryptoNumberFormat = NumberFormat(_cryptoNumberPattern), + hasAccounts = + appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.haven, amount = '', _settingsStore = appStore.settingsStore, super(appStore: appStore) { @@ -194,8 +196,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo _init(); selectedCurrency = walletTypeToCryptoCurrency(wallet.type); - _hasAccounts = - hasSilentAddresses || wallet.type == WalletType.monero || wallet.type == WalletType.haven; + hasAccounts = wallet.type == WalletType.monero || wallet.type == WalletType.haven; } static const String _cryptoNumberPattern = '0.00000000'; @@ -364,10 +365,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo } @observable - bool _hasAccounts = false; - - @computed - bool get hasAccounts => _hasAccounts; + bool hasAccounts; @computed String get accountLabel { @@ -382,21 +380,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return ''; } - @observable - // ignore: prefer_final_fields - bool? _hasSilentAddresses = null; - - @computed - bool get hasSilentAddresses => _hasSilentAddresses ?? wallet.type == WalletType.bitcoin; - // @computed - // bool get hasSilentAddresses => - // _hasSilentAddresses ?? - // wallet.type == WalletType.bitcoin && - // wallet.walletAddresses.addressPageType == btc.AddressType.p2sp; - @computed bool get hasAddressList => - hasSilentAddresses || wallet.type == WalletType.monero || wallet.type == WalletType.haven || wallet.type == WalletType.bitcoinCash || @@ -409,9 +394,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash; + @computed + bool get isSilentPayments => bitcoin!.hasSelectedSilentPayments(wallet); + @computed bool get isAutoGenerateSubaddressEnabled => wallet.type == WalletType.bitcoin - ? !bitcoin!.hasSelectedSilentPayments(wallet) + ? !isSilentPayments : _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled; List _baseItems; diff --git a/lib/view_model/wallet_list/wallet_list_item.dart b/lib/view_model/wallet_list/wallet_list_item.dart index a644c07b3a..24b1a3bd00 100644 --- a/lib/view_model/wallet_list/wallet_list_item.dart +++ b/lib/view_model/wallet_list/wallet_list_item.dart @@ -7,6 +7,7 @@ class WalletListItem { required this.key, this.isCurrent = false, this.isEnabled = true, + this.isTestnet = false, }); final String name; @@ -14,4 +15,5 @@ class WalletListItem { final bool isCurrent; final dynamic key; final bool isEnabled; + final bool isTestnet; } diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index b31133c7d9..2ed6358f44 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -61,6 +61,7 @@ abstract class WalletListViewModelBase with Store { key: info.key, isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type, isEnabled: availableWalletTypes.contains(info.type), + isTestnet: info.network?.toLowerCase().contains('testnet') ?? false, ), ), ); diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index a66f85da4f..067a50ca8b 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -610,6 +610,9 @@ "sign_up": "اشتراك", "signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ", "signup_for_card_accept_terms": "قم بالتسجيل للحصول على البطاقة وقبول الشروط.", + "silent_payments_scan_from_date": "فحص من التاريخ", + "silent_payments_scan_from_date_or_blockheight": "يرجى إدخال ارتفاع الكتلة الذي تريد بدء المسح الضوئي للمدفوعات الصامتة الواردة ، أو استخدام التاريخ بدلاً من ذلك. يمكنك اختيار ما إذا كانت المحفظة تواصل مسح كل كتلة ، أو تتحقق فقط من الارتفاع المحدد.", + "silent_payments_scan_from_height": "فحص من ارتفاع الكتلة", "silent_payments_scanning": "المدفوعات الصامتة المسح الضوئي", "slidable": "قابل للانزلاق", "sort_by": "ترتيب حسب", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "محفظتك تتم مزامنتها", "template": "قالب", "template_name": "اسم القالب", + "testnet_coins_no_value": "عملات TestNet ليس لها قيمة", "third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!", "third_intro_title": "يتماشي Yat بلطف مع الآخرين", "thorchain_taproot_address_not_supported": "لا يدعم مزود Thorchain عناوين Taproot. يرجى تغيير العنوان أو تحديد مزود مختلف.", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index cc48a56e98..626ed5ae81 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -610,6 +610,9 @@ "sign_up": "Регистрация", "signTransaction": "Подпишете транзакция", "signup_for_card_accept_terms": "Регистрайте се за картата и приемете условията.", + "silent_payments_scan_from_date": "Сканиране от дата", + "silent_payments_scan_from_date_or_blockheight": "Моля, въведете височината на блока, която искате да започнете да сканирате за входящи безшумни плащания, или вместо това използвайте датата. Можете да изберете дали портфейлът продължава да сканира всеки блок или проверява само определената височина.", + "silent_payments_scan_from_height": "Сканиране от височината на блока", "silent_payments_scanning": "Безшумни плащания за сканиране", "slidable": "Плъзгащ се", "sort_by": "Сортирай по", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "Вашият портфейл се синхронизира", "template": "Шаблон", "template_name": "Име на шаблон", + "testnet_coins_no_value": "Тестовите монети нямат стойност", "third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!", "third_intro_title": "Yat добре се сработва с други", "thorchain_taproot_address_not_supported": "Доставчикът на Thorchain не поддържа адреси на TapRoot. Моля, променете адреса или изберете друг доставчик.", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index f3076cf6c5..0e4253ccab 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -610,6 +610,9 @@ "sign_up": "Registrovat se", "signTransaction": "Podepsat transakci", "signup_for_card_accept_terms": "Zaregistrujte se pro kartu a souhlaste s podmínkami.", + "silent_payments_scan_from_date": "Skenovat od data", + "silent_payments_scan_from_date_or_blockheight": "Zadejte výšku bloku, kterou chcete začít skenovat, zda jsou přicházející tiché platby, nebo místo toho použijte datum. Můžete si vybrat, zda peněženka pokračuje v skenování každého bloku nebo zkontroluje pouze zadanou výšku.", + "silent_payments_scan_from_height": "Skenování z výšky bloku", "silent_payments_scanning": "Skenování tichých plateb", "slidable": "Posuvné", "sort_by": "Seřazeno podle", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "Vaše peněženka se synchronizuje", "template": "Šablona", "template_name": "Název šablony", + "testnet_coins_no_value": "Mince TestNet nemají žádnou hodnotu", "third_intro_content": "Yat existuje i mimo Cake Wallet. Jakákoliv adresa peněženky na světě může být nahrazena Yatem!", "third_intro_title": "Yat dobře spolupracuje s ostatními", "thorchain_taproot_address_not_supported": "Poskytovatel Thorchain nepodporuje adresy Taproot. Změňte adresu nebo vyberte jiného poskytovatele.", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index cf4cbb7fa2..b6f9d475c9 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -611,6 +611,9 @@ "sign_up": "Anmelden", "signTransaction": "Transaktion unterzeichnen", "signup_for_card_accept_terms": "Melden Sie sich für die Karte an und akzeptieren Sie die Bedingungen.", + "silent_payments_scan_from_date": "Scan ab Datum", + "silent_payments_scan_from_date_or_blockheight": "Bitte geben Sie die Blockhöhe ein, die Sie für eingehende stille Zahlungen scannen möchten, oder verwenden Sie stattdessen das Datum. Sie können wählen, ob die Brieftasche jeden Block scannt oder nur die angegebene Höhe überprüft.", + "silent_payments_scan_from_height": "Scan aus der Blockhöhe scannen", "silent_payments_scanning": "Stille Zahlungen scannen", "slidable": "Verschiebbar", "sort_by": "Sortiere nach", @@ -647,6 +650,7 @@ "syncing_wallet_alert_title": "Ihr Wallet wird synchronisiert", "template": "Vorlage", "template_name": "Vorlagenname", + "testnet_coins_no_value": "Testnet -Münzen haben keinen Wert", "third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!", "third_intro_title": "Yat spielt gut mit anderen", "thorchain_taproot_address_not_supported": "Der Thorchain -Anbieter unterstützt keine Taproot -Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index d31435b484..97546bf88a 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -610,6 +610,9 @@ "sign_up": "Sign Up", "signTransaction": "Sign Transaction", "signup_for_card_accept_terms": "Sign up for the card and accept the terms.", + "silent_payments_scan_from_date": "Scan from date", + "silent_payments_scan_from_date_or_blockheight": "Please enter the block height you want to start scanning for incoming silent payments, or, use the date instead. You can choose if the wallet continues scanning every block, or checks only the specified height.", + "silent_payments_scan_from_height": "Scan from block height", "silent_payments_scanning": "Silent Payments Scanning", "slidable": "Slidable", "sort_by": "Sort by", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "Your wallet is syncing", "template": "Template", "template_name": "Template Name", + "testnet_coins_no_value": "Testnet coins have no value", "third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!", "third_intro_title": "Yat plays nicely with others", "thorchain_taproot_address_not_supported": "The ThorChain provider does not support Taproot addresses. Please change the address or select a different provider.", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 6aa1cfbd81..2791de8d64 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -611,6 +611,9 @@ "sign_up": "Registrarse", "signTransaction": "Firmar transacción", "signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.", + "silent_payments_scan_from_date": "Escanear desde la fecha", + "silent_payments_scan_from_date_or_blockheight": "Ingrese la altura del bloque que desea comenzar a escanear para pagos silenciosos entrantes, o use la fecha en su lugar. Puede elegir si la billetera continúa escaneando cada bloque, o verifica solo la altura especificada.", + "silent_payments_scan_from_height": "Escanear desde la altura del bloque", "silent_payments_scanning": "Escaneo de pagos silenciosos", "slidable": "deslizable", "sort_by": "Ordenar por", @@ -647,6 +650,7 @@ "syncing_wallet_alert_title": "Tu billetera se está sincronizando", "template": "Plantilla", "template_name": "Nombre de la plantilla", + "testnet_coins_no_value": "Las monedas de prueba no tienen valor", "third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!", "third_intro_title": "Yat juega muy bien con otras", "thorchain_taproot_address_not_supported": "El proveedor de Thorchain no admite las direcciones de Taproot. Cambie la dirección o seleccione un proveedor diferente.", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 7b7ce56df7..50772e1120 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -610,6 +610,9 @@ "sign_up": "S'inscrire", "signTransaction": "Signer une transaction", "signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.", + "silent_payments_scan_from_date": "Analyser à partir de la date", + "silent_payments_scan_from_date_or_blockheight": "Veuillez saisir la hauteur du bloc que vous souhaitez commencer à scanner pour les paiements silencieux entrants, ou utilisez la date à la place. Vous pouvez choisir si le portefeuille continue de numériser chaque bloc ou ne vérifie que la hauteur spécifiée.", + "silent_payments_scan_from_height": "Scan à partir de la hauteur du bloc", "silent_payments_scanning": "Payments silencieux SCANNING", "slidable": "Glissable", "sort_by": "Trier par", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "Votre portefeuille (wallet) est en cours de synchronisation", "template": "Modèle", "template_name": "Nom du modèle", + "testnet_coins_no_value": "Les pièces TestNet n'ont aucune valeur", "third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !", "third_intro_title": "Yat est universel", "thorchain_taproot_address_not_supported": "Le fournisseur de Thorchain ne prend pas en charge les adresses de tapoot. Veuillez modifier l'adresse ou sélectionner un autre fournisseur.", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 6c3de1e037..6c8a9fabe2 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -612,6 +612,9 @@ "sign_up": "Shiga", "signTransaction": "Sa hannu Ma'amala", "signup_for_card_accept_terms": "Yi rajista don katin kuma karɓi sharuɗɗan.", + "silent_payments_scan_from_date": "Scan daga kwanan wata", + "silent_payments_scan_from_date_or_blockheight": "Da fatan za a shigar da toshe wurin da kake son fara bincika don biyan silins mai shigowa, ko, yi amfani da kwanan wata. Zaka iya zabar idan walat ɗin ya ci gaba da bincika kowane toshe, ko duba tsinkaye da aka ƙayyade.", + "silent_payments_scan_from_height": "Scan daga tsayin daka", "silent_payments_scanning": "Silent biya scanning", "slidable": "Mai iya zamewa", "sort_by": "Kasa", @@ -648,6 +651,7 @@ "syncing_wallet_alert_title": "Walat ɗin ku yana aiki tare", "template": "Samfura", "template_name": "Sunan Samfura", + "testnet_coins_no_value": "TalkNet tsabar kudi ba su da darajar", "third_intro_content": "Yats suna zaune a wajen Kek Wallet, kuma. Ana iya maye gurbin kowane adireshin walat a duniya da Yat!", "third_intro_title": "Yat yana wasa da kyau tare da wasu", "thorchain_taproot_address_not_supported": "Mai ba da tallafi na ThorChain baya goyan bayan adreshin taproot. Da fatan za a canza adireshin ko zaɓi mai bayarwa daban.", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 82445dfa3f..8fb2ea0810 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -612,6 +612,9 @@ "sign_up": "साइन अप करें", "signTransaction": "लेन-देन पर हस्ताक्षर करें", "signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।", + "silent_payments_scan_from_date": "तिथि से स्कैन करना", + "silent_payments_scan_from_date_or_blockheight": "कृपया उस ब्लॉक ऊंचाई दर्ज करें जिसे आप आने वाले मूक भुगतान के लिए स्कैन करना शुरू करना चाहते हैं, या, इसके बजाय तारीख का उपयोग करें। आप चुन सकते हैं कि क्या वॉलेट हर ब्लॉक को स्कैन करना जारी रखता है, या केवल निर्दिष्ट ऊंचाई की जांच करता है।", + "silent_payments_scan_from_height": "ब्लॉक ऊंचाई से स्कैन करें", "silent_payments_scanning": "मूक भुगतान स्कैनिंग", "slidable": "फिसलने लायक", "sort_by": "इसके अनुसार क्रमबद्ध करें", @@ -648,6 +651,7 @@ "syncing_wallet_alert_title": "आपका वॉलेट सिंक हो रहा है", "template": "खाका", "template_name": "टेम्पलेट नाम", + "testnet_coins_no_value": "टेस्टनेट सिक्कों का कोई मूल्य नहीं है", "third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!", "third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है", "thorchain_taproot_address_not_supported": "थोरचेन प्रदाता टैपरोट पते का समर्थन नहीं करता है। कृपया पता बदलें या एक अलग प्रदाता का चयन करें।", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 4c64a9a349..df1b1ce113 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -610,6 +610,9 @@ "sign_up": "Prijavite se", "signTransaction": "Potpišite transakciju", "signup_for_card_accept_terms": "Prijavite se za karticu i prihvatite uvjete.", + "silent_payments_scan_from_date": "Skeniranje iz datuma", + "silent_payments_scan_from_date_or_blockheight": "Unesite visinu bloka koju želite započeti skeniranje za dolazna tiha plaćanja ili umjesto toga upotrijebite datum. Možete odabrati da li novčanik nastavlja skenirati svaki blok ili provjerava samo navedenu visinu.", + "silent_payments_scan_from_height": "Skeniranje s visine bloka", "silent_payments_scanning": "Skeniranje tihih plaćanja", "slidable": "Klizna", "sort_by": "Poredaj po", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "Vaš novčanik se sinkronizira", "template": "Predložak", "template_name": "Naziv predloška", + "testnet_coins_no_value": "TestNet kovanice nemaju vrijednost", "third_intro_content": "Yats žive i izvan Cake Wallet -a. Bilo koja adresa novčanika na svijetu može se zamijeniti Yat!", "third_intro_title": "Yat se lijepo igra s drugima", "thorchain_taproot_address_not_supported": "Thorchain pružatelj ne podržava Taproot adrese. Promijenite adresu ili odaberite drugog davatelja usluga.", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 90a92a8f24..32d0a997a9 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -613,6 +613,9 @@ "sign_up": "Daftar", "signTransaction": "Tandatangani Transaksi", "signup_for_card_accept_terms": "Daftar untuk kartu dan terima syarat dan ketentuan.", + "silent_payments_scan_from_date": "Pindai dari tanggal", + "silent_payments_scan_from_date_or_blockheight": "Harap masukkan ketinggian blok yang ingin Anda mulai pemindaian untuk pembayaran diam yang masuk, atau, gunakan tanggal sebagai gantinya. Anda dapat memilih jika dompet terus memindai setiap blok, atau memeriksa hanya ketinggian yang ditentukan.", + "silent_payments_scan_from_height": "Pindai dari Tinggi Blok", "silent_payments_scanning": "Pemindaian pembayaran diam", "slidable": "Dapat digeser", "sort_by": "Sortir dengan", @@ -649,6 +652,7 @@ "syncing_wallet_alert_title": "Dompet Anda sedang disinkronkan", "template": "Template", "template_name": "Nama Templat", + "testnet_coins_no_value": "Koin TestNet tidak memiliki nilai", "third_intro_content": "Yats hidup di luar Cake Wallet juga. Setiap alamat dompet di dunia dapat diganti dengan Yat!", "third_intro_title": "Yat bermain baik dengan yang lain", "thorchain_taproot_address_not_supported": "Penyedia Thorchain tidak mendukung alamat Taproot. Harap ubah alamatnya atau pilih penyedia yang berbeda.", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 9795950484..b3ba636ba6 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -612,6 +612,9 @@ "sign_up": "Registrati", "signTransaction": "Firma la transazione", "signup_for_card_accept_terms": "Registrati per la carta e accetta i termini.", + "silent_payments_scan_from_date": "Scansionare dalla data", + "silent_payments_scan_from_date_or_blockheight": "Inserisci l'altezza del blocco che si desidera iniziare la scansione per i pagamenti silenziosi in arrivo o, utilizza invece la data. Puoi scegliere se il portafoglio continua a scansionare ogni blocco o controlla solo l'altezza specificata.", + "silent_payments_scan_from_height": "Scansione dall'altezza del blocco", "silent_payments_scanning": "Scansione di pagamenti silenziosi", "slidable": "Scorrevole", "sort_by": "Ordina per", @@ -648,6 +651,7 @@ "syncing_wallet_alert_title": "Il tuo portafoglio si sta sincronizzando", "template": "Modello", "template_name": "Nome modello", + "testnet_coins_no_value": "Le monete TestNet non hanno valore", "third_intro_content": "Yat può funzionare anche fuori da Cake Wallet. Qualsiasi indirizzo di portafoglio sulla terra può essere sostituito con uno Yat!", "third_intro_title": "Yat gioca bene con gli altri", "thorchain_taproot_address_not_supported": "Il provider di Thorchain non supporta gli indirizzi di TapRoot. Si prega di modificare l'indirizzo o selezionare un fornitore diverso.", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index b3a3cefe0f..7274f83ab5 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -611,6 +611,9 @@ "sign_up": "サインアップ", "signTransaction": "トランザクションに署名する", "signup_for_card_accept_terms": "カードにサインアップして、利用規約に同意してください。", + "silent_payments_scan_from_date": "日付からスキャンします", + "silent_payments_scan_from_date_or_blockheight": "着信のサイレント決済のためにスキャンを開始するブロックの高さを入力するか、代わりに日付を使用してください。ウォレットがすべてのブロックをスキャンし続けるか、指定された高さのみをチェックするかどうかを選択できます。", + "silent_payments_scan_from_height": "ブロックの高さからスキャンします", "silent_payments_scanning": "サイレントペイメントスキャン", "slidable": "スライド可能", "sort_by": "並び替え", @@ -647,6 +650,7 @@ "syncing_wallet_alert_title": "ウォレットは同期中です", "template": "テンプレート", "template_name": "テンプレート名", + "testnet_coins_no_value": "テストネットコインには価値がありません", "third_intro_content": "YatsはCakeWalletの外にも住んでいます。 地球上のどのウォレットアドレスもYatに置き換えることができます!", "third_intro_title": "Yatは他の人とうまく遊ぶ", "thorchain_taproot_address_not_supported": "Thorchainプロバイダーは、TapRootアドレスをサポートしていません。アドレスを変更するか、別のプロバイダーを選択してください。", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index b2908056f7..b38a5f5cd5 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -611,6 +611,9 @@ "sign_up": "가입", "signTransaction": "거래 서명", "signup_for_card_accept_terms": "카드에 가입하고 약관에 동의합니다.", + "silent_payments_scan_from_date": "날짜부터 스캔하십시오", + "silent_payments_scan_from_date_or_blockheight": "들어오는 사일런트 결제를 위해 스캔을 시작하려는 블록 높이를 입력하거나 대신 날짜를 사용하십시오. 지갑이 모든 블록을 계속 스캔하는지 여부를 선택하거나 지정된 높이 만 확인할 수 있습니다.", + "silent_payments_scan_from_height": "블록 높이에서 스캔하십시오", "silent_payments_scanning": "조용한 지불 스캔", "slidable": "슬라이딩 가능", "sort_by": "정렬 기준", @@ -647,6 +650,7 @@ "syncing_wallet_alert_title": "지갑 동기화 중", "template": "주형", "template_name": "템플릿 이름", + "testnet_coins_no_value": "Testnet 코인은 가치가 없습니다", "third_intro_content": "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!", "third_intro_title": "Yat는 다른 사람들과 잘 놉니다.", "thorchain_taproot_address_not_supported": "Thorchain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 공급자를 선택하십시오.", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 1453da4cc8..68fcaeb52b 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -610,6 +610,9 @@ "sign_up": "ဆိုင်းအပ်", "signTransaction": "ငွေလွှဲဝင်ပါ။", "signup_for_card_accept_terms": "ကတ်အတွက် စာရင်းသွင်းပြီး စည်းကမ်းချက်များကို လက်ခံပါ။", + "silent_payments_scan_from_date": "ရက်စွဲမှစကင်ဖတ်ပါ", + "silent_payments_scan_from_date_or_blockheight": "ကျေးဇူးပြု. သင်ဝင်လာသောအသံတိတ်ငွေပေးချေမှုအတွက်သင်စကင်ဖတ်စစ်ဆေးလိုသည့်အမြင့်ကိုဖြည့်ပါ။ သို့မဟုတ်နေ့စွဲကိုသုံးပါ။ Wallet သည်လုပ်ကွက်တိုင်းကိုဆက်လက်စကင်ဖတ်စစ်ဆေးပါကသို့မဟုတ်သတ်မှတ်ထားသောအမြင့်ကိုသာစစ်ဆေးပါကသင်ရွေးချယ်နိုင်သည်။", + "silent_payments_scan_from_height": "ပိတ်ပင်တားဆီးမှုအမြင့်ကနေ scan", "silent_payments_scanning": "အသံတိတ်ငွေပေးချေမှု scanning", "slidable": "လျှောချနိုင်သည်။", "sort_by": "အလိုက်စဥ်သည်", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "သင့်ပိုက်ဆံအိတ်ကို စင့်ခ်လုပ်နေပါသည်။", "template": "ပုံစံခွက်", "template_name": "နမူနာပုံစံ", + "testnet_coins_no_value": "Testnet ဒင်္ဂါးပြားတန်ဖိုးမရှိပါ", "third_intro_content": "Yats သည် Cake Wallet အပြင်ဘက်တွင် နေထိုင်ပါသည်။ ကမ္ဘာပေါ်ရှိ မည်သည့်ပိုက်ဆံအိတ်လိပ်စာကို Yat ဖြင့် အစားထိုးနိုင်ပါသည်။", "third_intro_title": "Yat သည် အခြားသူများနှင့် ကောင်းစွာကစားသည်။", "thorchain_taproot_address_not_supported": "Thorchain Provider သည် Taproot လိပ်စာများကိုမထောက်ခံပါ။ ကျေးဇူးပြု. လိပ်စာကိုပြောင်းပါသို့မဟုတ်အခြားပံ့ပိုးပေးသူကိုရွေးချယ်ပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index b82019e6bd..ccd25e2739 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -610,6 +610,9 @@ "sign_up": "Aanmelden", "signTransaction": "Transactie ondertekenen", "signup_for_card_accept_terms": "Meld je aan voor de kaart en accepteer de voorwaarden.", + "silent_payments_scan_from_date": "Scan vanaf datum", + "silent_payments_scan_from_date_or_blockheight": "Voer de blokhoogte in die u wilt beginnen met scannen op inkomende stille betalingen, of gebruik in plaats daarvan de datum. U kunt kiezen of de portemonnee elk blok blijft scannen of alleen de opgegeven hoogte controleert.", + "silent_payments_scan_from_height": "Scan van blokhoogte", "silent_payments_scanning": "Stille betalingen scannen", "slidable": "Verschuifbaar", "sort_by": "Sorteer op", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "Uw portemonnee wordt gesynchroniseerd", "template": "Sjabloon", "template_name": "Sjabloonnaam", + "testnet_coins_no_value": "Testnet -munten hebben geen waarde", "third_intro_content": "Yats wonen ook buiten Cake Wallet. Elk portemonnee-adres op aarde kan worden vervangen door een Yat!", "third_intro_title": "Yat speelt leuk met anderen", "thorchain_taproot_address_not_supported": "De Thorchain -provider ondersteunt geen Taprooot -adressen. Wijzig het adres of selecteer een andere provider.", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index c1e677284f..a464e78f27 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -610,6 +610,9 @@ "sign_up": "Zarejestruj się", "signTransaction": "Podpisz transakcję", "signup_for_card_accept_terms": "Zarejestruj się, aby otrzymać kartę i zaakceptuj warunki.", + "silent_payments_scan_from_date": "Skanuj z daty", + "silent_payments_scan_from_date_or_blockheight": "Wprowadź wysokość bloku, którą chcesz rozpocząć skanowanie w poszukiwaniu cichej płatności lub zamiast tego skorzystaj z daty. Możesz wybrać, czy portfel kontynuuje skanowanie każdego bloku, lub sprawdza tylko określoną wysokość.", + "silent_payments_scan_from_height": "Skanuj z wysokości bloku", "silent_payments_scanning": "Skanowanie cichych płatności", "slidable": "Przesuwne", "sort_by": "Sortuj według", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "Twój portfel się synchronizuje", "template": "Szablon", "template_name": "Nazwa szablonu", + "testnet_coins_no_value": "Monety testowe nie mają wartości", "third_intro_content": "Yats mieszkają również poza Cake Wallet. Każdy adres portfela na ziemi można zastąpić Yat!", "third_intro_title": "Yat ładnie bawi się z innymi", "thorchain_taproot_address_not_supported": "Dostawca Thorchain nie obsługuje adresów TAPROOT. Zmień adres lub wybierz innego dostawcę.", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 77e105b04e..e1e50252fa 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -612,7 +612,10 @@ "sign_up": "Inscrever-se", "signTransaction": "Assinar transação", "signup_for_card_accept_terms": "Cadastre-se no cartão e aceite os termos.", - "silent_payments_scanning": "Scanear Pagamentos Silenciosos", + "silent_payments_scan_from_date": "Escanear a partir da data", + "silent_payments_scan_from_date_or_blockheight": "Por favor, insira a altura do bloco que deseja iniciar o escaneamento para obter pagamentos silenciosos ou use a data. Você pode escolher se a carteira continua digitalizando cada bloco ou verifica apenas a altura especificada.", + "silent_payments_scan_from_height": "Escanear a partir da altura do bloco", + "silent_payments_scanning": "Escanear Pagamentos Silenciosos", "slidable": "Deslizável", "sort_by": "Ordenar por", "spend_key_private": "Chave de gastos (privada)", @@ -648,6 +651,7 @@ "syncing_wallet_alert_title": "Sua carteira está sincronizando", "template": "Modelo", "template_name": "Nome do modelo", + "testnet_coins_no_value": "As moedas de teste não têm valor", "third_intro_content": "Yats também mora fora da Cake Wallet. Qualquer endereço de carteira na Terra pode ser substituído por um Yat!", "third_intro_title": "Yat joga bem com os outros", "thorchain_taproot_address_not_supported": "O provedor de Thorchain não suporta endereços de raiz de Tap. Altere o endereço ou selecione um provedor diferente.", @@ -807,4 +811,4 @@ "you_will_get": "Converter para", "you_will_send": "Converter de", "yy": "aa" -} +} \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index c793ef6b12..74699d479b 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -611,6 +611,9 @@ "sign_up": "Зарегистрироваться", "signTransaction": "Подписать транзакцию", "signup_for_card_accept_terms": "Подпишитесь на карту и примите условия.", + "silent_payments_scan_from_date": "Сканирование с даты", + "silent_payments_scan_from_date_or_blockheight": "Пожалуйста, введите высоту блока, которую вы хотите начать сканирование для входящих молчаливых платежей, или вместо этого используйте дату. Вы можете выбрать, продолжает ли кошелек сканировать каждый блок или проверять только указанную высоту.", + "silent_payments_scan_from_height": "Сканирование с высоты блока", "silent_payments_scanning": "Сканирование безмолвных платежей", "slidable": "Скользящий", "sort_by": "Сортировать по", @@ -647,6 +650,7 @@ "syncing_wallet_alert_title": "Ваш кошелек синхронизируется", "template": "Шаблон", "template_name": "Имя Шаблона", + "testnet_coins_no_value": "Монеты теста не имеют значения", "third_intro_content": "Yat находятся за пределами Cake Wallet. Любой адрес кошелька на земле можно заменить на Yat!", "third_intro_title": "Yat хорошо взаимодействует с другими", "thorchain_taproot_address_not_supported": "Поставщик Thorchain не поддерживает адреса taproot. Пожалуйста, измените адрес или выберите другого поставщика.", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 1839cc930d..6c5ee98f4a 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -610,6 +610,9 @@ "sign_up": "สมัครสมาชิก", "signTransaction": "ลงนามในการทำธุรกรรม", "signup_for_card_accept_terms": "ลงทะเบียนสำหรับบัตรและยอมรับเงื่อนไข", + "silent_payments_scan_from_date": "สแกนตั้งแต่วันที่", + "silent_payments_scan_from_date_or_blockheight": "โปรดป้อนความสูงของบล็อกที่คุณต้องการเริ่มการสแกนสำหรับการชำระเงินแบบเงียบ ๆ หรือใช้วันที่แทน คุณสามารถเลือกได้ว่ากระเป๋าเงินยังคงสแกนทุกบล็อกหรือตรวจสอบความสูงที่ระบุเท่านั้น", + "silent_payments_scan_from_height": "สแกนจากความสูงของบล็อก", "silent_payments_scanning": "การสแกนการชำระเงินแบบเงียบ", "slidable": "เลื่อนได้", "sort_by": "เรียงตาม", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "กระเป๋าสตางค์ของคุณกำลังซิงค์", "template": "แบบฟอร์ม", "template_name": "ชื่อแม่แบบ", + "testnet_coins_no_value": "Testnet Coins ไม่มีค่า", "third_intro_content": "Yat อาศัยอยู่นอก Cake Wallet ด้วย ที่อยู่กระเป๋าใดๆ ทั่วโลกสามารถแทนด้วย Yat ได้อีกด้วย!", "third_intro_title": "Yat ปฏิบัติตนอย่างดีกับผู้อื่น", "thorchain_taproot_address_not_supported": "ผู้ให้บริการ Thorchain ไม่รองรับที่อยู่ taproot โปรดเปลี่ยนที่อยู่หรือเลือกผู้ให้บริการอื่น", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 4d3ef91c04..ca37f2f481 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -610,6 +610,9 @@ "sign_up": "Mag -sign up", "signTransaction": "Mag-sign Transaksyon", "signup_for_card_accept_terms": "Mag -sign up para sa card at tanggapin ang mga termino.", + "silent_payments_scan_from_date": "I -scan mula sa petsa", + "silent_payments_scan_from_date_or_blockheight": "Mangyaring ipasok ang taas ng block na nais mong simulan ang pag -scan para sa papasok na tahimik na pagbabayad, o, gamitin ang petsa sa halip. Maaari kang pumili kung ang pitaka ay patuloy na pag -scan sa bawat bloke, o suriin lamang ang tinukoy na taas.", + "silent_payments_scan_from_height": "I -scan mula sa taas ng block", "silent_payments_scanning": "Tahimik na pag -scan ng mga pagbabayad", "slidable": "Slidable", "sort_by": "Pag -uri -uriin sa pamamagitan ng", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "Ang iyong pitaka ay nag -sync", "template": "Template", "template_name": "Pangalan ng Template", + "testnet_coins_no_value": "Ang mga barya ng testnet ay walang halaga", "third_intro_content": "Ang mga yats ay nakatira sa labas ng cake wallet, din. Ang anumang address ng pitaka sa mundo ay maaaring mapalitan ng isang yat!", "third_intro_title": "Si Yat ay mahusay na gumaganap sa iba", "thorchain_taproot_address_not_supported": "Ang Tagabigay ng Thorchain ay hindi sumusuporta sa mga address ng taproot. Mangyaring baguhin ang address o pumili ng ibang provider.", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 55fbf6036b..e12853b92b 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -610,6 +610,9 @@ "sign_up": "Kaydol", "signTransaction": "İşlem İmzala", "signup_for_card_accept_terms": "Kart için kaydol ve koşulları kabul et.", + "silent_payments_scan_from_date": "Tarihten tarama", + "silent_payments_scan_from_date_or_blockheight": "Lütfen gelen sessiz ödemeler için taramaya başlamak istediğiniz blok yüksekliğini girin veya bunun yerine tarihi kullanın. Cüzdanın her bloğu taramaya devam edip etmediğini veya yalnızca belirtilen yüksekliği kontrol edip etmediğini seçebilirsiniz.", + "silent_payments_scan_from_height": "Blok yüksekliğinden tarama", "silent_payments_scanning": "Sessiz Ödemeler Taraması", "slidable": "kaydırılabilir", "sort_by": "Göre sırala", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "Cüzdanınız senkronize ediliyor", "template": "Şablon", "template_name": "şablon adı", + "testnet_coins_no_value": "TestNet paralarının değeri yok", "third_intro_content": "Yat'lar Cake Wallet'ın dışında da çalışabilir. Dünya üzerindeki herhangi bir cüzdan adresi Yat ile değiştirilebilir!", "third_intro_title": "Yat diğerleriyle iyi çalışır", "thorchain_taproot_address_not_supported": "Thorchain sağlayıcısı Taproot adreslerini desteklemiyor. Lütfen adresi değiştirin veya farklı bir sağlayıcı seçin.", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index d48c5eb3db..63e4242b18 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -611,6 +611,9 @@ "sign_up": "Зареєструватися", "signTransaction": "Підписати транзакцію", "signup_for_card_accept_terms": "Зареєструйтеся на картку та прийміть умови.", + "silent_payments_scan_from_date": "Сканувати з дати", + "silent_payments_scan_from_date_or_blockheight": "Введіть висоту блоку, яку ви хочете почати сканувати для вхідних мовчазних платежів, або скористайтеся датою замість цього. Ви можете вибрати, якщо гаманець продовжує сканувати кожен блок, або перевіряє лише вказану висоту.", + "silent_payments_scan_from_height": "Сканування від висоти блоку", "silent_payments_scanning": "Мовчазні платежі сканування", "slidable": "Розсувний", "sort_by": "Сортувати за", @@ -647,6 +650,7 @@ "syncing_wallet_alert_title": "Ваш гаманець синхронізується", "template": "Шаблон", "template_name": "Назва шаблону", + "testnet_coins_no_value": "Монети TestNet не мають значення", "third_intro_content": "Yat знаходиться за межами Cake Wallet. Будь-яку адресу гаманця на землі можна замінити на Yat!", "third_intro_title": "Yat добре взаємодіє з іншими", "thorchain_taproot_address_not_supported": "Постачальник Thorchain не підтримує адреси Taproot. Будь ласка, змініть адресу або виберіть іншого постачальника.", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index b5255bc150..171c2b0d28 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -612,6 +612,9 @@ "sign_up": "سائن اپ", "signTransaction": "۔ﮟﯾﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", "signup_for_card_accept_terms": "کارڈ کے لیے سائن اپ کریں اور شرائط کو قبول کریں۔", + "silent_payments_scan_from_date": "تاریخ سے اسکین کریں", + "silent_payments_scan_from_date_or_blockheight": "براہ کرم بلاک اونچائی میں داخل ہوں جس سے آپ آنے والی خاموش ادائیگیوں کے لئے اسکیننگ شروع کرنا چاہتے ہیں ، یا اس کے بجائے تاریخ کا استعمال کریں۔ آپ یہ منتخب کرسکتے ہیں کہ اگر پرس ہر بلاک کو اسکیننگ جاری رکھے ہوئے ہے ، یا صرف مخصوص اونچائی کی جانچ پڑتال کرتا ہے۔", + "silent_payments_scan_from_height": "بلاک اونچائی سے اسکین کریں", "silent_payments_scanning": "خاموش ادائیگی اسکیننگ", "slidable": "سلائیڈ ایبل", "sort_by": "ترتیب دیں", @@ -648,6 +651,7 @@ "syncing_wallet_alert_title": "آپ کا بٹوہ مطابقت پذیر ہو رہا ہے۔", "template": "سانچے", "template_name": "ٹیمپلیٹ کا نام", + "testnet_coins_no_value": "ٹیسٹ نیٹ سکے کی کوئی قیمت نہیں ہے", "third_intro_content": "Yats بھی Cake والیٹ سے باہر رہتے ہیں۔ زمین پر کسی بھی بٹوے کے پتے کو Yat سے تبدیل کیا جا سکتا ہے!", "third_intro_title": "Yat دوسروں کے ساتھ اچھی طرح کھیلتا ہے۔", "thorchain_taproot_address_not_supported": "تھورچین فراہم کنندہ ٹیپروٹ پتے کی حمایت نہیں کرتا ہے۔ براہ کرم پتہ تبدیل کریں یا ایک مختلف فراہم کنندہ کو منتخب کریں۔", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 3435f9ca13..463076ea09 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -611,6 +611,9 @@ "sign_up": "Forúkọ sílẹ̀", "signTransaction": "Wole Idunadura", "signup_for_card_accept_terms": "Ẹ f'orúkọ sílẹ̀ láti gba káàdì àti àjọrò.", + "silent_payments_scan_from_date": "Scan lati ọjọ", + "silent_payments_scan_from_date_or_blockheight": "Jọwọ tẹ giga idibo ti o fẹ bẹrẹ ọlọjẹ fun awọn sisanwo ipalọlọ, tabi, lo ọjọ dipo. O le yan ti apamọwọ naa tẹsiwaju nṣapẹẹrẹ gbogbo bulọọki, tabi ṣayẹwo nikan giga ti o sọ tẹlẹ.", + "silent_payments_scan_from_height": "Scan lati Iga Iga", "silent_payments_scanning": "Awọn sisanwo ipalọlọ", "slidable": "Slidable", "sort_by": "Sa pelu", @@ -647,6 +650,7 @@ "syncing_wallet_alert_title": "Apamọwọ rẹ n muṣiṣẹpọ", "template": "Àwòṣe", "template_name": "Orukọ Awoṣe", + "testnet_coins_no_value": "Awọn aṣọ irekọja ko ni iye", "third_intro_content": "A sì lè lo Yats níta Cake Wallet. A lè rọ́pò Àdírẹ́sì kankan àpamọ́wọ́ fún Yat!", "third_intro_title": "Àlàáfíà ni Yat àti àwọn ìmíìn jọ wà", "thorchain_taproot_address_not_supported": "Olupese Trockchain ko ṣe atilẹyin awọn adirẹsi Taproot. Jọwọ yi adirẹsi pada tabi yan olupese ti o yatọ.", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 22e74ce5b5..14ece96ddd 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -610,6 +610,9 @@ "sign_up": "注册", "signTransaction": "签署交易", "signup_for_card_accept_terms": "注册卡并接受条款。", + "silent_payments_scan_from_date": "从日期开始扫描", + "silent_payments_scan_from_date_or_blockheight": "请输入您要开始扫描输入静音付款的块高度,或者使用日期。您可以选择钱包是否继续扫描每个块,或仅检查指定的高度。", + "silent_payments_scan_from_height": "从块高度扫描", "silent_payments_scanning": "无声付款扫描", "slidable": "可滑动", "sort_by": "排序方式", @@ -646,6 +649,7 @@ "syncing_wallet_alert_title": "您的钱包正在同步", "template": "模板", "template_name": "模板名称", + "testnet_coins_no_value": "TestNet硬币没有价值", "third_intro_content": "Yats 也住在 Cake Wallet 之外。 地球上任何一個錢包地址都可以用一個Yat來代替!", "third_intro_title": "Yat 和別人玩得很好", "thorchain_taproot_address_not_supported": "Thorchain提供商不支持Taproot地址。请更改地址或选择其他提供商。", diff --git a/tool/configure.dart b/tool/configure.dart index 96891247c0..5b0ad99aa8 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -128,7 +128,7 @@ abstract class Bitcoin { List getAddresses(Object wallet); String getAddress(Object wallet); - List getSilentAddresses(Object wallet); + List getSilentAddresses(Object wallet); Future estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority); List getSubAddresses(Object wallet); @@ -149,14 +149,15 @@ abstract class Bitcoin { Future setAddressType(Object wallet, dynamic option); BitcoinReceivePageOption getSelectedAddressType(Object wallet); + List getBitcoinReceivePageOptions(); BitcoinAddressType getBitcoinAddressType(ReceivePageOption option); bool hasSelectedSilentPayments(Object wallet); - List getBitcoinReceivePageOptions(); bool isBitcoinReceivePageOption(ReceivePageOption option); BitcoinAddressType getOptionToType(ReceivePageOption option); bool hasTaprootInput(PendingTransaction pendingTransaction); bool getScanningActive(Object wallet); void setScanningActive(Object wallet, bool active); + bool isTestnet(Object wallet); } """; @@ -1052,7 +1053,8 @@ Future generatePubspec( final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); - final dependenciesIndex = inputLines.indexWhere((line) => line.toLowerCase().contains('dependencies:')); + final dependenciesIndex = + inputLines.indexWhere((line) => line.toLowerCase().contains('dependencies:')); var output = cwCore; if (hasMonero) { From 2b23ab1993e5ae5897f1c1eda47ee110d86c557c Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 5 Apr 2024 19:20:18 -0300 Subject: [PATCH 020/242] fix: invalid Object in sendData --- cw_bitcoin/lib/electrum_wallet.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 4cd2d9f226..617770661b 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -220,7 +220,7 @@ abstract class ElectrumWalletBase chainTip: currentChainTip, electrumClient: ElectrumClient(), transactionHistoryIds: transactionHistory.transactions.keys.toList(), - node: node!, + node: ScanNode(node!.uri, node!.useSSL), labels: walletAddresses.labels, )); @@ -1412,11 +1412,18 @@ abstract class ElectrumWalletBase } } +class ScanNode { + final Uri uri; + final bool? useSSL; + + ScanNode(this.uri, this.useSSL); +} + class ScanData { final SendPort sendPort; final SilentPaymentOwner silentAddress; final int height; - final Node node; + final ScanNode node; final BasedUtxoNetwork network; final int chainTip; final ElectrumClient electrumClient; From 487be52c891969d69ae2dffded4fd9fc708b50e2 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 8 Apr 2024 18:46:33 -0300 Subject: [PATCH 021/242] feat: improve addresses page & address book displays --- cw_bitcoin/lib/electrum_wallet_addresses.dart | 73 ++++++++++++++++++- lib/bitcoin/cw_bitcoin.dart | 13 +++- .../present_receive_option_picker.dart | 17 ++++- lib/src/screens/receive/receive_page.dart | 43 +++++++---- .../contact_list/contact_list_view_model.dart | 14 +++- .../wallet_address_list_header.dart | 5 +- .../wallet_address_list_item.dart | 9 ++- .../wallet_address_list_view_model.dart | 59 ++++++++++++--- res/values/strings_ar.arb | 4 +- res/values/strings_bg.arb | 4 +- res/values/strings_cs.arb | 4 +- res/values/strings_de.arb | 4 +- res/values/strings_en.arb | 4 +- res/values/strings_es.arb | 4 +- res/values/strings_fr.arb | 4 +- res/values/strings_ha.arb | 4 +- res/values/strings_hi.arb | 4 +- res/values/strings_hr.arb | 4 +- res/values/strings_id.arb | 4 +- res/values/strings_it.arb | 4 +- res/values/strings_ja.arb | 4 +- res/values/strings_ko.arb | 4 +- res/values/strings_my.arb | 4 +- res/values/strings_nl.arb | 4 +- res/values/strings_pl.arb | 4 +- res/values/strings_pt.arb | 4 +- res/values/strings_ru.arb | 4 +- res/values/strings_th.arb | 4 +- res/values/strings_tl.arb | 4 +- res/values/strings_tr.arb | 4 +- res/values/strings_uk.arb | 4 +- res/values/strings_ur.arb | 4 +- res/values/strings_yo.arb | 4 +- res/values/strings_zh.arb | 4 +- tool/configure.dart | 3 +- 35 files changed, 272 insertions(+), 68 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index c019da9a4c..1dd11bad3e 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -266,7 +266,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action BaseBitcoinAddressRecord generateNewAddress({String label = ''}) { if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) { - currentSilentAddressIndex += 1; + final currentSilentAddressIndex = silentAddresses + .where((addressRecord) => addressRecord.type != SegwitAddresType.p2tr) + .length + + 1; + + this.currentSilentAddressIndex = currentSilentAddressIndex; final address = BitcoinSilentPaymentAddressRecord( silentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(), @@ -309,12 +314,74 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { Future updateAddressesInBox() async { try { addressesMap.clear(); - addressesMap[address] = ''; + addressesMap[address] = 'Active'; allAddressesMap.clear(); _addresses.forEach((addressRecord) { allAddressesMap[addressRecord.address] = addressRecord.name; }); + + final lastP2wpkh = _addresses + .where((addressRecord) => + _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh)) + .toList() + .last; + if (lastP2wpkh.address != address) { + addressesMap[lastP2wpkh.address] = 'P2WPKH'; + } else { + addressesMap[address] = 'Active - P2WPKH'; + } + + final lastP2pkh = _addresses.firstWhere( + (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh)); + if (lastP2pkh.address != address) { + addressesMap[lastP2pkh.address] = 'P2PKH'; + } else { + addressesMap[address] = 'Active - P2PKH'; + } + + final lastP2sh = _addresses.firstWhere((addressRecord) => + _isUnusedReceiveAddressByType(addressRecord, P2shAddressType.p2wpkhInP2sh)); + if (lastP2sh.address != address) { + addressesMap[lastP2sh.address] = 'P2SH'; + } else { + addressesMap[address] = 'Active - P2SH'; + } + + final lastP2tr = _addresses.firstWhere( + (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr)); + if (lastP2tr.address != address) { + addressesMap[lastP2tr.address] = 'P2TR'; + } else { + addressesMap[address] = 'Active - P2TR'; + } + + final lastP2wsh = _addresses.firstWhere( + (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh)); + if (lastP2wsh.address != address) { + addressesMap[lastP2wsh.address] = 'P2WSH'; + } else { + addressesMap[address] = 'Active - P2WSH'; + } + + silentAddresses.forEach((addressRecord) { + if (addressRecord.type != SilentPaymentsAddresType.p2sp) { + return; + } + + if (addressRecord.address != address) { + print([ + addressRecord.address, + addressRecord.name.isEmpty ? "Silent Payments" : addressRecord.name + ]); + addressesMap[addressRecord.address] = addressRecord.name.isEmpty + ? "Silent Payments" + : "Silent Payments - " + addressRecord.name; + } else { + addressesMap[address] = 'Active - Silent Payments'; + } + }); + await saveAddressesInBox(); } catch (e) { print(e.toString()); @@ -472,4 +539,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd; bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; + bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => + !addr.isHidden && !addr.isUsed && addr.type == type; } diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index f019a91732..bcbb0bc07b 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -297,9 +297,18 @@ class CWBitcoin extends Bitcoin { ); } - List getSilentAddresses(Object wallet) { + List getSilentPaymentAddresses(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; - return bitcoinWallet.walletAddresses.silentAddresses; + return bitcoinWallet.walletAddresses.silentAddresses + .where((addr) => addr.type != SegwitAddresType.p2tr) + .toList(); + } + + List getSilentPaymentReceivedAddresses(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.walletAddresses.silentAddresses + .where((addr) => addr.type == SegwitAddresType.p2tr) + .toList(); } bool isBitcoinReceivePageOption(ReceivePageOption option) { diff --git a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart index 0ae750cf99..bebb58107e 100644 --- a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart +++ b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart @@ -45,7 +45,13 @@ class PresentReceiveOptionPicker extends StatelessWidget { fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color), ), Observer( - builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(), + builder: (_) => Text( + receiveOptionViewModel.selectedReceiveOption + .toString() + .replaceAll(RegExp(r'silent payments', caseSensitive: false), + S.current.silent_payments) + .replaceAll( + RegExp(r'default', caseSensitive: false), S.current.string_default), style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w500, color: color))) ], ), @@ -101,7 +107,14 @@ class PresentReceiveOptionPicker extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(option.toString(), + Text( + option + .toString() + .replaceAll( + RegExp(r'silent payments', caseSensitive: false), + S.current.silent_payments) + .replaceAll(RegExp(r'default', caseSensitive: false), + S.current.string_default), textAlign: TextAlign.left, style: textSmall( color: Theme.of(context) diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index e856d32008..6090bfdd8f 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -164,20 +164,27 @@ class ReceivePage extends BasePage { } if (item is WalletAddressListHeader) { + final hasTitle = item.title != null; + cell = HeaderTile( - title: S.of(context).addresses, - walletAddressListViewModel: addressListViewModel, - showTrailingButton: - !addressListViewModel.isAutoGenerateSubaddressEnabled, - showSearchButton: true, - trailingButtonTap: () => - Navigator.of(context).pushNamed(Routes.newSubaddress), - trailingIcon: Icon( - Icons.add, - size: 20, - color: - Theme.of(context).extension()!.iconsColor, - )); + title: hasTitle ? item.title! : S.of(context).addresses, + walletAddressListViewModel: addressListViewModel, + showTrailingButton: + !addressListViewModel.isAutoGenerateSubaddressEnabled && + !hasTitle, + showSearchButton: true, + trailingButtonTap: () => + Navigator.of(context).pushNamed(Routes.newSubaddress), + trailingIcon: hasTitle + ? null + : Icon( + Icons.add, + size: 20, + color: Theme.of(context) + .extension()! + .iconsColor, + ), + ); } if (item is WalletAddressListItem) { @@ -204,9 +211,13 @@ class ReceivePage extends BasePage { hasBalance: addressListViewModel.isElectrumWallet, backgroundColor: backgroundColor, textColor: textColor, - onTap: (_) => addressListViewModel.setAddress(item), - onEdit: () => Navigator.of(context) - .pushNamed(Routes.newSubaddress, arguments: item)); + onTap: item.isOneTimeReceiveAddress == true + ? null + : (_) => addressListViewModel.setAddress(item), + onEdit: item.isOneTimeReceiveAddress == true + ? null + : () => Navigator.of(context) + .pushNamed(Routes.newSubaddress, arguments: item)); }); } diff --git a/lib/view_model/contact_list/contact_list_view_model.dart b/lib/view_model/contact_list/contact_list_view_model.dart index 6c3169be1f..f811165066 100644 --- a/lib/view_model/contact_list/contact_list_view_model.dart +++ b/lib/view_model/contact_list/contact_list_view_model.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/contact_base.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -40,14 +41,17 @@ abstract class ContactListViewModelBase with Store { }); } else if (info.addresses?.isNotEmpty == true) { info.addresses!.forEach((address, label) { + if (label.isEmpty) { + return; + } final name = _createName(info.name, label); walletContacts.add(WalletContact( address, name, - walletTypeToCryptoCurrency(info.type), + walletTypeToCryptoCurrency(info.type, + isTestnet: + info.network == null ? false : info.network!.toLowerCase().contains("testnet")), )); - // Only one contact address per wallet - return; }); } else if (info.address != null) { walletContacts.add(WalletContact( @@ -64,7 +68,9 @@ abstract class ContactListViewModelBase with Store { } String _createName(String walletName, String label) { - return label.isNotEmpty ? '$walletName ($label)' : walletName; + return label.isNotEmpty + ? '$walletName (${label.replaceAll(RegExp(r'active', caseSensitive: false), S.current.active).replaceAll(RegExp(r'silent payments', caseSensitive: false), S.current.silent_payments)})' + : walletName; } final bool isAutoGenerateEnabled; diff --git a/lib/view_model/wallet_address_list/wallet_address_list_header.dart b/lib/view_model/wallet_address_list/wallet_address_list_header.dart index 0f19166b7c..a094014952 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_header.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_header.dart @@ -1,3 +1,6 @@ import 'package:cake_wallet/utils/list_item.dart'; -class WalletAddressListHeader extends ListItem {} \ No newline at end of file +class WalletAddressListHeader extends ListItem { + final String? title; + WalletAddressListHeader({this.title}); +} diff --git a/lib/view_model/wallet_address_list/wallet_address_list_item.dart b/lib/view_model/wallet_address_list/wallet_address_list_item.dart index 1152f1404b..ebcf541073 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_item.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_item.dart @@ -8,8 +8,9 @@ class WalletAddressListItem extends ListItem { this.name, this.txCount, this.balance, - this.isChange = false}) - : super(); + this.isChange = false, + this.isOneTimeReceiveAddress = false, + }) : super(); final int? id; final bool isPrimary; @@ -18,7 +19,9 @@ class WalletAddressListItem extends ListItem { final int? txCount; final String? balance; final bool isChange; + final bool? isOneTimeReceiveAddress; @override String toString() => name ?? address; -} \ No newline at end of file +} + diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index c1c275a875..9289701fc9 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -318,20 +318,57 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo } if (isElectrumWallet) { - final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) { - final isPrimary = subaddress.id == 0; + if (bitcoin!.hasSelectedSilentPayments(wallet)) { + final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) { + final isPrimary = address.index == 0; - return WalletAddressListItem( - id: subaddress.id, + return WalletAddressListItem( + id: address.index, isPrimary: isPrimary, - name: subaddress.name, - address: subaddress.address, - txCount: subaddress.txCount, + name: address.name, + address: address.address, + txCount: address.txCount, balance: AmountConverter.amountIntToString( - walletTypeToCryptoCurrency(type), subaddress.balance), - isChange: subaddress.isChange); - }); - addressList.addAll(addressItems); + walletTypeToCryptoCurrency(type), address.balance), + isChange: address.isHidden, + ); + }); + addressList.addAll(addressItems); + addressList.add(WalletAddressListHeader(title: S.current.received)); + + final receivedAddressItems = + bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) { + final isPrimary = address.index == 0; + + return WalletAddressListItem( + id: address.index, + isPrimary: isPrimary, + name: address.name, + address: address.address, + txCount: address.txCount, + balance: AmountConverter.amountIntToString( + walletTypeToCryptoCurrency(type), address.balance), + isChange: address.isHidden, + isOneTimeReceiveAddress: true, + ); + }); + addressList.addAll(receivedAddressItems); + } else { + final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) { + final isPrimary = subaddress.id == 0; + + return WalletAddressListItem( + id: subaddress.id, + isPrimary: isPrimary, + name: subaddress.name, + address: subaddress.address, + txCount: subaddress.txCount, + balance: AmountConverter.amountIntToString( + walletTypeToCryptoCurrency(type), subaddress.balance), + isChange: subaddress.isChange); + }); + addressList.addAll(addressItems); + } } if (wallet.type == WalletType.ethereum) { diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index c40cb42296..2e8b9ca682 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -618,6 +618,7 @@ "sign_up": "اشتراك", "signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ", "signup_for_card_accept_terms": "قم بالتسجيل للحصول على البطاقة وقبول الشروط.", + "silent_payments": "مدفوعات صامتة", "silent_payments_scan_from_date": "فحص من التاريخ", "silent_payments_scan_from_date_or_blockheight": "يرجى إدخال ارتفاع الكتلة الذي تريد بدء المسح الضوئي للمدفوعات الصامتة الواردة ، أو استخدام التاريخ بدلاً من ذلك. يمكنك اختيار ما إذا كانت المحفظة تواصل مسح كل كتلة ، أو تتحقق فقط من الارتفاع المحدد.", "silent_payments_scan_from_height": "فحص من ارتفاع الكتلة", @@ -627,6 +628,7 @@ "spend_key_private": "مفتاح الإنفاق (خاص)", "spend_key_public": "مفتاح الإنفاق (عام)", "status": "الحالة:", + "string_default": "تقصير", "subaddress_title": "قائمة العناوين الفرعية", "subaddresses": "العناوين الفرعية", "submit_request": "تقديم طلب", @@ -816,4 +818,4 @@ "you_will_get": "حول الى", "you_will_send": "تحويل من", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 7e586ecdcf..cde8ca2600 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -618,6 +618,7 @@ "sign_up": "Регистрация", "signTransaction": "Подпишете транзакция", "signup_for_card_accept_terms": "Регистрайте се за картата и приемете условията.", + "silent_payments": "Мълчаливи плащания", "silent_payments_scan_from_date": "Сканиране от дата", "silent_payments_scan_from_date_or_blockheight": "Моля, въведете височината на блока, която искате да започнете да сканирате за входящи безшумни плащания, или вместо това използвайте датата. Можете да изберете дали портфейлът продължава да сканира всеки блок или проверява само определената височина.", "silent_payments_scan_from_height": "Сканиране от височината на блока", @@ -627,6 +628,7 @@ "spend_key_private": "Spend key (таен)", "spend_key_public": "Spend key (публичен)", "status": "Статус: ", + "string_default": "По подразбиране", "subaddress_title": "Лист от подадреси", "subaddresses": "Подадреси", "submit_request": "изпращане на заявка", @@ -816,4 +818,4 @@ "you_will_get": "Обръщане в", "you_will_send": "Обръщане от", "yy": "гг" -} \ No newline at end of file +} diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 85a3b50891..29b193e8a1 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -618,6 +618,7 @@ "sign_up": "Registrovat se", "signTransaction": "Podepsat transakci", "signup_for_card_accept_terms": "Zaregistrujte se pro kartu a souhlaste s podmínkami.", + "silent_payments": "Tiché platby", "silent_payments_scan_from_date": "Skenovat od data", "silent_payments_scan_from_date_or_blockheight": "Zadejte výšku bloku, kterou chcete začít skenovat, zda jsou přicházející tiché platby, nebo místo toho použijte datum. Můžete si vybrat, zda peněženka pokračuje v skenování každého bloku nebo zkontroluje pouze zadanou výšku.", "silent_payments_scan_from_height": "Skenování z výšky bloku", @@ -627,6 +628,7 @@ "spend_key_private": "Klíč pro platby (soukromý)", "spend_key_public": "Klíč pro platby (veřejný)", "status": "Status: ", + "string_default": "Výchozí", "subaddress_title": "Seznam subadres", "subaddresses": "Subadresy", "submit_request": "odeslat požadavek", @@ -816,4 +818,4 @@ "you_will_get": "Směnit na", "you_will_send": "Směnit z", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 9dd7391789..0ab2be8e28 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -619,6 +619,7 @@ "sign_up": "Anmelden", "signTransaction": "Transaktion unterzeichnen", "signup_for_card_accept_terms": "Melden Sie sich für die Karte an und akzeptieren Sie die Bedingungen.", + "silent_payments": "Stille Zahlungen", "silent_payments_scan_from_date": "Scan ab Datum", "silent_payments_scan_from_date_or_blockheight": "Bitte geben Sie die Blockhöhe ein, die Sie für eingehende stille Zahlungen scannen möchten, oder verwenden Sie stattdessen das Datum. Sie können wählen, ob die Brieftasche jeden Block scannt oder nur die angegebene Höhe überprüft.", "silent_payments_scan_from_height": "Scan aus der Blockhöhe scannen", @@ -628,6 +629,7 @@ "spend_key_private": "Spend Key (geheim)", "spend_key_public": "Spend Key (öffentlich)", "status": "Status: ", + "string_default": "Standard", "subaddress_title": "Unteradressenliste", "subaddresses": "Unteradressen", "submit_request": "Eine Anfrage stellen", @@ -819,4 +821,4 @@ "you_will_get": "Konvertieren zu", "you_will_send": "Konvertieren von", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 87da283b86..86cd4452da 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -618,6 +618,7 @@ "sign_up": "Sign Up", "signTransaction": "Sign Transaction", "signup_for_card_accept_terms": "Sign up for the card and accept the terms.", + "silent_payments": "Silent Payments", "silent_payments_scan_from_date": "Scan from date", "silent_payments_scan_from_date_or_blockheight": "Please enter the block height you want to start scanning for incoming silent payments, or, use the date instead. You can choose if the wallet continues scanning every block, or checks only the specified height.", "silent_payments_scan_from_height": "Scan from block height", @@ -627,6 +628,7 @@ "spend_key_private": "Spend key (private)", "spend_key_public": "Spend key (public)", "status": "Status: ", + "string_default": "Default", "subaddress_title": "Subaddress list", "subaddresses": "Subaddresses", "submit_request": "submit a request", @@ -816,4 +818,4 @@ "you_will_get": "Convert to", "you_will_send": "Convert from", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index c773c8528e..61270e3760 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -619,6 +619,7 @@ "sign_up": "Registrarse", "signTransaction": "Firmar transacción", "signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.", + "silent_payments": "Pagos silenciosos", "silent_payments_scan_from_date": "Escanear desde la fecha", "silent_payments_scan_from_date_or_blockheight": "Ingrese la altura del bloque que desea comenzar a escanear para pagos silenciosos entrantes, o use la fecha en su lugar. Puede elegir si la billetera continúa escaneando cada bloque, o verifica solo la altura especificada.", "silent_payments_scan_from_height": "Escanear desde la altura del bloque", @@ -628,6 +629,7 @@ "spend_key_private": "Spend clave (privado)", "spend_key_public": "Spend clave (público)", "status": "Estado: ", + "string_default": "Por defecto", "subaddress_title": "Lista de subdirecciones", "subaddresses": "Subdirecciones", "submit_request": "presentar una solicitud", @@ -817,4 +819,4 @@ "you_will_get": "Convertir a", "you_will_send": "Convertir de", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 3423a08e7e..7295e26c2b 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -618,6 +618,7 @@ "sign_up": "S'inscrire", "signTransaction": "Signer une transaction", "signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.", + "silent_payments": "Paiements silencieux", "silent_payments_scan_from_date": "Analyser à partir de la date", "silent_payments_scan_from_date_or_blockheight": "Veuillez saisir la hauteur du bloc que vous souhaitez commencer à scanner pour les paiements silencieux entrants, ou utilisez la date à la place. Vous pouvez choisir si le portefeuille continue de numériser chaque bloc ou ne vérifie que la hauteur spécifiée.", "silent_payments_scan_from_height": "Scan à partir de la hauteur du bloc", @@ -627,6 +628,7 @@ "spend_key_private": "Clef de dépense (spend key) (privée)", "spend_key_public": "Clef de dépense (spend key) (publique)", "status": "Statut : ", + "string_default": "Défaut", "subaddress_title": "Liste des sous-adresses", "subaddresses": "Sous-adresses", "submit_request": "soumettre une requête", @@ -816,4 +818,4 @@ "you_will_get": "Convertir vers", "you_will_send": "Convertir depuis", "yy": "AA" -} \ No newline at end of file +} diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 9bcdc35667..e2f9537903 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -620,6 +620,7 @@ "sign_up": "Shiga", "signTransaction": "Sa hannu Ma'amala", "signup_for_card_accept_terms": "Yi rajista don katin kuma karɓi sharuɗɗan.", + "silent_payments": "Biya silent", "silent_payments_scan_from_date": "Scan daga kwanan wata", "silent_payments_scan_from_date_or_blockheight": "Da fatan za a shigar da toshe wurin da kake son fara bincika don biyan silins mai shigowa, ko, yi amfani da kwanan wata. Zaka iya zabar idan walat ɗin ya ci gaba da bincika kowane toshe, ko duba tsinkaye da aka ƙayyade.", "silent_payments_scan_from_height": "Scan daga tsayin daka", @@ -629,6 +630,7 @@ "spend_key_private": "makullin biya (maɓallin kalmar sirri)", "spend_key_public": "makullin biya (maɓallin jama'a)", "status": "Matsayi:", + "string_default": "Ƙin cika alƙawari", "subaddress_title": "Jagorar subaddress", "subaddresses": "Subaddresses", "submit_request": "gabatar da bukata", @@ -818,4 +820,4 @@ "you_will_get": "Maida zuwa", "you_will_send": "Maida daga", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 8de7c421b5..37b2e22f29 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -620,6 +620,7 @@ "sign_up": "साइन अप करें", "signTransaction": "लेन-देन पर हस्ताक्षर करें", "signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।", + "silent_payments": "मूक भुगतान", "silent_payments_scan_from_date": "तिथि से स्कैन करना", "silent_payments_scan_from_date_or_blockheight": "कृपया उस ब्लॉक ऊंचाई दर्ज करें जिसे आप आने वाले मूक भुगतान के लिए स्कैन करना शुरू करना चाहते हैं, या, इसके बजाय तारीख का उपयोग करें। आप चुन सकते हैं कि क्या वॉलेट हर ब्लॉक को स्कैन करना जारी रखता है, या केवल निर्दिष्ट ऊंचाई की जांच करता है।", "silent_payments_scan_from_height": "ब्लॉक ऊंचाई से स्कैन करें", @@ -629,6 +630,7 @@ "spend_key_private": "खर्च करना (निजी)", "spend_key_public": "खर्च करना (जनता)", "status": "स्थिति: ", + "string_default": "गलती करना", "subaddress_title": "उपखंड सूची", "subaddresses": "उप पते", "submit_request": "एक अनुरोध सबमिट करें", @@ -818,4 +820,4 @@ "you_will_get": "में बदलें", "you_will_send": "से रूपांतरित करें", "yy": "वाईवाई" -} \ No newline at end of file +} diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index dd173e0fb2..7a570e4380 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -618,6 +618,7 @@ "sign_up": "Prijavite se", "signTransaction": "Potpišite transakciju", "signup_for_card_accept_terms": "Prijavite se za karticu i prihvatite uvjete.", + "silent_payments": "Tiha plaćanja", "silent_payments_scan_from_date": "Skeniranje iz datuma", "silent_payments_scan_from_date_or_blockheight": "Unesite visinu bloka koju želite započeti skeniranje za dolazna tiha plaćanja ili umjesto toga upotrijebite datum. Možete odabrati da li novčanik nastavlja skenirati svaki blok ili provjerava samo navedenu visinu.", "silent_payments_scan_from_height": "Skeniranje s visine bloka", @@ -627,6 +628,7 @@ "spend_key_private": "Spend key (privatni)", "spend_key_public": "Spend key (javni)", "status": "Status: ", + "string_default": "Zadano", "subaddress_title": "Lista podadresa", "subaddresses": "Podadrese", "submit_request": "podnesi zahtjev", @@ -816,4 +818,4 @@ "you_will_get": "Razmijeni u", "you_will_send": "Razmijeni iz", "yy": "GG" -} \ No newline at end of file +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 8ccb00b5d5..f12416b5e4 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -621,6 +621,7 @@ "sign_up": "Daftar", "signTransaction": "Tandatangani Transaksi", "signup_for_card_accept_terms": "Daftar untuk kartu dan terima syarat dan ketentuan.", + "silent_payments": "Pembayaran diam", "silent_payments_scan_from_date": "Pindai dari tanggal", "silent_payments_scan_from_date_or_blockheight": "Harap masukkan ketinggian blok yang ingin Anda mulai pemindaian untuk pembayaran diam yang masuk, atau, gunakan tanggal sebagai gantinya. Anda dapat memilih jika dompet terus memindai setiap blok, atau memeriksa hanya ketinggian yang ditentukan.", "silent_payments_scan_from_height": "Pindai dari Tinggi Blok", @@ -630,6 +631,7 @@ "spend_key_private": "Kunci pengeluaran (privat)", "spend_key_public": "Kunci pengeluaran (publik)", "status": "Status: ", + "string_default": "Bawaan", "subaddress_title": "Daftar sub-alamat", "subaddresses": "Sub-alamat", "submit_request": "kirim permintaan", @@ -819,4 +821,4 @@ "you_will_get": "Konversi ke", "you_will_send": "Konversi dari", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 5045489573..750a72d9aa 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -620,6 +620,7 @@ "sign_up": "Registrati", "signTransaction": "Firma la transazione", "signup_for_card_accept_terms": "Registrati per la carta e accetta i termini.", + "silent_payments": "Pagamenti silenziosi", "silent_payments_scan_from_date": "Scansionare dalla data", "silent_payments_scan_from_date_or_blockheight": "Inserisci l'altezza del blocco che si desidera iniziare la scansione per i pagamenti silenziosi in arrivo o, utilizza invece la data. Puoi scegliere se il portafoglio continua a scansionare ogni blocco o controlla solo l'altezza specificata.", "silent_payments_scan_from_height": "Scansione dall'altezza del blocco", @@ -629,6 +630,7 @@ "spend_key_private": "Chiave di spesa (privata)", "spend_key_public": "Chiave di spesa (pubblica)", "status": "Stato: ", + "string_default": "Predefinito", "subaddress_title": "Lista sottoindirizzi", "subaddresses": "Sottoindirizzi", "submit_request": "invia una richiesta", @@ -819,4 +821,4 @@ "you_will_get": "Converti a", "you_will_send": "Conveti da", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index cb05c1b894..2c65216938 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -619,6 +619,7 @@ "sign_up": "サインアップ", "signTransaction": "トランザクションに署名する", "signup_for_card_accept_terms": "カードにサインアップして、利用規約に同意してください。", + "silent_payments": "サイレント支払い", "silent_payments_scan_from_date": "日付からスキャンします", "silent_payments_scan_from_date_or_blockheight": "着信のサイレント決済のためにスキャンを開始するブロックの高さを入力するか、代わりに日付を使用してください。ウォレットがすべてのブロックをスキャンし続けるか、指定された高さのみをチェックするかどうかを選択できます。", "silent_payments_scan_from_height": "ブロックの高さからスキャンします", @@ -628,6 +629,7 @@ "spend_key_private": "キーを使う (プライベート)", "spend_key_public": "キーを使う (パブリック)", "status": "状態: ", + "string_default": "デフォルト", "subaddress_title": "サブアドレス一覧", "subaddresses": "サブアドレス", "submit_request": "リクエストを送信する", @@ -817,4 +819,4 @@ "you_will_get": "に変換", "you_will_send": "から変換", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 788b8fba0d..dc68515c8d 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -619,6 +619,7 @@ "sign_up": "가입", "signTransaction": "거래 서명", "signup_for_card_accept_terms": "카드에 가입하고 약관에 동의합니다.", + "silent_payments": "조용한 지불", "silent_payments_scan_from_date": "날짜부터 스캔하십시오", "silent_payments_scan_from_date_or_blockheight": "들어오는 사일런트 결제를 위해 스캔을 시작하려는 블록 높이를 입력하거나 대신 날짜를 사용하십시오. 지갑이 모든 블록을 계속 스캔하는지 여부를 선택하거나 지정된 높이 만 확인할 수 있습니다.", "silent_payments_scan_from_height": "블록 높이에서 스캔하십시오", @@ -628,6 +629,7 @@ "spend_key_private": "지출 키 (은밀한)", "spend_key_public": "지출 키 (공공의)", "status": "지위: ", + "string_default": "기본", "subaddress_title": "하위 주소 목록", "subaddresses": "하위 주소", "submit_request": "요청을 제출", @@ -818,4 +820,4 @@ "you_will_send": "다음에서 변환", "YY": "YY", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 07897bb252..d4164b4547 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -618,6 +618,7 @@ "sign_up": "ဆိုင်းအပ်", "signTransaction": "ငွေလွှဲဝင်ပါ။", "signup_for_card_accept_terms": "ကတ်အတွက် စာရင်းသွင်းပြီး စည်းကမ်းချက်များကို လက်ခံပါ။", + "silent_payments": "အသံတိတ်ငွေပေးချေမှု", "silent_payments_scan_from_date": "ရက်စွဲမှစကင်ဖတ်ပါ", "silent_payments_scan_from_date_or_blockheight": "ကျေးဇူးပြု. သင်ဝင်လာသောအသံတိတ်ငွေပေးချေမှုအတွက်သင်စကင်ဖတ်စစ်ဆေးလိုသည့်အမြင့်ကိုဖြည့်ပါ။ သို့မဟုတ်နေ့စွဲကိုသုံးပါ။ Wallet သည်လုပ်ကွက်တိုင်းကိုဆက်လက်စကင်ဖတ်စစ်ဆေးပါကသို့မဟုတ်သတ်မှတ်ထားသောအမြင့်ကိုသာစစ်ဆေးပါကသင်ရွေးချယ်နိုင်သည်။", "silent_payments_scan_from_height": "ပိတ်ပင်တားဆီးမှုအမြင့်ကနေ scan", @@ -627,6 +628,7 @@ "spend_key_private": "သော့သုံးရန် (သီးသန့်)", "spend_key_public": "သုံးစွဲရန်သော့ (အများပြည်သူ)", "status": "အခြေအနေ:", + "string_default": "ပျက်ကွက်ခြင်း", "subaddress_title": "လိပ်စာစာရင်း", "subaddresses": "လိပ်စာများ", "submit_request": "တောင်းဆိုချက်တစ်ခုတင်ပြပါ။", @@ -816,4 +818,4 @@ "you_will_get": "သို့ပြောင်းပါ။", "you_will_send": "မှပြောင်းပါ။", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 2ebc0cb930..cdd50981e7 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -618,6 +618,7 @@ "sign_up": "Aanmelden", "signTransaction": "Transactie ondertekenen", "signup_for_card_accept_terms": "Meld je aan voor de kaart en accepteer de voorwaarden.", + "silent_payments": "Stille betalingen", "silent_payments_scan_from_date": "Scan vanaf datum", "silent_payments_scan_from_date_or_blockheight": "Voer de blokhoogte in die u wilt beginnen met scannen op inkomende stille betalingen, of gebruik in plaats daarvan de datum. U kunt kiezen of de portemonnee elk blok blijft scannen of alleen de opgegeven hoogte controleert.", "silent_payments_scan_from_height": "Scan van blokhoogte", @@ -627,6 +628,7 @@ "spend_key_private": "Sleutel uitgeven (privaat)", "spend_key_public": "Sleutel uitgeven (openbaar)", "status": "Staat: ", + "string_default": "Standaard", "subaddress_title": "Subadreslijst", "subaddresses": "Subadressen", "submit_request": "een verzoek indienen", @@ -817,4 +819,4 @@ "you_will_get": "Converteren naar", "you_will_send": "Converteren van", "yy": "JJ" -} \ No newline at end of file +} diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index bc41c9b11e..cde8cad380 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -618,6 +618,7 @@ "sign_up": "Zarejestruj się", "signTransaction": "Podpisz transakcję", "signup_for_card_accept_terms": "Zarejestruj się, aby otrzymać kartę i zaakceptuj warunki.", + "silent_payments": "Ciche płatności", "silent_payments_scan_from_date": "Skanuj z daty", "silent_payments_scan_from_date_or_blockheight": "Wprowadź wysokość bloku, którą chcesz rozpocząć skanowanie w poszukiwaniu cichej płatności lub zamiast tego skorzystaj z daty. Możesz wybrać, czy portfel kontynuuje skanowanie każdego bloku, lub sprawdza tylko określoną wysokość.", "silent_payments_scan_from_height": "Skanuj z wysokości bloku", @@ -627,6 +628,7 @@ "spend_key_private": "Klucz prywatny", "spend_key_public": "Klucz publiczny", "status": "Status: ", + "string_default": "Domyślny", "subaddress_title": "Lista podadresów", "subaddresses": "Podadresy", "submit_request": "Złóż wniosek", @@ -816,4 +818,4 @@ "you_will_get": "Konwertuj na", "you_will_send": "Konwertuj z", "yy": "RR" -} \ No newline at end of file +} diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index d440177735..168b0312a1 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -620,6 +620,7 @@ "sign_up": "Inscrever-se", "signTransaction": "Assinar transação", "signup_for_card_accept_terms": "Cadastre-se no cartão e aceite os termos.", + "silent_payments": "Pagamentos silenciosos", "silent_payments_scan_from_date": "Escanear a partir da data", "silent_payments_scan_from_date_or_blockheight": "Por favor, insira a altura do bloco que deseja iniciar o escaneamento para obter pagamentos silenciosos ou use a data. Você pode escolher se a carteira continua digitalizando cada bloco ou verifica apenas a altura especificada.", "silent_payments_scan_from_height": "Escanear a partir da altura do bloco", @@ -629,6 +630,7 @@ "spend_key_private": "Chave de gastos (privada)", "spend_key_public": "Chave de gastos (pública)", "status": "Status: ", + "string_default": "Padrão", "subaddress_title": "Sub-endereços", "subaddresses": "Sub-endereços", "submit_request": "enviar um pedido", @@ -819,4 +821,4 @@ "you_will_get": "Converter para", "you_will_send": "Converter de", "yy": "aa" -} \ No newline at end of file +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 99cc385949..9a8f6563a6 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -619,6 +619,7 @@ "sign_up": "Зарегистрироваться", "signTransaction": "Подписать транзакцию", "signup_for_card_accept_terms": "Подпишитесь на карту и примите условия.", + "silent_payments": "Молчаливые платежи", "silent_payments_scan_from_date": "Сканирование с даты", "silent_payments_scan_from_date_or_blockheight": "Пожалуйста, введите высоту блока, которую вы хотите начать сканирование для входящих молчаливых платежей, или вместо этого используйте дату. Вы можете выбрать, продолжает ли кошелек сканировать каждый блок или проверять только указанную высоту.", "silent_payments_scan_from_height": "Сканирование с высоты блока", @@ -628,6 +629,7 @@ "spend_key_private": "Приватный ключ траты", "spend_key_public": "Публичный ключ траты", "status": "Статус: ", + "string_default": "По умолчанию", "subaddress_title": "Список субадресов", "subaddresses": "Субадреса", "submit_request": "отправить запрос", @@ -817,4 +819,4 @@ "you_will_get": "Конвертировать в", "you_will_send": "Конвертировать из", "yy": "ГГ" -} \ No newline at end of file +} diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 14ecaf62a8..8bee7acc6f 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -618,6 +618,7 @@ "sign_up": "สมัครสมาชิก", "signTransaction": "ลงนามในการทำธุรกรรม", "signup_for_card_accept_terms": "ลงทะเบียนสำหรับบัตรและยอมรับเงื่อนไข", + "silent_payments": "การชำระเงินเงียบ", "silent_payments_scan_from_date": "สแกนตั้งแต่วันที่", "silent_payments_scan_from_date_or_blockheight": "โปรดป้อนความสูงของบล็อกที่คุณต้องการเริ่มการสแกนสำหรับการชำระเงินแบบเงียบ ๆ หรือใช้วันที่แทน คุณสามารถเลือกได้ว่ากระเป๋าเงินยังคงสแกนทุกบล็อกหรือตรวจสอบความสูงที่ระบุเท่านั้น", "silent_payments_scan_from_height": "สแกนจากความสูงของบล็อก", @@ -627,6 +628,7 @@ "spend_key_private": "คีย์จ่าย (ส่วนตัว)", "spend_key_public": "คีย์จ่าย (สาธารณะ)", "status": "สถานะ: ", + "string_default": "ค่าเริ่มต้น", "subaddress_title": "รายการที่อยู่ย่อย", "subaddresses": "ที่อยู่ย่อย", "submit_request": "ส่งคำขอ", @@ -816,4 +818,4 @@ "you_will_get": "แปลงเป็น", "you_will_send": "แปลงจาก", "yy": "ปี" -} \ No newline at end of file +} diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 6680a9341f..784a91892e 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -618,6 +618,7 @@ "sign_up": "Mag -sign up", "signTransaction": "Mag-sign Transaksyon", "signup_for_card_accept_terms": "Mag -sign up para sa card at tanggapin ang mga termino.", + "silent_payments": "Tahimik na pagbabayad", "silent_payments_scan_from_date": "I -scan mula sa petsa", "silent_payments_scan_from_date_or_blockheight": "Mangyaring ipasok ang taas ng block na nais mong simulan ang pag -scan para sa papasok na tahimik na pagbabayad, o, gamitin ang petsa sa halip. Maaari kang pumili kung ang pitaka ay patuloy na pag -scan sa bawat bloke, o suriin lamang ang tinukoy na taas.", "silent_payments_scan_from_height": "I -scan mula sa taas ng block", @@ -627,6 +628,7 @@ "spend_key_private": "Gumastos ng susi (pribado)", "spend_key_public": "Gumastos ng susi (publiko)", "status": "Katayuan:", + "string_default": "Default", "subaddress_title": "Listahan ng Subaddress", "subaddresses": "Mga Subaddresses", "submit_request": "magsumite ng isang kahilingan", @@ -816,4 +818,4 @@ "you_will_get": "Mag -convert sa", "you_will_send": "I -convert mula sa", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index b9110639d6..95844f593b 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -618,6 +618,7 @@ "sign_up": "Kaydol", "signTransaction": "İşlem İmzala", "signup_for_card_accept_terms": "Kart için kaydol ve koşulları kabul et.", + "silent_payments": "Sessiz ödemeler", "silent_payments_scan_from_date": "Tarihten tarama", "silent_payments_scan_from_date_or_blockheight": "Lütfen gelen sessiz ödemeler için taramaya başlamak istediğiniz blok yüksekliğini girin veya bunun yerine tarihi kullanın. Cüzdanın her bloğu taramaya devam edip etmediğini veya yalnızca belirtilen yüksekliği kontrol edip etmediğini seçebilirsiniz.", "silent_payments_scan_from_height": "Blok yüksekliğinden tarama", @@ -627,6 +628,7 @@ "spend_key_private": "Harcama anahtarı (özel)", "spend_key_public": "Harcama anahtarı (genel)", "status": "Durum: ", + "string_default": "Varsayılan", "subaddress_title": "Alt adres listesi", "subaddresses": "Alt adresler", "submit_request": "talep gönder", @@ -816,4 +818,4 @@ "you_will_get": "Biçimine dönüştür:", "you_will_send": "Biçiminden dönüştür:", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 4b608fe45f..c9aaef6f27 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -619,6 +619,7 @@ "sign_up": "Зареєструватися", "signTransaction": "Підписати транзакцію", "signup_for_card_accept_terms": "Зареєструйтеся на картку та прийміть умови.", + "silent_payments": "Мовчазні платежі", "silent_payments_scan_from_date": "Сканувати з дати", "silent_payments_scan_from_date_or_blockheight": "Введіть висоту блоку, яку ви хочете почати сканувати для вхідних мовчазних платежів, або скористайтеся датою замість цього. Ви можете вибрати, якщо гаманець продовжує сканувати кожен блок, або перевіряє лише вказану висоту.", "silent_payments_scan_from_height": "Сканування від висоти блоку", @@ -628,6 +629,7 @@ "spend_key_private": "Приватний ключ витрати", "spend_key_public": "Публічний ключ витрати", "status": "Статус: ", + "string_default": "За замовчуванням", "subaddress_title": "Список Субадрес", "subaddresses": "Субадреси", "submit_request": "надіслати запит", @@ -817,4 +819,4 @@ "you_will_get": "Конвертувати в", "you_will_send": "Конвертувати з", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 9c6b9cf4b6..c02919463b 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -620,6 +620,7 @@ "sign_up": "سائن اپ", "signTransaction": "۔ﮟﯾﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", "signup_for_card_accept_terms": "کارڈ کے لیے سائن اپ کریں اور شرائط کو قبول کریں۔", + "silent_payments": "خاموش ادائیگی", "silent_payments_scan_from_date": "تاریخ سے اسکین کریں", "silent_payments_scan_from_date_or_blockheight": "براہ کرم بلاک اونچائی میں داخل ہوں جس سے آپ آنے والی خاموش ادائیگیوں کے لئے اسکیننگ شروع کرنا چاہتے ہیں ، یا اس کے بجائے تاریخ کا استعمال کریں۔ آپ یہ منتخب کرسکتے ہیں کہ اگر پرس ہر بلاک کو اسکیننگ جاری رکھے ہوئے ہے ، یا صرف مخصوص اونچائی کی جانچ پڑتال کرتا ہے۔", "silent_payments_scan_from_height": "بلاک اونچائی سے اسکین کریں", @@ -629,6 +630,7 @@ "spend_key_private": "خرچ کی کلید (نجی)", "spend_key_public": "خرچ کی کلید (عوامی)", "status": "حالت:", + "string_default": "پہلے سے طے شدہ", "subaddress_title": "ذیلی ایڈریس کی فہرست", "subaddresses": "ذیلی پتے", "submit_request": "درخواست بھیج دو", @@ -818,4 +820,4 @@ "you_will_get": "میں تبدیل کریں۔", "you_will_send": "سے تبدیل کریں۔", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index fcc2d71c9f..76a17bc997 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -619,6 +619,7 @@ "sign_up": "Forúkọ sílẹ̀", "signTransaction": "Wole Idunadura", "signup_for_card_accept_terms": "Ẹ f'orúkọ sílẹ̀ láti gba káàdì àti àjọrò.", + "silent_payments": "Awọn sisanwo ipalọlọ", "silent_payments_scan_from_date": "Scan lati ọjọ", "silent_payments_scan_from_date_or_blockheight": "Jọwọ tẹ giga idibo ti o fẹ bẹrẹ ọlọjẹ fun awọn sisanwo ipalọlọ, tabi, lo ọjọ dipo. O le yan ti apamọwọ naa tẹsiwaju nṣapẹẹrẹ gbogbo bulọọki, tabi ṣayẹwo nikan giga ti o sọ tẹlẹ.", "silent_payments_scan_from_height": "Scan lati Iga Iga", @@ -628,6 +629,7 @@ "spend_key_private": "Kọ́kọ́rọ́ sísan (àdáni)", "spend_key_public": "Kọ́kọ́rọ́ sísan (kò àdáni)", "status": "Tó ń ṣẹlẹ̀: ", + "string_default": "Aiyipada", "subaddress_title": "Àkọsílẹ̀ ni nínú àwọn àdírẹ́sì tíwọn rẹ̀lẹ̀", "subaddresses": "Àwọn àdírẹ́sì kékeré", "submit_request": "Ṣé ìbéèrè", @@ -817,4 +819,4 @@ "you_will_get": "Ṣe pàṣípààrọ̀ sí", "you_will_send": "Ṣe pàṣípààrọ̀ láti", "yy": "Ọd" -} \ No newline at end of file +} diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 4b77c997e6..c60807e2cc 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -618,6 +618,7 @@ "sign_up": "注册", "signTransaction": "签署交易", "signup_for_card_accept_terms": "注册卡并接受条款。", + "silent_payments": "无声付款", "silent_payments_scan_from_date": "从日期开始扫描", "silent_payments_scan_from_date_or_blockheight": "请输入您要开始扫描输入静音付款的块高度,或者使用日期。您可以选择钱包是否继续扫描每个块,或仅检查指定的高度。", "silent_payments_scan_from_height": "从块高度扫描", @@ -627,6 +628,7 @@ "spend_key_private": "Spend 密钥 (私钥)", "spend_key_public": "Spend 密钥 (公钥)", "status": "状态: ", + "string_default": "默认", "subaddress_title": "子地址列表", "subaddresses": "子地址", "submit_request": "提交请求", @@ -816,4 +818,4 @@ "you_will_get": "转换到", "you_will_send": "转换自", "yy": "YY" -} \ No newline at end of file +} diff --git a/tool/configure.dart b/tool/configure.dart index 422d0b8162..b082f64701 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -129,7 +129,8 @@ abstract class Bitcoin { List getAddresses(Object wallet); String getAddress(Object wallet); - List getSilentAddresses(Object wallet); + List getSilentPaymentAddresses(Object wallet); + List getSilentPaymentReceivedAddresses(Object wallet); Future estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority); List getSubAddresses(Object wallet); From 36c64368134c3c970ffe6a69065eba4a7504e367 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 8 Apr 2024 20:30:23 -0300 Subject: [PATCH 022/242] feat: silent payments labeled addresses disclaimer --- lib/src/screens/receive/receive_page.dart | 22 ++++++++++++---------- res/values/strings_ar.arb | 3 ++- res/values/strings_bg.arb | 3 ++- res/values/strings_cs.arb | 3 ++- res/values/strings_de.arb | 3 ++- res/values/strings_en.arb | 3 ++- res/values/strings_es.arb | 3 ++- res/values/strings_fr.arb | 3 ++- res/values/strings_ha.arb | 3 ++- res/values/strings_hi.arb | 3 ++- res/values/strings_hr.arb | 3 ++- res/values/strings_id.arb | 3 ++- res/values/strings_it.arb | 3 ++- res/values/strings_ja.arb | 3 ++- res/values/strings_ko.arb | 3 ++- res/values/strings_my.arb | 3 ++- res/values/strings_nl.arb | 3 ++- res/values/strings_pl.arb | 3 ++- res/values/strings_pt.arb | 3 ++- res/values/strings_ru.arb | 3 ++- res/values/strings_th.arb | 3 ++- res/values/strings_tl.arb | 3 ++- res/values/strings_tr.arb | 3 ++- res/values/strings_uk.arb | 3 ++- res/values/strings_ur.arb | 3 ++- res/values/strings_yo.arb | 3 ++- res/values/strings_zh.arb | 3 ++- 27 files changed, 64 insertions(+), 36 deletions(-) diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 6090bfdd8f..4d0c6185f1 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -230,16 +230,18 @@ class ReceivePage extends BasePage { child: cell, ); })), - if (!addressListViewModel.isSilentPayments) - Padding( - padding: EdgeInsets.fromLTRB(24, 24, 24, 32), - child: Text(S.of(context).electrum_address_disclaimer, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - color: - Theme.of(context).extension()!.labelTextColor)), - ), + Padding( + padding: EdgeInsets.fromLTRB(24, 24, 24, 32), + child: Text( + !addressListViewModel.isSilentPayments + ? S.of(context).electrum_address_disclaimer + : S.of(context).silent_payments_disclaimer, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + color: + Theme.of(context).extension()!.labelTextColor)), + ), ], ), )) diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 2e8b9ca682..5646dcf83e 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -619,6 +619,7 @@ "signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ", "signup_for_card_accept_terms": "قم بالتسجيل للحصول على البطاقة وقبول الشروط.", "silent_payments": "مدفوعات صامتة", + "silent_payments_disclaimer": "العناوين الجديدة ليست هويات جديدة. إنها إعادة استخدام هوية موجودة مع ملصق مختلف.", "silent_payments_scan_from_date": "فحص من التاريخ", "silent_payments_scan_from_date_or_blockheight": "يرجى إدخال ارتفاع الكتلة الذي تريد بدء المسح الضوئي للمدفوعات الصامتة الواردة ، أو استخدام التاريخ بدلاً من ذلك. يمكنك اختيار ما إذا كانت المحفظة تواصل مسح كل كتلة ، أو تتحقق فقط من الارتفاع المحدد.", "silent_payments_scan_from_height": "فحص من ارتفاع الكتلة", @@ -818,4 +819,4 @@ "you_will_get": "حول الى", "you_will_send": "تحويل من", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index cde8ca2600..5c06a9cbdc 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -619,6 +619,7 @@ "signTransaction": "Подпишете транзакция", "signup_for_card_accept_terms": "Регистрайте се за картата и приемете условията.", "silent_payments": "Мълчаливи плащания", + "silent_payments_disclaimer": "Новите адреси не са нови идентичности. Това е повторна употреба на съществуваща идентичност с различен етикет.", "silent_payments_scan_from_date": "Сканиране от дата", "silent_payments_scan_from_date_or_blockheight": "Моля, въведете височината на блока, която искате да започнете да сканирате за входящи безшумни плащания, или вместо това използвайте датата. Можете да изберете дали портфейлът продължава да сканира всеки блок или проверява само определената височина.", "silent_payments_scan_from_height": "Сканиране от височината на блока", @@ -818,4 +819,4 @@ "you_will_get": "Обръщане в", "you_will_send": "Обръщане от", "yy": "гг" -} +} \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 29b193e8a1..adb194a1a3 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -619,6 +619,7 @@ "signTransaction": "Podepsat transakci", "signup_for_card_accept_terms": "Zaregistrujte se pro kartu a souhlaste s podmínkami.", "silent_payments": "Tiché platby", + "silent_payments_disclaimer": "Nové adresy nejsou nové identity. Je to opětovné použití existující identity s jiným štítkem.", "silent_payments_scan_from_date": "Skenovat od data", "silent_payments_scan_from_date_or_blockheight": "Zadejte výšku bloku, kterou chcete začít skenovat, zda jsou přicházející tiché platby, nebo místo toho použijte datum. Můžete si vybrat, zda peněženka pokračuje v skenování každého bloku nebo zkontroluje pouze zadanou výšku.", "silent_payments_scan_from_height": "Skenování z výšky bloku", @@ -818,4 +819,4 @@ "you_will_get": "Směnit na", "you_will_send": "Směnit z", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 0ab2be8e28..7a7e60413d 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -620,6 +620,7 @@ "signTransaction": "Transaktion unterzeichnen", "signup_for_card_accept_terms": "Melden Sie sich für die Karte an und akzeptieren Sie die Bedingungen.", "silent_payments": "Stille Zahlungen", + "silent_payments_disclaimer": "Neue Adressen sind keine neuen Identitäten. Es ist eine Wiederverwendung einer bestehenden Identität mit einem anderen Etikett.", "silent_payments_scan_from_date": "Scan ab Datum", "silent_payments_scan_from_date_or_blockheight": "Bitte geben Sie die Blockhöhe ein, die Sie für eingehende stille Zahlungen scannen möchten, oder verwenden Sie stattdessen das Datum. Sie können wählen, ob die Brieftasche jeden Block scannt oder nur die angegebene Höhe überprüft.", "silent_payments_scan_from_height": "Scan aus der Blockhöhe scannen", @@ -821,4 +822,4 @@ "you_will_get": "Konvertieren zu", "you_will_send": "Konvertieren von", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 86cd4452da..3b6afa1c86 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -619,6 +619,7 @@ "signTransaction": "Sign Transaction", "signup_for_card_accept_terms": "Sign up for the card and accept the terms.", "silent_payments": "Silent Payments", + "silent_payments_disclaimer": "New addresses are not new identities. It is a re-use of an existing identity with a different label.", "silent_payments_scan_from_date": "Scan from date", "silent_payments_scan_from_date_or_blockheight": "Please enter the block height you want to start scanning for incoming silent payments, or, use the date instead. You can choose if the wallet continues scanning every block, or checks only the specified height.", "silent_payments_scan_from_height": "Scan from block height", @@ -818,4 +819,4 @@ "you_will_get": "Convert to", "you_will_send": "Convert from", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 61270e3760..1166d6bbe3 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -620,6 +620,7 @@ "signTransaction": "Firmar transacción", "signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.", "silent_payments": "Pagos silenciosos", + "silent_payments_disclaimer": "Las nuevas direcciones no son nuevas identidades. Es una reutilización de una identidad existente con una etiqueta diferente.", "silent_payments_scan_from_date": "Escanear desde la fecha", "silent_payments_scan_from_date_or_blockheight": "Ingrese la altura del bloque que desea comenzar a escanear para pagos silenciosos entrantes, o use la fecha en su lugar. Puede elegir si la billetera continúa escaneando cada bloque, o verifica solo la altura especificada.", "silent_payments_scan_from_height": "Escanear desde la altura del bloque", @@ -819,4 +820,4 @@ "you_will_get": "Convertir a", "you_will_send": "Convertir de", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 7295e26c2b..7ae4c3da70 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -619,6 +619,7 @@ "signTransaction": "Signer une transaction", "signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.", "silent_payments": "Paiements silencieux", + "silent_payments_disclaimer": "Les nouvelles adresses ne sont pas de nouvelles identités. Il s'agit d'une réutilisation d'une identité existante avec une étiquette différente.", "silent_payments_scan_from_date": "Analyser à partir de la date", "silent_payments_scan_from_date_or_blockheight": "Veuillez saisir la hauteur du bloc que vous souhaitez commencer à scanner pour les paiements silencieux entrants, ou utilisez la date à la place. Vous pouvez choisir si le portefeuille continue de numériser chaque bloc ou ne vérifie que la hauteur spécifiée.", "silent_payments_scan_from_height": "Scan à partir de la hauteur du bloc", @@ -818,4 +819,4 @@ "you_will_get": "Convertir vers", "you_will_send": "Convertir depuis", "yy": "AA" -} +} \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index e2f9537903..bb78d76765 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -621,6 +621,7 @@ "signTransaction": "Sa hannu Ma'amala", "signup_for_card_accept_terms": "Yi rajista don katin kuma karɓi sharuɗɗan.", "silent_payments": "Biya silent", + "silent_payments_disclaimer": "Sabbin adiresoshin ba sabon tsari bane. Wannan shine sake amfani da asalin asalin tare da wata alama daban.", "silent_payments_scan_from_date": "Scan daga kwanan wata", "silent_payments_scan_from_date_or_blockheight": "Da fatan za a shigar da toshe wurin da kake son fara bincika don biyan silins mai shigowa, ko, yi amfani da kwanan wata. Zaka iya zabar idan walat ɗin ya ci gaba da bincika kowane toshe, ko duba tsinkaye da aka ƙayyade.", "silent_payments_scan_from_height": "Scan daga tsayin daka", @@ -820,4 +821,4 @@ "you_will_get": "Maida zuwa", "you_will_send": "Maida daga", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 37b2e22f29..8b6599a4d7 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -621,6 +621,7 @@ "signTransaction": "लेन-देन पर हस्ताक्षर करें", "signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।", "silent_payments": "मूक भुगतान", + "silent_payments_disclaimer": "नए पते नई पहचान नहीं हैं। यह एक अलग लेबल के साथ एक मौजूदा पहचान का पुन: उपयोग है।", "silent_payments_scan_from_date": "तिथि से स्कैन करना", "silent_payments_scan_from_date_or_blockheight": "कृपया उस ब्लॉक ऊंचाई दर्ज करें जिसे आप आने वाले मूक भुगतान के लिए स्कैन करना शुरू करना चाहते हैं, या, इसके बजाय तारीख का उपयोग करें। आप चुन सकते हैं कि क्या वॉलेट हर ब्लॉक को स्कैन करना जारी रखता है, या केवल निर्दिष्ट ऊंचाई की जांच करता है।", "silent_payments_scan_from_height": "ब्लॉक ऊंचाई से स्कैन करें", @@ -820,4 +821,4 @@ "you_will_get": "में बदलें", "you_will_send": "से रूपांतरित करें", "yy": "वाईवाई" -} +} \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 7a570e4380..ae5fff09d4 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -619,6 +619,7 @@ "signTransaction": "Potpišite transakciju", "signup_for_card_accept_terms": "Prijavite se za karticu i prihvatite uvjete.", "silent_payments": "Tiha plaćanja", + "silent_payments_disclaimer": "Nove adrese nisu novi identiteti. To je ponovna upotreba postojećeg identiteta s drugom oznakom.", "silent_payments_scan_from_date": "Skeniranje iz datuma", "silent_payments_scan_from_date_or_blockheight": "Unesite visinu bloka koju želite započeti skeniranje za dolazna tiha plaćanja ili umjesto toga upotrijebite datum. Možete odabrati da li novčanik nastavlja skenirati svaki blok ili provjerava samo navedenu visinu.", "silent_payments_scan_from_height": "Skeniranje s visine bloka", @@ -818,4 +819,4 @@ "you_will_get": "Razmijeni u", "you_will_send": "Razmijeni iz", "yy": "GG" -} +} \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index f12416b5e4..e2c9a2e344 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -622,6 +622,7 @@ "signTransaction": "Tandatangani Transaksi", "signup_for_card_accept_terms": "Daftar untuk kartu dan terima syarat dan ketentuan.", "silent_payments": "Pembayaran diam", + "silent_payments_disclaimer": "Alamat baru bukanlah identitas baru. Ini adalah penggunaan kembali identitas yang ada dengan label yang berbeda.", "silent_payments_scan_from_date": "Pindai dari tanggal", "silent_payments_scan_from_date_or_blockheight": "Harap masukkan ketinggian blok yang ingin Anda mulai pemindaian untuk pembayaran diam yang masuk, atau, gunakan tanggal sebagai gantinya. Anda dapat memilih jika dompet terus memindai setiap blok, atau memeriksa hanya ketinggian yang ditentukan.", "silent_payments_scan_from_height": "Pindai dari Tinggi Blok", @@ -821,4 +822,4 @@ "you_will_get": "Konversi ke", "you_will_send": "Konversi dari", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 750a72d9aa..8dffcfc212 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -621,6 +621,7 @@ "signTransaction": "Firma la transazione", "signup_for_card_accept_terms": "Registrati per la carta e accetta i termini.", "silent_payments": "Pagamenti silenziosi", + "silent_payments_disclaimer": "I nuovi indirizzi non sono nuove identità. È un riutilizzo di un'identità esistente con un'etichetta diversa.", "silent_payments_scan_from_date": "Scansionare dalla data", "silent_payments_scan_from_date_or_blockheight": "Inserisci l'altezza del blocco che si desidera iniziare la scansione per i pagamenti silenziosi in arrivo o, utilizza invece la data. Puoi scegliere se il portafoglio continua a scansionare ogni blocco o controlla solo l'altezza specificata.", "silent_payments_scan_from_height": "Scansione dall'altezza del blocco", @@ -821,4 +822,4 @@ "you_will_get": "Converti a", "you_will_send": "Conveti da", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 2c65216938..bfa555ab2b 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -620,6 +620,7 @@ "signTransaction": "トランザクションに署名する", "signup_for_card_accept_terms": "カードにサインアップして、利用規約に同意してください。", "silent_payments": "サイレント支払い", + "silent_payments_disclaimer": "新しいアドレスは新しいアイデンティティではありません。これは、異なるラベルを持つ既存のアイデンティティの再利用です。", "silent_payments_scan_from_date": "日付からスキャンします", "silent_payments_scan_from_date_or_blockheight": "着信のサイレント決済のためにスキャンを開始するブロックの高さを入力するか、代わりに日付を使用してください。ウォレットがすべてのブロックをスキャンし続けるか、指定された高さのみをチェックするかどうかを選択できます。", "silent_payments_scan_from_height": "ブロックの高さからスキャンします", @@ -819,4 +820,4 @@ "you_will_get": "に変換", "you_will_send": "から変換", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index dc68515c8d..78a693a6ca 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -620,6 +620,7 @@ "signTransaction": "거래 서명", "signup_for_card_accept_terms": "카드에 가입하고 약관에 동의합니다.", "silent_payments": "조용한 지불", + "silent_payments_disclaimer": "새로운 주소는 새로운 정체성이 아닙니다. 다른 레이블로 기존 신원을 재사용하는 것입니다.", "silent_payments_scan_from_date": "날짜부터 스캔하십시오", "silent_payments_scan_from_date_or_blockheight": "들어오는 사일런트 결제를 위해 스캔을 시작하려는 블록 높이를 입력하거나 대신 날짜를 사용하십시오. 지갑이 모든 블록을 계속 스캔하는지 여부를 선택하거나 지정된 높이 만 확인할 수 있습니다.", "silent_payments_scan_from_height": "블록 높이에서 스캔하십시오", @@ -820,4 +821,4 @@ "you_will_send": "다음에서 변환", "YY": "YY", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index d4164b4547..5df33cf8b6 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -619,6 +619,7 @@ "signTransaction": "ငွေလွှဲဝင်ပါ။", "signup_for_card_accept_terms": "ကတ်အတွက် စာရင်းသွင်းပြီး စည်းကမ်းချက်များကို လက်ခံပါ။", "silent_payments": "အသံတိတ်ငွေပေးချေမှု", + "silent_payments_disclaimer": "လိပ်စာအသစ်များသည်အထောက်အထားအသစ်များမဟုတ်ပါ။ ၎င်းသည်ကွဲပြားခြားနားသောတံဆိပ်ဖြင့်ရှိပြီးသားဝိသေသလက်ခဏာကိုပြန်လည်အသုံးပြုခြင်းဖြစ်သည်။", "silent_payments_scan_from_date": "ရက်စွဲမှစကင်ဖတ်ပါ", "silent_payments_scan_from_date_or_blockheight": "ကျေးဇူးပြု. သင်ဝင်လာသောအသံတိတ်ငွေပေးချေမှုအတွက်သင်စကင်ဖတ်စစ်ဆေးလိုသည့်အမြင့်ကိုဖြည့်ပါ။ သို့မဟုတ်နေ့စွဲကိုသုံးပါ။ Wallet သည်လုပ်ကွက်တိုင်းကိုဆက်လက်စကင်ဖတ်စစ်ဆေးပါကသို့မဟုတ်သတ်မှတ်ထားသောအမြင့်ကိုသာစစ်ဆေးပါကသင်ရွေးချယ်နိုင်သည်။", "silent_payments_scan_from_height": "ပိတ်ပင်တားဆီးမှုအမြင့်ကနေ scan", @@ -818,4 +819,4 @@ "you_will_get": "သို့ပြောင်းပါ။", "you_will_send": "မှပြောင်းပါ။", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index cdd50981e7..e2bd9a2a78 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -619,6 +619,7 @@ "signTransaction": "Transactie ondertekenen", "signup_for_card_accept_terms": "Meld je aan voor de kaart en accepteer de voorwaarden.", "silent_payments": "Stille betalingen", + "silent_payments_disclaimer": "Nieuwe adressen zijn geen nieuwe identiteiten. Het is een hergebruik van een bestaande identiteit met een ander label.", "silent_payments_scan_from_date": "Scan vanaf datum", "silent_payments_scan_from_date_or_blockheight": "Voer de blokhoogte in die u wilt beginnen met scannen op inkomende stille betalingen, of gebruik in plaats daarvan de datum. U kunt kiezen of de portemonnee elk blok blijft scannen of alleen de opgegeven hoogte controleert.", "silent_payments_scan_from_height": "Scan van blokhoogte", @@ -819,4 +820,4 @@ "you_will_get": "Converteren naar", "you_will_send": "Converteren van", "yy": "JJ" -} +} \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index cde8cad380..2834e4772b 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -619,6 +619,7 @@ "signTransaction": "Podpisz transakcję", "signup_for_card_accept_terms": "Zarejestruj się, aby otrzymać kartę i zaakceptuj warunki.", "silent_payments": "Ciche płatności", + "silent_payments_disclaimer": "Nowe adresy nie są nową tożsamością. Jest to ponowne wykorzystanie istniejącej tożsamości z inną etykietą.", "silent_payments_scan_from_date": "Skanuj z daty", "silent_payments_scan_from_date_or_blockheight": "Wprowadź wysokość bloku, którą chcesz rozpocząć skanowanie w poszukiwaniu cichej płatności lub zamiast tego skorzystaj z daty. Możesz wybrać, czy portfel kontynuuje skanowanie każdego bloku, lub sprawdza tylko określoną wysokość.", "silent_payments_scan_from_height": "Skanuj z wysokości bloku", @@ -818,4 +819,4 @@ "you_will_get": "Konwertuj na", "you_will_send": "Konwertuj z", "yy": "RR" -} +} \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 168b0312a1..eaa51320f4 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -621,6 +621,7 @@ "signTransaction": "Assinar transação", "signup_for_card_accept_terms": "Cadastre-se no cartão e aceite os termos.", "silent_payments": "Pagamentos silenciosos", + "silent_payments_disclaimer": "Novos endereços não são novas identidades. É uma reutilização de uma identidade existente com um rótulo diferente.", "silent_payments_scan_from_date": "Escanear a partir da data", "silent_payments_scan_from_date_or_blockheight": "Por favor, insira a altura do bloco que deseja iniciar o escaneamento para obter pagamentos silenciosos ou use a data. Você pode escolher se a carteira continua digitalizando cada bloco ou verifica apenas a altura especificada.", "silent_payments_scan_from_height": "Escanear a partir da altura do bloco", @@ -821,4 +822,4 @@ "you_will_get": "Converter para", "you_will_send": "Converter de", "yy": "aa" -} +} \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 9a8f6563a6..fb9b2c12d0 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -620,6 +620,7 @@ "signTransaction": "Подписать транзакцию", "signup_for_card_accept_terms": "Подпишитесь на карту и примите условия.", "silent_payments": "Молчаливые платежи", + "silent_payments_disclaimer": "Новые адреса не являются новыми личностями. Это повторное использование существующей идентичности с другой этикеткой.", "silent_payments_scan_from_date": "Сканирование с даты", "silent_payments_scan_from_date_or_blockheight": "Пожалуйста, введите высоту блока, которую вы хотите начать сканирование для входящих молчаливых платежей, или вместо этого используйте дату. Вы можете выбрать, продолжает ли кошелек сканировать каждый блок или проверять только указанную высоту.", "silent_payments_scan_from_height": "Сканирование с высоты блока", @@ -819,4 +820,4 @@ "you_will_get": "Конвертировать в", "you_will_send": "Конвертировать из", "yy": "ГГ" -} +} \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 8bee7acc6f..b7dffc565a 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -619,6 +619,7 @@ "signTransaction": "ลงนามในการทำธุรกรรม", "signup_for_card_accept_terms": "ลงทะเบียนสำหรับบัตรและยอมรับเงื่อนไข", "silent_payments": "การชำระเงินเงียบ", + "silent_payments_disclaimer": "ที่อยู่ใหม่ไม่ใช่ตัวตนใหม่ มันเป็นการใช้ซ้ำของตัวตนที่มีอยู่ด้วยฉลากที่แตกต่างกัน", "silent_payments_scan_from_date": "สแกนตั้งแต่วันที่", "silent_payments_scan_from_date_or_blockheight": "โปรดป้อนความสูงของบล็อกที่คุณต้องการเริ่มการสแกนสำหรับการชำระเงินแบบเงียบ ๆ หรือใช้วันที่แทน คุณสามารถเลือกได้ว่ากระเป๋าเงินยังคงสแกนทุกบล็อกหรือตรวจสอบความสูงที่ระบุเท่านั้น", "silent_payments_scan_from_height": "สแกนจากความสูงของบล็อก", @@ -818,4 +819,4 @@ "you_will_get": "แปลงเป็น", "you_will_send": "แปลงจาก", "yy": "ปี" -} +} \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 784a91892e..35c777530d 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -619,6 +619,7 @@ "signTransaction": "Mag-sign Transaksyon", "signup_for_card_accept_terms": "Mag -sign up para sa card at tanggapin ang mga termino.", "silent_payments": "Tahimik na pagbabayad", + "silent_payments_disclaimer": "Ang mga bagong address ay hindi mga bagong pagkakakilanlan. Ito ay isang muling paggamit ng isang umiiral na pagkakakilanlan na may ibang label.", "silent_payments_scan_from_date": "I -scan mula sa petsa", "silent_payments_scan_from_date_or_blockheight": "Mangyaring ipasok ang taas ng block na nais mong simulan ang pag -scan para sa papasok na tahimik na pagbabayad, o, gamitin ang petsa sa halip. Maaari kang pumili kung ang pitaka ay patuloy na pag -scan sa bawat bloke, o suriin lamang ang tinukoy na taas.", "silent_payments_scan_from_height": "I -scan mula sa taas ng block", @@ -818,4 +819,4 @@ "you_will_get": "Mag -convert sa", "you_will_send": "I -convert mula sa", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 95844f593b..a384bc08ea 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -619,6 +619,7 @@ "signTransaction": "İşlem İmzala", "signup_for_card_accept_terms": "Kart için kaydol ve koşulları kabul et.", "silent_payments": "Sessiz ödemeler", + "silent_payments_disclaimer": "Yeni adresler yeni kimlikler değildir. Farklı bir etikete sahip mevcut bir kimliğin yeniden kullanımıdır.", "silent_payments_scan_from_date": "Tarihten tarama", "silent_payments_scan_from_date_or_blockheight": "Lütfen gelen sessiz ödemeler için taramaya başlamak istediğiniz blok yüksekliğini girin veya bunun yerine tarihi kullanın. Cüzdanın her bloğu taramaya devam edip etmediğini veya yalnızca belirtilen yüksekliği kontrol edip etmediğini seçebilirsiniz.", "silent_payments_scan_from_height": "Blok yüksekliğinden tarama", @@ -818,4 +819,4 @@ "you_will_get": "Biçimine dönüştür:", "you_will_send": "Biçiminden dönüştür:", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index c9aaef6f27..d0cdfa7059 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -620,6 +620,7 @@ "signTransaction": "Підписати транзакцію", "signup_for_card_accept_terms": "Зареєструйтеся на картку та прийміть умови.", "silent_payments": "Мовчазні платежі", + "silent_payments_disclaimer": "Нові адреси - це не нові ідентичності. Це повторне використання існуючої ідентичності з іншою етикеткою.", "silent_payments_scan_from_date": "Сканувати з дати", "silent_payments_scan_from_date_or_blockheight": "Введіть висоту блоку, яку ви хочете почати сканувати для вхідних мовчазних платежів, або скористайтеся датою замість цього. Ви можете вибрати, якщо гаманець продовжує сканувати кожен блок, або перевіряє лише вказану висоту.", "silent_payments_scan_from_height": "Сканування від висоти блоку", @@ -819,4 +820,4 @@ "you_will_get": "Конвертувати в", "you_will_send": "Конвертувати з", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index c02919463b..ef911df1ef 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -621,6 +621,7 @@ "signTransaction": "۔ﮟﯾﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", "signup_for_card_accept_terms": "کارڈ کے لیے سائن اپ کریں اور شرائط کو قبول کریں۔", "silent_payments": "خاموش ادائیگی", + "silent_payments_disclaimer": "نئے پتے نئی شناخت نہیں ہیں۔ یہ ایک مختلف لیبل کے ساتھ موجودہ شناخت کا دوبارہ استعمال ہے۔", "silent_payments_scan_from_date": "تاریخ سے اسکین کریں", "silent_payments_scan_from_date_or_blockheight": "براہ کرم بلاک اونچائی میں داخل ہوں جس سے آپ آنے والی خاموش ادائیگیوں کے لئے اسکیننگ شروع کرنا چاہتے ہیں ، یا اس کے بجائے تاریخ کا استعمال کریں۔ آپ یہ منتخب کرسکتے ہیں کہ اگر پرس ہر بلاک کو اسکیننگ جاری رکھے ہوئے ہے ، یا صرف مخصوص اونچائی کی جانچ پڑتال کرتا ہے۔", "silent_payments_scan_from_height": "بلاک اونچائی سے اسکین کریں", @@ -820,4 +821,4 @@ "you_will_get": "میں تبدیل کریں۔", "you_will_send": "سے تبدیل کریں۔", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 76a17bc997..2fe812c08e 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -620,6 +620,7 @@ "signTransaction": "Wole Idunadura", "signup_for_card_accept_terms": "Ẹ f'orúkọ sílẹ̀ láti gba káàdì àti àjọrò.", "silent_payments": "Awọn sisanwo ipalọlọ", + "silent_payments_disclaimer": "Awọn adirẹsi tuntun kii ṣe awọn idanimọ tuntun. O jẹ yiyan ti idanimọ ti o wa pẹlu aami oriṣiriṣi.", "silent_payments_scan_from_date": "Scan lati ọjọ", "silent_payments_scan_from_date_or_blockheight": "Jọwọ tẹ giga idibo ti o fẹ bẹrẹ ọlọjẹ fun awọn sisanwo ipalọlọ, tabi, lo ọjọ dipo. O le yan ti apamọwọ naa tẹsiwaju nṣapẹẹrẹ gbogbo bulọọki, tabi ṣayẹwo nikan giga ti o sọ tẹlẹ.", "silent_payments_scan_from_height": "Scan lati Iga Iga", @@ -819,4 +820,4 @@ "you_will_get": "Ṣe pàṣípààrọ̀ sí", "you_will_send": "Ṣe pàṣípààrọ̀ láti", "yy": "Ọd" -} +} \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index c60807e2cc..1a4d0b3414 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -619,6 +619,7 @@ "signTransaction": "签署交易", "signup_for_card_accept_terms": "注册卡并接受条款。", "silent_payments": "无声付款", + "silent_payments_disclaimer": "新地址不是新的身份。这是重复使用具有不同标签的现有身份。", "silent_payments_scan_from_date": "从日期开始扫描", "silent_payments_scan_from_date_or_blockheight": "请输入您要开始扫描输入静音付款的块高度,或者使用日期。您可以选择钱包是否继续扫描每个块,或仅检查指定的高度。", "silent_payments_scan_from_height": "从块高度扫描", @@ -818,4 +819,4 @@ "you_will_get": "转换到", "you_will_send": "转换自", "yy": "YY" -} +} \ No newline at end of file From 6ee57a7be4ab4d1bf68929c2230e620552d6ef24 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 8 Apr 2024 20:37:48 -0300 Subject: [PATCH 023/242] fix: missing i18n --- lib/src/screens/receive/widgets/address_cell.dart | 2 +- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 27 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/src/screens/receive/widgets/address_cell.dart b/lib/src/screens/receive/widgets/address_cell.dart index a07456284d..69e0119004 100644 --- a/lib/src/screens/receive/widgets/address_cell.dart +++ b/lib/src/screens/receive/widgets/address_cell.dart @@ -134,7 +134,7 @@ class AddressCell extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: [ Text( - 'Balance: $balance', + '${S.of(context).balance}: $txCount', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 5646dcf83e..2cdc983953 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -70,6 +70,7 @@ "backup": "نسخ الاحتياطي", "backup_file": "ملف النسخ الاحتياطي", "backup_password": "كلمة مرور النسخ الاحتياطي", + "balance": "توازن", "balance_page": "صفحة التوازن", "bill_amount": "مبلغ الفاتورة", "billing_address_info": "إذا طُلب منك عنوان إرسال فواتير ، فأدخل عنوان الشحن الخاص بك", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 5c06a9cbdc..7b7c62e506 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -70,6 +70,7 @@ "backup": "Резервно копие", "backup_file": "Резервно копие", "backup_password": "Парола за възстановяване", + "balance": "Баланс", "balance_page": "Страница за баланс", "bill_amount": "Искана сума", "billing_address_info": "Ако Ви попитат за билинг адрес, въведето своя адрес за доставка", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index adb194a1a3..b032f4ee31 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -70,6 +70,7 @@ "backup": "Záloha", "backup_file": "Soubor se zálohou", "backup_password": "Heslo pro zálohy", + "balance": "Zůstatek", "balance_page": "Stránka zůstatku", "bill_amount": "Účtovaná částka", "billing_address_info": "Při dotazu na fakturační adresu, zadejte svou doručovací adresu", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 7a7e60413d..0168f415cd 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -70,6 +70,7 @@ "backup": "Sicherung", "backup_file": "Sicherungsdatei", "backup_password": "Passwort sichern", + "balance": "Gleichgewicht", "balance_page": "Balance-Seite", "bill_amount": "Rechnungsbetrag", "billing_address_info": "Wenn Sie nach einer Rechnungsadresse gefragt werden, geben Sie bitte Ihre Lieferadresse an", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 3b6afa1c86..a22844bc18 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -70,6 +70,7 @@ "backup": "Backup", "backup_file": "Backup file", "backup_password": "Backup password", + "balance": "Balance", "balance_page": "Balance Page", "bill_amount": "Bill Amount", "billing_address_info": "If asked for a billing address, provide your shipping address", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 1166d6bbe3..6a190abc06 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -70,6 +70,7 @@ "backup": "Apoyo", "backup_file": "Archivo de respaldo", "backup_password": "Contraseña de respaldo", + "balance": "Balance", "balance_page": "Página de saldo", "bill_amount": "Importe de la factura", "billing_address_info": "Si se le solicita una dirección de facturación, proporcione su dirección de envío", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 7ae4c3da70..ecfa4bc71c 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -70,6 +70,7 @@ "backup": "Sauvegarde", "backup_file": "Fichier de sauvegarde", "backup_password": "Mot de passe de sauvegarde", + "balance": "Équilibre", "balance_page": "Page Solde", "bill_amount": "Montant de la facture", "billing_address_info": "Si une adresse de facturation vous est demandée, indiquez votre adresse de livraison", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index bb78d76765..696fc79907 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -70,6 +70,7 @@ "backup": "Ajiyayyen", "backup_file": "Ajiyayyen fayil", "backup_password": "Ajiyayyen kalmar sirri", + "balance": "Ma'auni", "balance_page": "Ma'auni Page", "bill_amount": "Adadin Bill", "billing_address_info": "Idan an nemi adireshin biyan kuɗi, samar da adireshin jigilar kaya", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 8b6599a4d7..c45cc96da2 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -70,6 +70,7 @@ "backup": "बैकअप", "backup_file": "बैकअपफ़ाइल", "backup_password": "बैकअप पासवर्ड", + "balance": "संतुलन", "balance_page": "बैलेंस पेज", "bill_amount": "बिल राशि", "billing_address_info": "यदि बिलिंग पता मांगा जाए, तो अपना शिपिंग पता प्रदान करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index ae5fff09d4..03e99ac551 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -70,6 +70,7 @@ "backup": "Sigurnosna kopija", "backup_file": "Sigurnosna kopija datoteke", "backup_password": "Lozinka za sigurnosnu kopiju", + "balance": "Uravnotežiti", "balance_page": "Stranica sa stanjem", "bill_amount": "Iznos računa", "billing_address_info": "Ako se od vas zatraži adresa za naplatu, navedite svoju adresu za dostavu", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index e2c9a2e344..33745c6c95 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -70,6 +70,7 @@ "backup": "Cadangan", "backup_file": "File cadangan", "backup_password": "Kata sandi cadangan", + "balance": "Keseimbangan", "balance_page": "Halaman Saldo", "bill_amount": "Jumlah Tagihan", "billing_address_info": "Jika diminta alamat billing, berikan alamat pengiriman Anda", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 8dffcfc212..77884037ae 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -70,6 +70,7 @@ "backup": "Backup", "backup_file": "Backup file", "backup_password": "Backup password", + "balance": "Bilancia", "balance_page": "Pagina di equilibrio", "bill_amount": "Importo della fattura", "billing_address_info": "Se ti viene richiesto un indirizzo di fatturazione, fornisci il tuo indirizzo di spedizione", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index bfa555ab2b..36932fbc0d 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -70,6 +70,7 @@ "backup": "バックアップ", "backup_file": "バックアップファイル", "backup_password": "バックアップパスワード", + "balance": "バランス", "balance_page": "残高ページ", "bill_amount": "請求額", "billing_address_info": "請求先住所を尋ねられた場合は、配送先住所を入力してください", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 78a693a6ca..eb6f1ce726 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -70,6 +70,7 @@ "backup": "지원", "backup_file": "백업 파일", "backup_password": "백업 비밀번호", + "balance": "균형", "balance_page": "잔액 페이지", "bill_amount": "청구 금액", "billing_address_info": "청구서 수신 주소를 묻는 메시지가 표시되면 배송 주소를 입력하세요.", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 5df33cf8b6..77fa298ebe 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -70,6 +70,7 @@ "backup": "မိတ္တူ", "backup_file": "အရန်ဖိုင်", "backup_password": "စကားဝှက်ကို အရန်သိမ်းဆည်းပါ။", + "balance": "လက်ကျန်ငေှ", "balance_page": "လက်ကျန်စာမျက်နှာ", "bill_amount": "ဘီလ်ပမာဏ", "billing_address_info": "ငွေပေးချေရမည့်လိပ်စာကို တောင်းဆိုပါက သင့်ပို့ဆောင်ရေးလိပ်စာကို ပေးပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index e2bd9a2a78..4a60a994e9 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -70,6 +70,7 @@ "backup": "Back-up", "backup_file": "Backup bestand", "backup_password": "Reservewachtwoord", + "balance": "Evenwicht", "balance_page": "Saldo pagina", "bill_amount": "Bill bedrag", "billing_address_info": "Als u om een ​​factuuradres wordt gevraagd, geef dan uw verzendadres op", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 2834e4772b..54859366dd 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -70,6 +70,7 @@ "backup": "Kopia zapasowa", "backup_file": "Plik kopii zapasowej", "backup_password": "Hasło kpoii zapasowej", + "balance": "Balansować", "balance_page": "Strona salda", "bill_amount": "Kwota rachunku", "billing_address_info": "Jeśli zostaniesz poproszony o podanie adresu rozliczeniowego, podaj swój adres wysyłki", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index eaa51320f4..eb1c8e3d83 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -70,6 +70,7 @@ "backup": "Cópia de segurança", "backup_file": "Arquivo de backup", "backup_password": "Senha de backup", + "balance": "Equilíbrio", "balance_page": "Página de saldo", "bill_amount": "Valor da conta", "billing_address_info": "Se for solicitado um endereço de cobrança, forneça seu endereço de entrega", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index fb9b2c12d0..946ef093c7 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -70,6 +70,7 @@ "backup": "Резервная копия", "backup_file": "Файл резервной копии", "backup_password": "Пароль резервной копии", + "balance": "Баланс", "balance_page": "Страница баланса", "bill_amount": "Сумма счета", "billing_address_info": "Если вас попросят указать платежный адрес, укажите адрес доставки", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index b7dffc565a..7778dd876e 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -70,6 +70,7 @@ "backup": "สำรองข้อมูล", "backup_file": "ไฟล์สำรองข้อมูล", "backup_password": "รหัสผ่านสำรองข้อมูล", + "balance": "สมดุล", "balance_page": "หน้ายอดคงเหลือ", "bill_amount": "จำนวนบิล", "billing_address_info": "ถ้าถูกร้องขอที่อยู่สำหรับการวางบิล ให้ใช้ที่อยู่จัดส่งของคุณ", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 35c777530d..4e1dcfa8fe 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -70,6 +70,7 @@ "backup": "Backup", "backup_file": "Backup file", "backup_password": "Backup password", + "balance": "Balansehin", "balance_page": "Pahina ng Balanse", "bill_amount": "Halaga ng Bill", "billing_address_info": "Kung tatanungin ang isang address ng pagsingil, ibigay ang iyong address sa pagpapadala", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index a384bc08ea..321e2a0e14 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -70,6 +70,7 @@ "backup": "Yedek", "backup_file": "Yedek dosyası", "backup_password": "Yedek parolası", + "balance": "Denge", "balance_page": "Bakiye Sayfası", "bill_amount": "Fatura Tutarı", "billing_address_info": "Eğer fatura adresi istenirse, kargo adresinizi girin", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index d0cdfa7059..407d19447b 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -70,6 +70,7 @@ "backup": "Резервна копія", "backup_file": "Файл резервної копії", "backup_password": "Пароль резервної копії", + "balance": "Балансувати", "balance_page": "Сторінка балансу", "bill_amount": "Сума рахунку", "billing_address_info": "Якщо буде запропоновано платіжну адресу, вкажіть свою адресу доставки", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index ef911df1ef..62c5eda8ee 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -70,6 +70,7 @@ "backup": "بیک اپ", "backup_file": "بیک اپ فائل", "backup_password": "بیک اپ پاس ورڈ", + "balance": "بقیہ", "balance_page": "بیلنس صفحہ", "bill_amount": "بل رقم", "billing_address_info": "اگر آپ سے بلنگ کا پتہ پوچھا جائے تو اپنا شپنگ ایڈریس فراہم کریں۔", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 2fe812c08e..a90ddd3ae4 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -70,6 +70,7 @@ "backup": "Ṣẹ̀dà", "backup_file": "Ṣẹ̀dà akọsílẹ̀", "backup_password": "Ṣẹ̀dà ọ̀rọ̀ aṣínà", + "balance": "Iwọntunwọnsi", "balance_page": "Oju-iwe iwọntunwọnsi", "bill_amount": "Iye ìwé owó", "billing_address_info": "Tí ọlọ́jà bá bèèrè àdírẹ́sì sísan yín, fún òun ni àdírẹ́sì t'á ránṣẹ́ káàdì yìí sí", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 1a4d0b3414..9d5bd9a191 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -70,6 +70,7 @@ "backup": "备份", "backup_file": "备份文件", "backup_password": "备份密码", + "balance": "平衡", "balance_page": "余额页", "bill_amount": "账单金额", "billing_address_info": "如果要求提供帐单地址,请提供您的送货地址", From 058ff6aeecdb2705e15f97db44b5c04a1c0dce97 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 8 Apr 2024 21:06:49 -0300 Subject: [PATCH 024/242] chore: print --- cw_bitcoin/lib/electrum_wallet_addresses.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 1dd11bad3e..c21bc6c902 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -370,10 +370,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } if (addressRecord.address != address) { - print([ - addressRecord.address, - addressRecord.name.isEmpty ? "Silent Payments" : addressRecord.name - ]); addressesMap[addressRecord.address] = addressRecord.name.isEmpty ? "Silent Payments" : "Silent Payments - " + addressRecord.name; From 8ea2e6ee407d48389e12c3829ee7807a4e74a741 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 9 Apr 2024 12:15:21 -0300 Subject: [PATCH 025/242] feat: single block scan, rescan by date working for btc mainnet --- cw_bitcoin/lib/electrum_wallet.dart | 33 +++++++++++------ cw_core/lib/get_height_by_date.dart | 29 +++++++++++++++ lib/bitcoin/cw_bitcoin.dart | 8 ++++ lib/src/screens/rescan/rescan_page.dart | 14 ++++--- lib/src/widgets/blockchain_height_widget.dart | 37 ++++++++++++++++++- lib/view_model/rescan_view_model.dart | 16 ++++++-- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 5 ++- res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + tool/configure.dart | 4 +- 33 files changed, 147 insertions(+), 24 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 244faf3e9b..a5a29b6a6f 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -201,7 +201,7 @@ abstract class ElectrumWalletBase } @action - Future _setListeners(int height, {int? chainTip}) async { + Future _setListeners(int height, {int? chainTip, bool? doSingleScan}) async { final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; syncStatus = AttemptingSyncStatus(); @@ -223,6 +223,7 @@ abstract class ElectrumWalletBase transactionHistoryIds: transactionHistory.transactions.keys.toList(), node: ScanNode(node!.uri, node!.useSSL), labels: walletAddresses.labels, + isSingleScan: doSingleScan ?? false, )); await for (var message in receivePort) { @@ -981,9 +982,10 @@ abstract class ElectrumWalletBase @action @override - Future rescan({required int height, int? chainTip, ScanData? scanData}) async { + Future rescan( + {required int height, int? chainTip, ScanData? scanData, bool? doSingleScan}) async { silentPaymentsScanningActive = true; - _setListeners(height); + _setListeners(height, doSingleScan: doSingleScan); } @override @@ -1606,6 +1608,7 @@ class ScanData { final ElectrumClient electrumClient; final List transactionHistoryIds; final Map labels; + final bool isSingleScan; ScanData({ required this.sendPort, @@ -1617,6 +1620,7 @@ class ScanData { required this.electrumClient, required this.transactionHistoryIds, required this.labels, + required this.isSingleScan, }); factory ScanData.fromHeight(ScanData scanData, int newHeight) { @@ -1630,6 +1634,7 @@ class ScanData { transactionHistoryIds: scanData.transactionHistoryIds, electrumClient: scanData.electrumClient, labels: scanData.labels, + isSingleScan: scanData.isSingleScan, ); } } @@ -1689,11 +1694,17 @@ Future startRefresh(ScanData scanData) async { while (true) { lastKnownBlockHeight = syncHeight; - final syncingStatus = - SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight); + SyncingSyncStatus syncingStatus; + if (scanData.isSingleScan) { + syncingStatus = SyncingSyncStatus(1, 0); + } else { + syncingStatus = + SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight); + } + scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); - if (syncingStatus.blocksLeft <= 0) { + if (syncingStatus.blocksLeft <= 0 || (scanData.isSingleScan && scanData.height != syncHeight)) { scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); return; } @@ -1701,7 +1712,8 @@ Future startRefresh(ScanData scanData) async { try { final electrumClient = await getElectrumConnection(); - final scanningBlockCount = scanData.network == BitcoinNetwork.testnet ? 50 : 10; + final scanningBlockCount = + scanData.isSingleScan ? 1 : (scanData.network == BitcoinNetwork.testnet ? 1 : 10); Map? tweaks; try { @@ -1743,8 +1755,7 @@ Future startRefresh(ScanData scanData) async { scanData.silentAddress.b_scan, scanData.silentAddress.B_spend, outputPubkeys.values - .map((o) => getScriptFromOutput( - o["pubkey"].toString(), int.parse(o["amount"].toString()))) + .map((o) => getScriptFromOutput(o[0].toString(), int.parse(o[1].toString()))) .toList(), precomputedLabels: scanData.labels, ); @@ -1770,9 +1781,9 @@ Future startRefresh(ScanData scanData) async { int? amount; int? pos; outputPubkeys.entries.firstWhere((k) { - final matches = k.value["pubkey"] == key; + final matches = k.value[0] == key; if (matches) { - amount = int.parse(k.value["amount"].toString()); + amount = int.parse(k.value[1].toString()); pos = int.parse(k.key.toString()); return true; } diff --git a/cw_core/lib/get_height_by_date.dart b/cw_core/lib/get_height_by_date.dart index 6f3ccaf686..1dc624a7ff 100644 --- a/cw_core/lib/get_height_by_date.dart +++ b/cw_core/lib/get_height_by_date.dart @@ -242,3 +242,32 @@ Future getHavenCurrentHeight() async { throw Exception('Failed to load current blockchain height'); } } + +// Data taken from https://timechaincalendar.com/ +const bitcoinDates = { + "2024-04": 837182, + "2024-03": 832623, + "2024-02": 828319, + "2024-01": 823807, + "2023-12": 819206, + "2023-11": 814765, + "2023-10": 810098, + "2023-09": 805675, + "2023-08": 801140, + "2023-07": 796640, + "2023-06": 792330, + "2023-05": 787733, + "2023-04": 783403, + "2023-03": 778740, + "2023-02": 774525, + "2023-01": 769810, +}; + +int getBitcoinHeightByDate({required DateTime date}) { + String closestKey = + bitcoinDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => ''); + + final oldestHeight = bitcoinDates.values.last; + + return bitcoinDates[closestKey] ?? oldestHeight; +} diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index bcbb0bc07b..49fdcedab6 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -335,4 +335,12 @@ class CWBitcoin extends Bitcoin { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.isTestnet ?? false; } + + @override + int getHeightByDate({required DateTime date}) => getBitcoinHeightByDate(date: date); + + void rescan(Object wallet, {required int height, bool? doSingleScan}) { + final bitcoinWallet = wallet as ElectrumWallet; + bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan); + } } diff --git a/lib/src/screens/rescan/rescan_page.dart b/lib/src/screens/rescan/rescan_page.dart index f4dc865f03..da0f351378 100644 --- a/lib/src/screens/rescan/rescan_page.dart +++ b/lib/src/screens/rescan/rescan_page.dart @@ -21,11 +21,15 @@ class RescanPage extends BasePage { return Padding( padding: EdgeInsets.only(left: 24, right: 24, bottom: 24), child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - BlockchainHeightWidget( - key: _blockchainHeightWidgetKey, - onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value, - isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan, - ), + Observer( + builder: (_) => BlockchainHeightWidget( + key: _blockchainHeightWidgetKey, + onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value, + isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan, + doSingleScan: _rescanViewModel.doSingleScan, + toggleSingleScan: () => + _rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan, + )), Observer( builder: (_) => LoadingPrimaryButton( isLoading: _rescanViewModel.state == RescanWalletState.rescaning, diff --git a/lib/src/widgets/blockchain_height_widget.dart b/lib/src/widgets/blockchain_height_widget.dart index fcfeedda32..d85680cc8d 100644 --- a/lib/src/widgets/blockchain_height_widget.dart +++ b/lib/src/widgets/blockchain_height_widget.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/src/widgets/standard_switch.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/utils/date_picker.dart'; import 'package:flutter/material.dart'; @@ -14,6 +16,8 @@ class BlockchainHeightWidget extends StatefulWidget { this.onHeightOrDateEntered, this.hasDatePicker = true, this.isSilentPaymentsScan = false, + this.toggleSingleScan, + this.doSingleScan = false, }) : super(key: key); final Function(int)? onHeightChange; @@ -21,6 +25,8 @@ class BlockchainHeightWidget extends StatefulWidget { final FocusNode? focusNode; final bool hasDatePicker; final bool isSilentPaymentsScan; + final bool doSingleScan; + final Function()? toggleSingleScan; @override State createState() => BlockchainHeightState(); @@ -101,6 +107,30 @@ class BlockchainHeightState extends State { )) ], ), + if (widget.isSilentPaymentsScan) + Padding( + padding: EdgeInsets.only(top: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + S.of(context).scan_one_block, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), + ), + Padding( + padding: const EdgeInsets.only(right: 8), + child: StandardSwitch( + value: widget.doSingleScan, + onTaped: () => widget.toggleSingleScan?.call(), + ), + ) + ], + ), + ), Padding( padding: EdgeInsets.only(left: 40, right: 40, top: 24), child: Text( @@ -126,7 +156,12 @@ class BlockchainHeightState extends State { lastDate: now); if (date != null) { - final height = monero!.getHeightByDate(date: date); + int height; + if (widget.isSilentPaymentsScan) { + height = bitcoin!.getHeightByDate(date: date); + } else { + height = monero!.getHeightByDate(date: date); + } setState(() { dateController.text = DateFormat('yyyy-MM-dd').format(date); restoreHeightController.text = '$height'; diff --git a/lib/view_model/rescan_view_model.dart b/lib/view_model/rescan_view_model.dart index b10e29e698..ae7baf0083 100644 --- a/lib/view_model/rescan_view_model.dart +++ b/lib/view_model/rescan_view_model.dart @@ -12,7 +12,8 @@ enum RescanWalletState { rescaning, none } abstract class RescanViewModelBase with Store { RescanViewModelBase(this._wallet) : state = RescanWalletState.none, - isButtonEnabled = false; + isButtonEnabled = false, + doSingleScan = false; final WalletBase _wallet; @@ -22,14 +23,21 @@ abstract class RescanViewModelBase with Store { @observable bool isButtonEnabled; + @observable + bool doSingleScan; + @computed - bool get isSilentPaymentsScan => bitcoin!.hasSelectedSilentPayments(_wallet); + bool get isSilentPaymentsScan => _wallet.type == WalletType.bitcoin; @action Future rescanCurrentWallet({required int restoreHeight}) async { state = RescanWalletState.rescaning; - _wallet.rescan(height: restoreHeight); - if (_wallet.type != WalletType.bitcoin) _wallet.transactionHistory.clear(); + if (_wallet.type != WalletType.bitcoin) { + _wallet.rescan(height: restoreHeight); + _wallet.transactionHistory.clear(); + } else { + bitcoin!.rescan(_wallet, height: restoreHeight, doSingleScan: doSingleScan); + } state = RescanWalletState.none; } } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 2cdc983953..44df63a3ab 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "حفظ كلمة المرور الاحتياطية", "save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ", "saved_the_trade_id": "لقد تم حفظ معرف العملية", + "scan_one_block": "مسح كتلة واحدة", "scan_qr_code": "امسح رمز QR ضوئيًا", "scan_qr_code_to_get_address": "امسح ال QR للحصول على العنوان", "scan_qr_on_device": " ﺮﺧﺁ ﺯﺎﻬﺟ ﻰﻠﻋ ﺎﻴًﺋﻮﺿ ﺍﺬﻫ ﺔﻌﻳﺮﺴﻟﺍ ﺔﺑﺎﺠﺘﺳﻻﺍ ﺰﻣﺭ ﺢﺴﻤﺑ ﻢﻗ", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 7b7c62e506..185e22e0b6 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "Запазване на паролата за възстановяване", "save_to_downloads": "Запазване в Изтегляния", "saved_the_trade_id": "Запазих trade ID-то", + "scan_one_block": "Сканирайте един блок", "scan_qr_code": "Сканирайте QR кода, за да получите адреса", "scan_qr_code_to_get_address": "Сканирайте QR кода, за да получите адреса", "scan_qr_on_device": "Сканирайте този QR код на друго устройство", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index b032f4ee31..2146ae7251 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "Uložit heslo pro zálohy", "save_to_downloads": "Uložit do Stažených souborů", "saved_the_trade_id": "Uložil jsem si ID transakce (trade ID)", + "scan_one_block": "Prohledejte jeden blok", "scan_qr_code": "Naskenujte QR kód pro získání adresy", "scan_qr_code_to_get_address": "Prohledejte QR kód a získejte adresu", "scan_qr_on_device": "Naskenujte tento QR kód na jiném zařízení", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 0168f415cd..f4b6cd0df5 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -512,6 +512,7 @@ "save_backup_password_alert": "Sicherungskennwort speichern", "save_to_downloads": "Unter „Downloads“ speichern", "saved_the_trade_id": "Ich habe die Handels-ID gespeichert", + "scan_one_block": "Einen Block scannen", "scan_qr_code": "QR-Code scannen", "scan_qr_code_to_get_address": "Scannen Sie den QR-Code, um die Adresse zu erhalten", "scan_qr_on_device": "Scannen Sie diesen QR-Code auf einem anderen Gerät", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index a22844bc18..73b2bde1cc 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "Save backup password", "save_to_downloads": "Save to Downloads", "saved_the_trade_id": "I've saved the trade ID", + "scan_one_block": "Scan one block", "scan_qr_code": "Scan QR code", "scan_qr_code_to_get_address": "Scan the QR code to get the address", "scan_qr_on_device": "Scan this QR code on another device", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 6a190abc06..03c4775689 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -512,6 +512,7 @@ "save_backup_password_alert": "Guardar contraseña de respaldo", "save_to_downloads": "Guardar en Descargas", "saved_the_trade_id": "He salvado comercial ID", + "scan_one_block": "Escanear un bloque", "scan_qr_code": "Escanear código QR", "scan_qr_code_to_get_address": "Escanee el código QR para obtener la dirección", "scan_qr_on_device": "Escanea este código QR en otro dispositivo", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index ecfa4bc71c..583fdd489e 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "Enregistrer le mot de passe de sauvegarde", "save_to_downloads": "Enregistrer dans les téléchargements", "saved_the_trade_id": "J'ai sauvegardé l'ID d'échange", + "scan_one_block": "Scanner un bloc", "scan_qr_code": "Scannez le QR code", "scan_qr_code_to_get_address": "Scannez le QR code pour obtenir l'adresse", "scan_qr_on_device": "Scannez ce code QR sur un autre appareil", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 696fc79907..4c153ee02f 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -513,6 +513,7 @@ "save_backup_password_alert": "Ajiye kalmar sirri ta ajiya", "save_to_downloads": "Ajiye zuwa Zazzagewa", "saved_the_trade_id": "Na ajiye ID na ciniki", + "scan_one_block": "Duba toshe daya", "scan_qr_code": "Gani QR kodin", "scan_qr_code_to_get_address": "Duba lambar QR don samun adireshin", "scan_qr_on_device": "Duba wannan lambar QR akan wata na'ura", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index c45cc96da2..bbf9749158 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -513,6 +513,7 @@ "save_backup_password_alert": "बैकअप पासवर्ड सेव करें", "save_to_downloads": "डाउनलोड में सहेजें", "saved_the_trade_id": "मैंने व्यापार बचा लिया है ID", + "scan_one_block": "एक ब्लॉक को स्कैन करना", "scan_qr_code": "स्कैन क्यू आर कोड", "scan_qr_code_to_get_address": "पता प्राप्त करने के लिए QR कोड स्कैन करें", "scan_qr_on_device": "इस QR कोड को किसी अन्य डिवाइस पर स्कैन करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 03e99ac551..04ac5acd4e 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "Spremi lozinku za sigurnosnu kopiju", "save_to_downloads": "Spremi u Preuzimanja", "saved_the_trade_id": "Spremio/la sam transakcijski ID", + "scan_one_block": "Skenirajte jedan blok", "scan_qr_code": "Skenirajte QR kod", "scan_qr_code_to_get_address": "Skeniraj QR kod za dobivanje adrese", "scan_qr_on_device": "Skenirajte ovaj QR kod na drugom uređaju", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 33745c6c95..f45a66b5b5 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -514,6 +514,7 @@ "save_backup_password_alert": "Simpan kata sandi cadangan", "save_to_downloads": "Simpan ke Unduhan", "saved_the_trade_id": "Saya telah menyimpan ID perdagangan", + "scan_one_block": "Pindai satu blok", "scan_qr_code": "Scan kode QR untuk mendapatkan alamat", "scan_qr_code_to_get_address": "Pindai kode QR untuk mendapatkan alamat", "scan_qr_on_device": "Pindai kode QR ini di perangkat lain", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 77884037ae..133ef62d5b 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -513,6 +513,7 @@ "save_backup_password_alert": "Salva password Backup", "save_to_downloads": "Salva in Download", "saved_the_trade_id": "Ho salvato l'ID dello scambio", + "scan_one_block": "Scansionare un blocco", "scan_qr_code": "Scansiona il codice QR", "scan_qr_code_to_get_address": "Scansiona il codice QR per ottenere l'indirizzo", "scan_qr_on_device": "Scansiona questo codice QR su un altro dispositivo", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 36932fbc0d..a6191460be 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -512,6 +512,7 @@ "save_backup_password_alert": "バックアップパスワードを保存する", "save_to_downloads": "ダウンロードに保存", "saved_the_trade_id": "取引IDを保存しました", + "scan_one_block": "1つのブロックをスキャンします", "scan_qr_code": "QRコードをスキャン", "scan_qr_code_to_get_address": "QRコードをスキャンして住所を取得します", "scan_qr_on_device": "別のデバイスでこの QR コードをスキャンします", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index eb6f1ce726..9cd30a13f1 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -512,6 +512,7 @@ "save_backup_password_alert": "백업 비밀번호 저장", "save_to_downloads": "다운로드에 저장", "saved_the_trade_id": "거래 ID를 저장했습니다", + "scan_one_block": "하나의 블록을 스캔하십시오", "scan_qr_code": "QR 코드 스캔", "scan_qr_code_to_get_address": "QR 코드를 스캔하여 주소를 얻습니다.", "scan_qr_on_device": "다른 기기에서 이 QR 코드를 스캔하세요.", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 77fa298ebe..ee1fc90c7f 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "အရန်စကားဝှက်ကို သိမ်းဆည်းပါ။", "save_to_downloads": "ဒေါင်းလုဒ်များထံ သိမ်းဆည်းပါ။", "saved_the_trade_id": "ကုန်သွယ်မှု ID ကို သိမ်းဆည်းပြီးပါပြီ။", + "scan_one_block": "တစ်ကွက်ကိုစကင်ဖတ်စစ်ဆေးပါ", "scan_qr_code": "QR ကုဒ်ကို စကင်န်ဖတ်ပါ။", "scan_qr_code_to_get_address": "လိပ်စာရယူရန် QR ကုဒ်ကို စကင်န်ဖတ်ပါ။", "scan_qr_on_device": "အခြားစက်တွင် ဤ QR ကုဒ်ကို စကင်ဖတ်ပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 4a60a994e9..ae7a32dbb6 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "Bewaar back-upwachtwoord", "save_to_downloads": "Opslaan in downloads", "saved_the_trade_id": "Ik heb de ruil-ID opgeslagen", + "scan_one_block": "Scan een blok", "scan_qr_code": "Scan QR-code", "scan_qr_code_to_get_address": "Scan de QR-code om het adres te krijgen", "scan_qr_on_device": "Scan deze QR-code op een ander apparaat", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 54859366dd..7e7cdfb758 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "Zapisz hasło kopii zapasowej", "save_to_downloads": "Zapisz w Pobranych", "saved_the_trade_id": "Zapisałem ID", + "scan_one_block": "Zeskanuj jeden blok", "scan_qr_code": "Skanowania QR code", "scan_qr_code_to_get_address": "Zeskanuj kod QR, aby uzyskać adres", "scan_qr_on_device": "Zeskanuj ten kod QR na innym urządzeniu", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index eb1c8e3d83..1526b6aad2 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -513,6 +513,7 @@ "save_backup_password_alert": "Salvar senha de backup", "save_to_downloads": "Salvar em Downloads", "saved_the_trade_id": "ID da troca salvo", + "scan_one_block": "Escanear um bloco", "scan_qr_code": "Escanear código QR", "scan_qr_code_to_get_address": "Digitalize o código QR para obter o endereço", "scan_qr_on_device": "Digitalize este código QR em outro dispositivo", @@ -624,7 +625,7 @@ "silent_payments": "Pagamentos silenciosos", "silent_payments_disclaimer": "Novos endereços não são novas identidades. É uma reutilização de uma identidade existente com um rótulo diferente.", "silent_payments_scan_from_date": "Escanear a partir da data", - "silent_payments_scan_from_date_or_blockheight": "Por favor, insira a altura do bloco que deseja iniciar o escaneamento para obter pagamentos silenciosos ou use a data. Você pode escolher se a carteira continua digitalizando cada bloco ou verifica apenas a altura especificada.", + "silent_payments_scan_from_date_or_blockheight": "Por favor, insira a altura do bloco que deseja iniciar o escaneamento para obter pagamentos silenciosos ou use a data. Você pode escolher se a carteira continua escaneando cada bloco ou verifica apenas a altura especificada.", "silent_payments_scan_from_height": "Escanear a partir da altura do bloco", "silent_payments_scanning": "Escanear Pagamentos Silenciosos", "slidable": "Deslizável", @@ -823,4 +824,4 @@ "you_will_get": "Converter para", "you_will_send": "Converter de", "yy": "aa" -} \ No newline at end of file +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 946ef093c7..0534f52d77 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -512,6 +512,7 @@ "save_backup_password_alert": "Сохранить пароль резервной копии", "save_to_downloads": "Сохранить в загрузках", "saved_the_trade_id": "Я сохранил ID сделки", + "scan_one_block": "Сканируйте один блок", "scan_qr_code": "Сканировать QR-код", "scan_qr_code_to_get_address": "Отсканируйте QR-код для получения адреса", "scan_qr_on_device": "Отсканируйте этот QR-код на другом устройстве", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 7778dd876e..434940a312 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "บันทึกรหัสผ่านสำรอง", "save_to_downloads": "บันทึกลงดาวน์โหลด", "saved_the_trade_id": "ฉันได้บันทึก ID ของการซื้อขายแล้ว", + "scan_one_block": "สแกนหนึ่งบล็อก", "scan_qr_code": "สแกนรหัส QR", "scan_qr_code_to_get_address": "สแกน QR code เพื่อรับที่อยู่", "scan_qr_on_device": "สแกนโค้ด QR นี้บนอุปกรณ์อื่น", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 4e1dcfa8fe..08be1b7277 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "I -save ang backup password", "save_to_downloads": "I -save sa mga pag -download", "saved_the_trade_id": "Nai -save ko ang trade ID", + "scan_one_block": "I -scan ang isang bloke", "scan_qr_code": "I -scan ang QR Code", "scan_qr_code_to_get_address": "I -scan ang QR code upang makuha ang address", "scan_qr_on_device": "I-scan ang QR code na ito sa ibang device", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 321e2a0e14..24f79222e5 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "Yedek parolasını kaydet", "save_to_downloads": "İndirilenlere Kaydet", "saved_the_trade_id": "Takas ID'imi kaydettim", + "scan_one_block": "Bir bloğu tara", "scan_qr_code": "QR kodunu tarayın", "scan_qr_code_to_get_address": "Adresi getirmek için QR kodunu tara", "scan_qr_on_device": "Bu QR kodunu başka bir cihazda tarayın", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 407d19447b..f3643a64b8 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -512,6 +512,7 @@ "save_backup_password_alert": "Зберегти пароль резервної копії", "save_to_downloads": "Зберегти до завантажень", "saved_the_trade_id": "Я зберіг ID операції", + "scan_one_block": "Сканувати один блок", "scan_qr_code": "Відскануйте QR-код", "scan_qr_code_to_get_address": "Скануйте QR-код для одержання адреси", "scan_qr_on_device": "Відскануйте цей QR-код на іншому пристрої", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 62c5eda8ee..b39e211ef5 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -513,6 +513,7 @@ "save_backup_password_alert": "بیک اپ پاس ورڈ محفوظ کریں۔", "save_to_downloads": "۔ﮟﯾﺮﮐ ﻅﻮﻔﺤﻣ ﮟﯿﻣ ﺯﮈﻮﻟ ﻥﺅﺍﮈ", "saved_the_trade_id": "میں نے تجارتی ID محفوظ کر لی ہے۔", + "scan_one_block": "ایک بلاک اسکین کریں", "scan_qr_code": "پتہ حاصل کرنے کے لیے QR کوڈ اسکین کریں۔", "scan_qr_code_to_get_address": "پتہ حاصل کرنے کے لئے QR کوڈ کو اسکین کریں", "scan_qr_on_device": " ۔ﮟﯾﺮﮐ ﻦﯿﮑﺳﺍ ﺮﭘ ﺲﺋﺍﻮﯾﮈ ﺭﻭﺍ ﯽﺴﮐ ﻮﮐ ﮈﻮﮐ QR ﺱﺍ", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index a90ddd3ae4..ea9caf6370 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -512,6 +512,7 @@ "save_backup_password_alert": "Pamọ́ ọ̀rọ̀ aṣínà ti ẹ̀dà", "save_to_downloads": "Fipamọ si Awọn igbasilẹ", "saved_the_trade_id": "Mo ti pamọ́ àmì ìdánimọ̀ pàṣípààrọ̀", + "scan_one_block": "Ọlọjẹ ọkan bulọki", "scan_qr_code": "Yan QR koodu", "scan_qr_code_to_get_address": "Ṣayẹwo koodu QR naa lati gba adirẹsi naa", "scan_qr_on_device": "Ṣe ayẹwo koodu QR yii lori ẹrọ miiran", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 9d5bd9a191..822dc0b166 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -511,6 +511,7 @@ "save_backup_password_alert": "保存备份密码", "save_to_downloads": "保存到下载", "saved_the_trade_id": "我已经保存了交易编号", + "scan_one_block": "扫描一个街区", "scan_qr_code": "扫描二维码", "scan_qr_code_to_get_address": "扫描二维码获取地址", "scan_qr_on_device": "在另一台设备上扫描此二维码", diff --git a/tool/configure.dart b/tool/configure.dart index b082f64701..9c5f4b5907 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -87,7 +87,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/litecoin_wallet_service.dart'; -import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; +import 'package:cw_core/get_height_by_date.dart'; import 'package:mobx/mobx.dart'; """; const bitcoinCwPart = "part 'cw_bitcoin.dart';"; @@ -167,6 +167,8 @@ abstract class Bitcoin { Future isChangeSufficientForFee(Object wallet, String txId, String newFee); int getFeeAmountForPriority(Object wallet, TransactionPriority priority, int inputsCount, int outputsCount, {int? size}); int getFeeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, {int? size}); + int getHeightByDate({required DateTime date}); + void rescan(Object wallet, {required int height, bool? doSingleScan}); } """; From ddbb63ae462501a33c5a6b3520c2d5c3b4adb357 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 9 Apr 2024 16:37:00 -0300 Subject: [PATCH 026/242] feat: new cake features page replace market page, move sp scan toggle, auto switch node pop up alert --- assets/images/cards.svg | 65 ++++++ lib/bitcoin/cw_bitcoin.dart | 26 ++- lib/di.dart | 11 +- lib/src/screens/dashboard/dashboard_page.dart | 10 +- .../desktop_dashboard_actions.dart | 8 +- .../screens/dashboard/pages/balance_page.dart | 44 ---- .../dashboard/pages/cake_features_page.dart | 204 ++++++++++++++++++ .../dashboard/pages/market_place_page.dart | 105 --------- lib/src/screens/rescan/rescan_page.dart | 32 +++ lib/src/widgets/dashboard_card_widget.dart | 63 ++++-- .../dashboard/cake_features_view_model.dart | 16 ++ .../dashboard/dashboard_view_model.dart | 2 +- .../dashboard/market_place_view_model.dart | 17 -- lib/view_model/rescan_view_model.dart | 17 +- res/values/strings_ar.arb | 4 + res/values/strings_bg.arb | 4 + res/values/strings_cs.arb | 4 + res/values/strings_de.arb | 4 + res/values/strings_en.arb | 4 + res/values/strings_es.arb | 4 + res/values/strings_fr.arb | 4 + res/values/strings_ha.arb | 4 + res/values/strings_hi.arb | 4 + res/values/strings_hr.arb | 4 + res/values/strings_id.arb | 4 + res/values/strings_it.arb | 4 + res/values/strings_ja.arb | 4 + res/values/strings_ko.arb | 4 + res/values/strings_my.arb | 4 + res/values/strings_nl.arb | 4 + res/values/strings_pl.arb | 4 + res/values/strings_pt.arb | 4 + res/values/strings_ru.arb | 4 + res/values/strings_th.arb | 4 + res/values/strings_tl.arb | 4 + res/values/strings_tr.arb | 4 + res/values/strings_uk.arb | 4 + res/values/strings_ur.arb | 4 + res/values/strings_yo.arb | 4 + res/values/strings_zh.arb | 4 + tool/configure.dart | 1 + 41 files changed, 516 insertions(+), 209 deletions(-) create mode 100644 assets/images/cards.svg create mode 100644 lib/src/screens/dashboard/pages/cake_features_page.dart delete mode 100644 lib/src/screens/dashboard/pages/market_place_page.dart create mode 100644 lib/view_model/dashboard/cake_features_view_model.dart delete mode 100644 lib/view_model/dashboard/market_place_view_model.dart diff --git a/assets/images/cards.svg b/assets/images/cards.svg new file mode 100644 index 0000000000..699f9d3110 --- /dev/null +++ b/assets/images/cards.svg @@ -0,0 +1,65 @@ + + + + diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 49fdcedab6..db1ebcf4ab 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -326,8 +326,15 @@ class CWBitcoin extends Bitcoin { return bitcoinWallet.silentPaymentsScanningActive; } - void setScanningActive(Object wallet, bool active) { + Future setScanningActive(Object wallet, SettingsStore settingsStore, bool active) async { final bitcoinWallet = wallet as ElectrumWallet; + // TODO: always when setting to scanning active, will force switch nodes. Remove when not needed anymore + if (!getNodeIsCakeElectrs(wallet)) { + final node = Node(useSSL: false, uri: '198.58.111.154:50002'); + node.type = WalletType.bitcoin; + settingsStore.nodes[WalletType.bitcoin] = node; + await bitcoinWallet.connectToNode(node: node); + } bitcoinWallet.setSilentPaymentsScanning(active); } @@ -339,8 +346,23 @@ class CWBitcoin extends Bitcoin { @override int getHeightByDate({required DateTime date}) => getBitcoinHeightByDate(date: date); - void rescan(Object wallet, {required int height, bool? doSingleScan}) { + Future rescan(Object wallet, SettingsStore settingsStore, + {required int height, bool? doSingleScan}) async { final bitcoinWallet = wallet as ElectrumWallet; + // TODO: always when setting to scanning active, will force switch nodes. Remove when not needed anymore + if (!getNodeIsCakeElectrs(wallet)) { + final node = Node(useSSL: false, uri: '198.58.111.154:50002'); + node.type = WalletType.bitcoin; + settingsStore.nodes[WalletType.bitcoin] = node; + await bitcoinWallet.connectToNode(node: node); + } bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan); } + + bool getNodeIsCakeElectrs(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + final node = bitcoinWallet.node; + + return node?.uri.host == '198.58.111.154' && node?.uri.port == 50002; + } } diff --git a/lib/di.dart b/lib/di.dart index 59a94d1eb1..cf5b3f8dcc 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -69,7 +69,7 @@ import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart'; import 'package:cake_wallet/view_model/anonpay_details_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart'; -import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; @@ -890,7 +890,8 @@ Future setup({ (onSuccessfulPinSetup, _) => SetupPinCodePage(getIt.get(), onSuccessfulPinSetup: onSuccessfulPinSetup)); - getIt.registerFactory(() => RescanViewModel(getIt.get().wallet!)); + getIt.registerFactory( + () => RescanViewModel(getIt.get().wallet!, getIt.get())); getIt.registerFactory(() => RescanPage(getIt.get())); @@ -1048,7 +1049,7 @@ Future setup({ getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get())); - getIt.registerFactory(() => MarketPlaceViewModel(getIt.get())); + getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get())); getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get())); @@ -1145,9 +1146,9 @@ Future setup({ getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get())); getIt.registerFactoryParam( - (TransactionInfo transactionInfo, _) => RBFDetailsPage( + (TransactionInfo transactionInfo, _) => RBFDetailsPage( transactionDetailsViewModel: - getIt.get(param1: transactionInfo))); + getIt.get(param1: transactionInfo))); getIt.registerFactory(() => AnonPayApi( useTorOnly: getIt.get().exchangeStatus == ExchangeApiMode.torOnly, diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index ed06f47040..2c72995a9c 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/main_actions.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart'; -import 'package:cake_wallet/src/screens/dashboard/pages/market_place_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart'; import 'package:cake_wallet/src/widgets/gradient_background.dart'; import 'package:cake_wallet/src/widgets/services_updates_widget.dart'; @@ -12,7 +12,7 @@ import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart'; import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/version_comparator.dart'; -import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/yat_emoji_id.dart'; @@ -291,10 +291,10 @@ class _DashboardPageView extends BasePage { if (dashboardViewModel.shouldShowMarketPlaceInDashboard) { pages.add( Semantics( - label: S.of(context).market_place, - child: MarketPlacePage( + label: 'Cake ${S.of(context).features}', + child: CakeFeaturesPage( dashboardViewModel: dashboardViewModel, - marketPlaceViewModel: getIt.get(), + cakeFeaturesViewModel: getIt.get(), ), ), ); diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart index 20ddea361e..d36c06013f 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart @@ -1,9 +1,9 @@ import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/main_actions.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_action_button.dart'; -import 'package:cake_wallet/src/screens/dashboard/pages/market_place_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -74,9 +74,9 @@ class DesktopDashboardActions extends StatelessWidget { ], ), Expanded( - child: MarketPlacePage( + child: CakeFeaturesPage( dashboardViewModel: dashboardViewModel, - marketPlaceViewModel: getIt.get(), + cakeFeaturesViewModel: getIt.get(), ), ), ], diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index 2c77ffceaa..312cf3240d 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -209,13 +209,6 @@ class CryptoBalanceWidget extends StatelessWidget { currency: balance.asset, hasAdditionalBalance: dashboardViewModel.balanceViewModel.hasAdditionalBalance, - hasSilentPayments: dashboardViewModel.balanceViewModel.hasSilentPayments, - silentPaymentsScanningActive: - dashboardViewModel.silentPaymentsScanningActive, - setSilentPaymentsScanning: () => - dashboardViewModel.setSilentPaymentsScanning( - !dashboardViewModel.silentPaymentsScanningActive, - ), isTestnet: dashboardViewModel.isTestnet, ); }); @@ -242,9 +235,6 @@ class BalanceRowWidget extends StatelessWidget { required this.frozenFiatBalance, required this.currency, required this.hasAdditionalBalance, - required this.hasSilentPayments, - required this.silentPaymentsScanningActive, - required this.setSilentPaymentsScanning, required this.isTestnet, super.key, }); @@ -259,10 +249,7 @@ class BalanceRowWidget extends StatelessWidget { final String frozenFiatBalance; final CryptoCurrency currency; final bool hasAdditionalBalance; - final bool hasSilentPayments; - final bool silentPaymentsScanningActive; final bool isTestnet; - final void Function() setSilentPaymentsScanning; // void _showBalanceDescription(BuildContext context) { // showPopUp( @@ -505,37 +492,6 @@ class BalanceRowWidget extends StatelessWidget { ), ], ), - if (hasSilentPayments) ...[ - Padding( - padding: const EdgeInsets.only(right: 8, top: 8), - child: Divider( - color: Theme.of(context).extension()!.labelTextColor, - thickness: 1, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AutoSizeText( - S.of(context).silent_payments_scanning, - style: TextStyle( - fontSize: 14, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context).extension()!.assetTitleColor, - height: 1, - ), - maxLines: 1, - textAlign: TextAlign.center, - ), - Padding( - padding: const EdgeInsets.only(right: 8), - child: StandardSwitch( - value: silentPaymentsScanningActive, onTaped: setSilentPaymentsScanning), - ) - ], - ), - ] ], ), ), diff --git a/lib/src/screens/dashboard/pages/cake_features_page.dart b/lib/src/screens/dashboard/pages/cake_features_page.dart new file mode 100644 index 0000000000..aa587a5f40 --- /dev/null +++ b/lib/src/screens/dashboard/pages/cake_features_page.dart @@ -0,0 +1,204 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; +import 'package:cake_wallet/src/widgets/standard_switch.dart'; +import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class CakeFeaturesPage extends StatelessWidget { + CakeFeaturesPage({ + required this.dashboardViewModel, + required this.cakeFeaturesViewModel, + }); + + final DashboardViewModel dashboardViewModel; + final CakeFeaturesViewModel cakeFeaturesViewModel; + final _scrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: RawScrollbar( + thumbColor: Colors.white.withOpacity(0.15), + radius: Radius.circular(20), + thumbVisibility: true, + thickness: 2, + controller: _scrollController, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 50), + Text( + 'Cake ${S.of(context).features}', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.pageTitleTextColor, + ), + ), + Expanded( + child: ListView( + controller: _scrollController, + children: [ + // SizedBox(height: 20), + // DashBoardRoundedCardWidget( + // onTap: () => launchUrl( + // Uri.parse("https://cakelabs.com/news/cake-pay-mobile-to-shut-down/"), + // mode: LaunchMode.externalApplication, + // ), + // title: S.of(context).cake_pay_title, + // subTitle: S.of(context).cake_pay_subtitle, + // ), + SizedBox(height: 20), + DashBoardRoundedCardWidget( + onTap: () => launchUrl( + Uri.https("buy.cakepay.com"), + mode: LaunchMode.externalApplication, + ), + title: S.of(context).cake_pay_web_cards_title, + subTitle: S.of(context).cake_pay_web_cards_subtitle, + svgPicture: SvgPicture.asset( + 'assets/images/cards.svg', + height: 125, + width: 125, + fit: BoxFit.cover, + ), + ), + if (dashboardViewModel.hasSilentPayments) ...[ + SizedBox(height: 10), + DashBoardRoundedCardWidget( + title: S.of(context).silent_payments, + subTitle: S.of(context).enable_silent_payments_scanning, + hint: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => launchUrl( + // TODO: Update URL + Uri.https("guides.cakewallet.com"), + mode: LaunchMode.externalApplication, + ), + child: Row( + children: [ + Text( + S.of(context).what_is_silent_payments, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .labelTextColor, + height: 1, + ), + softWrap: true, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Icon(Icons.help_outline, + size: 16, + color: Theme.of(context) + .extension()! + .labelTextColor), + ) + ], + ), + ), + Observer( + builder: (_) => StandardSwitch( + value: dashboardViewModel.silentPaymentsScanningActive, + onTaped: () => _toggleSilentPaymentsScanning(context), + ), + ) + ], + ), + ], + ), + onTap: () => _toggleSilentPaymentsScanning(context), + icon: Icon( + Icons.lock, + color: + Theme.of(context).extension()!.pageTitleTextColor, + size: 50, + ), + ), + ] + ], + ), + ), + ], + ), + ), + ), + ); + } + + // TODO: Remove ionia flow/files if we will discard it + void _navigatorToGiftCardsPage(BuildContext context) { + final walletType = dashboardViewModel.type; + + switch (walletType) { + case WalletType.haven: + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: S.of(context).gift_cards_unavailable, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + break; + default: + cakeFeaturesViewModel.isIoniaUserAuthenticated().then((value) { + if (value) { + Navigator.pushNamed(context, Routes.ioniaManageCardsPage); + return; + } + Navigator.of(context).pushNamed(Routes.ioniaWelcomePage); + }); + } + } + + Future _toggleSilentPaymentsScanning(BuildContext context) async { + final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive; + final newValue = !isSilentPaymentsScanningActive; + + final needsToSwitch = bitcoin!.getNodeIsCakeElectrs(dashboardViewModel.wallet) == false; + + if (needsToSwitch) { + return showPopUp( + context: context, + builder: (BuildContext context) => AlertWithTwoActions( + alertTitle: S.of(context).change_current_node_title, + alertContent: S.of(context).confirm_silent_payments_switch_node, + rightButtonText: S.of(context).ok, + leftButtonText: S.of(context).cancel, + actionRightButton: () { + dashboardViewModel.setSilentPaymentsScanning(newValue); + Navigator.of(context).pop(); + }, + actionLeftButton: () => Navigator.of(context).pop(), + )); + } + + return dashboardViewModel.setSilentPaymentsScanning(newValue); + } +} diff --git a/lib/src/screens/dashboard/pages/market_place_page.dart b/lib/src/screens/dashboard/pages/market_place_page.dart deleted file mode 100644 index 1bdcb61b4f..0000000000 --- a/lib/src/screens/dashboard/pages/market_place_page.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/material.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; - -class MarketPlacePage extends StatelessWidget { - MarketPlacePage({ - required this.dashboardViewModel, - required this.marketPlaceViewModel, - }); - - final DashboardViewModel dashboardViewModel; - final MarketPlaceViewModel marketPlaceViewModel; - final _scrollController = ScrollController(); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: RawScrollbar( - thumbColor: Colors.white.withOpacity(0.15), - radius: Radius.circular(20), - thumbVisibility: true, - thickness: 2, - controller: _scrollController, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 50), - Text( - S.of(context).market_place, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.pageTitleTextColor, - ), - ), - Expanded( - child: ListView( - controller: _scrollController, - children: [ - // SizedBox(height: 20), - // DashBoardRoundedCardWidget( - // onTap: () => launchUrl( - // Uri.parse("https://cakelabs.com/news/cake-pay-mobile-to-shut-down/"), - // mode: LaunchMode.externalApplication, - // ), - // title: S.of(context).cake_pay_title, - // subTitle: S.of(context).cake_pay_subtitle, - // ), - SizedBox(height: 20), - DashBoardRoundedCardWidget( - onTap: () => launchUrl( - Uri.https("buy.cakepay.com"), - mode: LaunchMode.externalApplication, - ), - title: S.of(context).cake_pay_web_cards_title, - subTitle: S.of(context).cake_pay_web_cards_subtitle, - ), - ], - ), - ), - ], - ), - ), - ), - ); - } - - // TODO: Remove ionia flow/files if we will discard it - void _navigatorToGiftCardsPage(BuildContext context) { - final walletType = dashboardViewModel.type; - - switch (walletType) { - case WalletType.haven: - showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).error, - alertContent: S.of(context).gift_cards_unavailable, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); - break; - default: - marketPlaceViewModel.isIoniaUserAuthenticated().then((value) { - if (value) { - Navigator.pushNamed(context, Routes.ioniaManageCardsPage); - return; - } - Navigator.of(context).pushNamed(Routes.ioniaWelcomePage); - }); - } - } -} diff --git a/lib/src/screens/rescan/rescan_page.dart b/lib/src/screens/rescan/rescan_page.dart index da0f351378..b633a127ea 100644 --- a/lib/src/screens/rescan/rescan_page.dart +++ b/lib/src/screens/rescan/rescan_page.dart @@ -1,3 +1,6 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/view_model/rescan_view_model.dart'; @@ -35,6 +38,10 @@ class RescanPage extends BasePage { isLoading: _rescanViewModel.state == RescanWalletState.rescaning, text: S.of(context).rescan, onPressed: () async { + if (_rescanViewModel.isSilentPaymentsScan) { + return _toggleSilentPaymentsScanning(context); + } + await _rescanViewModel.rescanCurrentWallet( restoreHeight: _blockchainHeightWidgetKey.currentState!.height); Navigator.of(context).pop(); @@ -46,4 +53,29 @@ class RescanPage extends BasePage { ]), ); } + + Future _toggleSilentPaymentsScanning(BuildContext context) async { + final needsToSwitch = bitcoin!.getNodeIsCakeElectrs(_rescanViewModel.wallet) == false; + + if (needsToSwitch) { + return showPopUp( + context: context, + builder: (BuildContext context) => AlertWithTwoActions( + alertTitle: S.of(context).change_current_node_title, + alertContent: S.of(context).confirm_silent_payments_switch_node, + rightButtonText: S.of(context).ok, + leftButtonText: S.of(context).cancel, + actionRightButton: () async { + _rescanViewModel.rescanCurrentWallet( + restoreHeight: _blockchainHeightWidgetKey.currentState!.height); + Navigator.of(context).pop(); + }, + actionLeftButton: () => Navigator.of(context).pop(), + )); + } + + await _rescanViewModel.rescanCurrentWallet( + restoreHeight: _blockchainHeightWidgetKey.currentState!.height); + Navigator.of(context).pop(); + } } diff --git a/lib/src/widgets/dashboard_card_widget.dart b/lib/src/widgets/dashboard_card_widget.dart index b3f92123a4..eb865b63b4 100644 --- a/lib/src/widgets/dashboard_card_widget.dart +++ b/lib/src/widgets/dashboard_card_widget.dart @@ -2,19 +2,24 @@ import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; +import 'package:flutter_svg/flutter_svg.dart'; class DashBoardRoundedCardWidget extends StatelessWidget { - - DashBoardRoundedCardWidget({ required this.onTap, required this.title, required this.subTitle, + this.hint, + this.svgPicture, + this.icon, }); final VoidCallback onTap; final String title; final String subTitle; + final Widget? hint; + final SvgPicture? svgPicture; + final Icon? icon; @override Widget build(BuildContext context) { @@ -35,32 +40,52 @@ class DashBoardRoundedCardWidget extends StatelessWidget { color: Theme.of(context).extension()!.cardBorderColor, ), ), - child: - Column( + child: Column( + children: [ + Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title, - style: TextStyle( - color: Theme.of(context).extension()!.cardTextColor, - fontSize: 24, - fontWeight: FontWeight.w900, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + color: + Theme.of(context).extension()!.cardTextColor, + fontSize: 24, + fontWeight: FontWeight.w900, + ), + softWrap: true, + ), + SizedBox(height: 5), + Text( + subTitle, + style: TextStyle( + color: Theme.of(context) + .extension()! + .cardTextColor, + fontWeight: FontWeight.w500, + fontFamily: 'Lato'), + softWrap: true, + ), + ], ), ), - SizedBox(height: 5), - Text( - subTitle, - style: TextStyle( - color: Theme.of(context).extension()!.cardTextColor, - fontWeight: FontWeight.w500, - fontFamily: 'Lato'), - ) + if (svgPicture != null) svgPicture!, + if (icon != null) icon! ], ), + if (hint != null) ...[ + SizedBox(height: 10), + hint!, + ] + ], + ), ), ], ), ); } } - diff --git a/lib/view_model/dashboard/cake_features_view_model.dart b/lib/view_model/dashboard/cake_features_view_model.dart new file mode 100644 index 0000000000..0a8fbc6408 --- /dev/null +++ b/lib/view_model/dashboard/cake_features_view_model.dart @@ -0,0 +1,16 @@ +import 'package:cake_wallet/ionia/ionia_service.dart'; +import 'package:mobx/mobx.dart'; + +part 'cake_features_view_model.g.dart'; + +class CakeFeaturesViewModel = CakeFeaturesViewModelBase with _$CakeFeaturesViewModel; + +abstract class CakeFeaturesViewModelBase with Store { + final IoniaService _ioniaService; + + CakeFeaturesViewModelBase(this._ioniaService); + + Future isIoniaUserAuthenticated() async { + return await _ioniaService.isLogined(); + } +} diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index b4cd026fec..c90e51cd19 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -318,7 +318,7 @@ abstract class DashboardViewModelBase with Store { silentPaymentsScanningActive = active; if (hasSilentPayments) { - bitcoin!.setScanningActive(wallet, active); + bitcoin!.setScanningActive(wallet, settingsStore, active); } } diff --git a/lib/view_model/dashboard/market_place_view_model.dart b/lib/view_model/dashboard/market_place_view_model.dart deleted file mode 100644 index 4700411277..0000000000 --- a/lib/view_model/dashboard/market_place_view_model.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:cake_wallet/ionia/ionia_service.dart'; -import 'package:mobx/mobx.dart'; - -part 'market_place_view_model.g.dart'; - -class MarketPlaceViewModel = MarketPlaceViewModelBase with _$MarketPlaceViewModel; - -abstract class MarketPlaceViewModelBase with Store { - final IoniaService _ioniaService; - - MarketPlaceViewModelBase(this._ioniaService); - - - Future isIoniaUserAuthenticated() async { - return await _ioniaService.isLogined(); - } -} \ No newline at end of file diff --git a/lib/view_model/rescan_view_model.dart b/lib/view_model/rescan_view_model.dart index ae7baf0083..4008ee9f1a 100644 --- a/lib/view_model/rescan_view_model.dart +++ b/lib/view_model/rescan_view_model.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -10,12 +11,14 @@ class RescanViewModel = RescanViewModelBase with _$RescanViewModel; enum RescanWalletState { rescaning, none } abstract class RescanViewModelBase with Store { - RescanViewModelBase(this._wallet) + RescanViewModelBase(this.wallet, this.settingsStore) : state = RescanWalletState.none, isButtonEnabled = false, doSingleScan = false; - final WalletBase _wallet; + final WalletBase wallet; + + final SettingsStore settingsStore; @observable RescanWalletState state; @@ -27,16 +30,16 @@ abstract class RescanViewModelBase with Store { bool doSingleScan; @computed - bool get isSilentPaymentsScan => _wallet.type == WalletType.bitcoin; + bool get isSilentPaymentsScan => wallet.type == WalletType.bitcoin; @action Future rescanCurrentWallet({required int restoreHeight}) async { state = RescanWalletState.rescaning; - if (_wallet.type != WalletType.bitcoin) { - _wallet.rescan(height: restoreHeight); - _wallet.transactionHistory.clear(); + if (wallet.type != WalletType.bitcoin) { + wallet.rescan(height: restoreHeight); + wallet.transactionHistory.clear(); } else { - bitcoin!.rescan(_wallet, height: restoreHeight, doSingleScan: doSingleScan); + bitcoin!.rescan(wallet, settingsStore, height: restoreHeight, doSingleScan: doSingleScan); } state = RescanWalletState.none; } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 44df63a3ab..03f91bf1cd 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "تأكيد خصم الرسوم", "confirm_fee_deduction_content": "هل توافق على خصم الرسوم من الإخراج؟", "confirm_sending": "تأكيد الإرسال", + "confirm_silent_payments_switch_node": "حاليا مطلوب لتبديل العقد لمسح المدفوعات الصامتة", "confirmations": "التأكيدات", "confirmed": "رصيد مؤكد", "confirmed_tx": "مؤكد", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "نقوم بإنشاء عناوين جديدة في كل مرة تستخدم فيها عنوانًا ، لكن العناوين السابقة تستمر في العمل", "email_address": "عنوان البريد الالكترونى", "enable_replace_by_fee": "تمكين الاستبدال", + "enable_silent_payments_scanning": "تمكين المسح الضوئي للمدفوعات الصامتة", "enabled": "ممكنة", "enter_amount": "أدخل المبلغ", "enter_backup_password": "أدخل كلمة المرور الاحتياطية هنا", @@ -275,6 +277,7 @@ "extracted_address_content": "سوف ترسل الأموال إلى\n${recipient_name}", "failed_authentication": "${state_error} فشل المصادقة.", "faq": "الأسئلة الشائعة", + "features": "سمات", "fetching": "جار الجلب", "fiat_api": "Fiat API", "fiat_balance": "الرصيد فيات", @@ -793,6 +796,7 @@ "warning": "تحذير", "welcome": "مرحبا بك في", "welcome_to_cakepay": "مرحبا بكم في Cake Pay!", + "what_is_silent_payments": "ما هي المدفوعات الصامتة؟", "widgets_address": "عنوان", "widgets_or": "أو", "widgets_restore_from_blockheight": "استعادة من ارتفاع البلوك", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 185e22e0b6..4125969951 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Потвърдете приспадането на таксите", "confirm_fee_deduction_content": "Съгласни ли сте да приспадате таксата от продукцията?", "confirm_sending": "Потвърждаване на изпращането", + "confirm_silent_payments_switch_node": "Понастоящем се изисква да превключвате възлите за сканиране на мълчаливи плащания", "confirmations": "потвърждения", "confirmed": "Потвърден баланс", "confirmed_tx": "Потвърдено", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Нови адреси се генерират всеки път, когато използвате този, но и предишните продължават да работят", "email_address": "Имейл адрес", "enable_replace_by_fee": "Активиране на замяна по забрана", + "enable_silent_payments_scanning": "Активирайте безшумните плащания за сканиране", "enabled": "Активирано", "enter_amount": "Въведете сума", "enter_backup_password": "Въведете парола за възстановяване", @@ -275,6 +277,7 @@ "extracted_address_content": "Ще изпратите средства на \n${recipient_name}", "failed_authentication": "Неуспешно удостоверяване. ${state_error}", "faq": "FAQ", + "features": "Характеристика", "fetching": "Обработване", "fiat_api": "Fiat API", "fiat_balance": "Фиат Баланс", @@ -793,6 +796,7 @@ "warning": "Внимание", "welcome": "Добре дошли в", "welcome_to_cakepay": "Добре дошли в Cake Pay!", + "what_is_silent_payments": "Какво са мълчаливи плащания?", "widgets_address": "Адрес", "widgets_or": "или", "widgets_restore_from_blockheight": "Възстановяване от blockheight", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 2146ae7251..c063691f1a 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Potvrďte odpočet poplatků", "confirm_fee_deduction_content": "Souhlasíte s odečtením poplatku z výstupu?", "confirm_sending": "Potvrdit odeslání", + "confirm_silent_payments_switch_node": "V současné době je nutné přepínat uzly pro skenování tichých plateb", "confirmations": "Potvrzení", "confirmed": "Potvrzený zůstatek", "confirmed_tx": "Potvrzeno", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Po každém použití je generována nová adresa, ale předchozí adresy také stále fungují", "email_address": "E-mailová adresa", "enable_replace_by_fee": "Povolit výměnu podle poplatku", + "enable_silent_payments_scanning": "Povolte skenování tichých plateb", "enabled": "Povoleno", "enter_amount": "Zadejte částku", "enter_backup_password": "Zde zadejte své heslo pro zálohy", @@ -275,6 +277,7 @@ "extracted_address_content": "Prostředky budete posílat na\n${recipient_name}", "failed_authentication": "Ověřování selhalo. ${state_error}", "faq": "FAQ", + "features": "Funkce", "fetching": "Načítá se", "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", @@ -793,6 +796,7 @@ "warning": "Varování", "welcome": "Vítejte v", "welcome_to_cakepay": "Vítejte v Cake Pay!", + "what_is_silent_payments": "Co jsou tiché platby?", "widgets_address": "Adresa", "widgets_or": "nebo", "widgets_restore_from_blockheight": "Obnovit z výšky bloku", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index f4b6cd0df5..4f29258d1d 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Gebührenabzug bestätigen", "confirm_fee_deduction_content": "Stimmen Sie zu, die Gebühr von der Ausgabe abzuziehen?", "confirm_sending": "Senden bestätigen", + "confirm_silent_payments_switch_node": "Derzeit ist es erforderlich, Knoten zu wechseln, um stille Zahlungen zu scannen", "confirmations": "Bestätigungen", "confirmed": "Bestätigter Saldo", "confirmed_tx": "Bestätigt", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin", "email_address": "E-Mail-Adresse", "enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee", + "enable_silent_payments_scanning": "Aktivieren Sie stille Zahlungen Scannen", "enabled": "Ermöglicht", "enter_amount": "Betrag eingeben", "enter_backup_password": "Sicherungskennwort hier eingeben", @@ -275,6 +277,7 @@ "extracted_address_content": "Sie senden Geld an\n${recipient_name}", "failed_authentication": "Authentifizierung fehlgeschlagen. ${state_error}", "faq": "Häufig gestellte Fragen", + "features": "Merkmale", "fetching": "Frage ab", "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", @@ -796,6 +799,7 @@ "warning": "Warnung", "welcome": "Willkommen bei", "welcome_to_cakepay": "Willkommen bei Cake Pay!", + "what_is_silent_payments": "Was sind stille Zahlungen?", "widgets_address": "Adresse", "widgets_or": "oder", "widgets_restore_from_blockheight": "Ab Blockhöhe wiederherstellen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 73b2bde1cc..677e3e50f8 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Confirm Fee Deduction", "confirm_fee_deduction_content": "Do you agree to deduct the fee from the output?", "confirm_sending": "Confirm sending", + "confirm_silent_payments_switch_node": "Currently it is required to switch nodes to scan silent payments", "confirmations": "Confirmations", "confirmed": "Confirmed Balance", "confirmed_tx": "Confirmed", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work", "email_address": "Email Address", "enable_replace_by_fee": "Enable Replace-By-Fee", + "enable_silent_payments_scanning": "Enable silent payments scanning", "enabled": "Enabled", "enter_amount": "Enter Amount", "enter_backup_password": "Enter backup password here", @@ -275,6 +277,7 @@ "extracted_address_content": "You will be sending funds to\n${recipient_name}", "failed_authentication": "Failed authentication. ${state_error}", "faq": "FAQ", + "features": "Features", "fetching": "Fetching", "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", @@ -793,6 +796,7 @@ "warning": "Warning", "welcome": "Welcome to", "welcome_to_cakepay": "Welcome to Cake Pay!", + "what_is_silent_payments": "What is silent payments?", "widgets_address": "Address", "widgets_or": "or", "widgets_restore_from_blockheight": "Restore from blockheight", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 03c4775689..3c47d2c40a 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Confirmar la deducción de la tarifa", "confirm_fee_deduction_content": "¿Acepta deducir la tarifa de la producción?", "confirm_sending": "Confirmar envío", + "confirm_silent_payments_switch_node": "Actualmente se requiere cambiar los nodos para escanear pagos silenciosos", "confirmations": "Confirmaciones", "confirmed": "Saldo confirmado", "confirmed_tx": "Confirmado", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando", "email_address": "Dirección de correo electrónico", "enable_replace_by_fee": "Habilitar reemplazar por tarea", + "enable_silent_payments_scanning": "Habilitar escaneo de pagos silenciosos", "enabled": "Activado", "enter_amount": "Ingrese la cantidad", "enter_backup_password": "Ingrese la contraseña de respaldo aquí", @@ -275,6 +277,7 @@ "extracted_address_content": "Enviará fondos a\n${recipient_name}", "failed_authentication": "Autenticación fallida. ${state_error}", "faq": "FAQ", + "features": "Características", "fetching": "Cargando", "fiat_api": "Fiat API", "fiat_balance": "Equilibrio Fiat", @@ -794,6 +797,7 @@ "warning": "Advertencia", "welcome": "Bienvenido", "welcome_to_cakepay": "¡Bienvenido a Cake Pay!", + "what_is_silent_payments": "¿Qué son los pagos silenciosos?", "widgets_address": "Dirección", "widgets_or": "o", "widgets_restore_from_blockheight": "Restaurar desde blockheight", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 583fdd489e..594ca09ab6 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Confirmer la déduction des frais", "confirm_fee_deduction_content": "Acceptez-vous de déduire les frais de la production?", "confirm_sending": "Confirmer l'envoi", + "confirm_silent_payments_switch_node": "Actuellement, il est nécessaire de changer de nœuds pour scanner les paiements silencieux", "confirmations": "Confirmations", "confirmed": "Solde confirmé", "confirmed_tx": "Confirmé", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner", "email_address": "Adresse e-mail", "enable_replace_by_fee": "Activer Remplace-by-Fee", + "enable_silent_payments_scanning": "Activer la numérisation des paiements silencieux", "enabled": "Activé", "enter_amount": "Entrez le montant", "enter_backup_password": "Entrez le mot de passe de sauvegarde ici", @@ -275,6 +277,7 @@ "extracted_address_content": "Vous allez envoyer des fonds à\n${recipient_name}", "failed_authentication": "Échec d'authentification. ${state_error}", "faq": "FAQ", + "features": "Caractéristiques", "fetching": "Récupération", "fiat_api": "Fiat API", "fiat_balance": "Solde fiat", @@ -793,6 +796,7 @@ "warning": "Avertissement", "welcome": "Bienvenue sur", "welcome_to_cakepay": "Bienvenue sur Cake Pay !", + "what_is_silent_payments": "Qu'est-ce que les paiements silencieux?", "widgets_address": "Adresse", "widgets_or": "ou", "widgets_restore_from_blockheight": "Restaurer depuis une hauteur de bloc", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 4c153ee02f..3ada6e5281 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Tabbatar da cire kudade", "confirm_fee_deduction_content": "Shin kun yarda ku cire kuɗin daga fitarwa?", "confirm_sending": "Tabbatar da aikawa", + "confirm_silent_payments_switch_node": "A halin yanzu ana buƙatar sauya nodes don bincika biyan siliki", "confirmations": "Tabbatar", "confirmed": "An tabbatar", "confirmed_tx": "Tabbatar", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Muna samar da sababbin adireshi duk lokacin da kuka yi amfani da ɗaya, amma adiresoshin da suka gabata suna ci gaba da aiki", "email_address": "Adireshin i-mel", "enable_replace_by_fee": "Ba da damar maye gurbin-by-kudin", + "enable_silent_payments_scanning": "Kunna biya biya", "enabled": "An kunna", "enter_amount": "Shigar da Adadi", "enter_backup_password": "Shigar da kalmar wucewa ta madadin nan", @@ -275,6 +277,7 @@ "extracted_address_content": "Za ku aika da kudade zuwa\n${recipient_name}", "failed_authentication": "Binne wajen shiga. ${state_error}", "faq": "FAQ", + "features": "Fasas", "fetching": "Daukewa", "fiat_api": "API ɗin Fiat", "fiat_balance": "Fiat Balance", @@ -795,6 +798,7 @@ "warning": "Gargadi", "welcome": "Barka da zuwa", "welcome_to_cakepay": "Barka da zuwa Cake Pay!", + "what_is_silent_payments": "Menene biyan shiru?", "widgets_address": "Adireshin", "widgets_or": "ko", "widgets_restore_from_blockheight": "Sake dawo da daga blockheight", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index bbf9749158..979aaeb257 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "शुल्क कटौती की पुष्टि करें", "confirm_fee_deduction_content": "क्या आप आउटपुट से शुल्क में कटौती करने के लिए सहमत हैं?", "confirm_sending": "भेजने की पुष्टि करें", + "confirm_silent_payments_switch_node": "वर्तमान में मूक भुगतान को स्कैन करने के लिए नोड्स को स्विच करना आवश्यक है", "confirmations": "पुष्टिकरण", "confirmed": "पुष्टि की गई शेष राशिी", "confirmed_tx": "की पुष्टि", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं", "email_address": "ईमेल पता", "enable_replace_by_fee": "प्रतिस्थापित-दर-शुल्क सक्षम करें", + "enable_silent_payments_scanning": "मूक भुगतान स्कैनिंग सक्षम करें", "enabled": "सक्रिय", "enter_amount": "राशि दर्ज करें", "enter_backup_password": "यहां बैकअप पासवर्ड डालें", @@ -275,6 +277,7 @@ "extracted_address_content": "आपको धनराशि भेजी जाएगी\n${recipient_name}", "failed_authentication": "प्रमाणीकरण विफल. ${state_error}", "faq": "FAQ", + "features": "विशेषताएँ", "fetching": "ला रहा है", "fiat_api": "फिएट पैसे API", "fiat_balance": "फिएट बैलेंस", @@ -795,6 +798,7 @@ "warning": "चेतावनी", "welcome": "स्वागत हे सेवा मेरे", "welcome_to_cakepay": "केकपे में आपका स्वागत है!", + "what_is_silent_payments": "मूक भुगतान क्या है?", "widgets_address": "पता", "widgets_or": "या", "widgets_restore_from_blockheight": "ब्लॉकचेन से पुनर्स्थापित करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 04ac5acd4e..5eca97a90c 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Potvrdite odbitak naknade", "confirm_fee_deduction_content": "Slažete li se da ćete odbiti naknadu od izlaza?", "confirm_sending": "Potvrdi slanje", + "confirm_silent_payments_switch_node": "Trenutno je potrebno prebaciti čvorove na skeniranje tihih plaćanja", "confirmations": "Potvrde", "confirmed": "Potvrđeno stanje", "confirmed_tx": "Potvrđen", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek", "email_address": "Adresa e-pošte", "enable_replace_by_fee": "Omogući zamjenu", + "enable_silent_payments_scanning": "Omogući skeniranje tihih plaćanja", "enabled": "Omogućeno", "enter_amount": "Unesite iznos", "enter_backup_password": "Unesite svoju lozinku za sigurnosnu kopiju ovdje", @@ -275,6 +277,7 @@ "extracted_address_content": "Poslat ćete sredstva primatelju\n${recipient_name}", "failed_authentication": "Autentifikacija neuspješna. ${state_error}", "faq": "FAQ", + "features": "Značajke", "fetching": "Dohvaćanje", "fiat_api": "Fiat API", "fiat_balance": "Fiat Bilans", @@ -793,6 +796,7 @@ "warning": "Upozorenje", "welcome": "Dobrodošli na", "welcome_to_cakepay": "Dobro došli u Cake Pay!", + "what_is_silent_payments": "Što je tiha plaćanja?", "widgets_address": "Adresa", "widgets_or": "ili", "widgets_restore_from_blockheight": "Oporavi pomoću visine bloka", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index f45a66b5b5..ca823c3d5d 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Konfirmasi pengurangan biaya", "confirm_fee_deduction_content": "Apakah Anda setuju untuk mengurangi biaya dari output?", "confirm_sending": "Konfirmasi pengiriman", + "confirm_silent_payments_switch_node": "Saat ini diminta untuk mengganti node untuk memindai pembayaran diam", "confirmations": "Konfirmasi", "confirmed": "Saldo Terkonfirmasi", "confirmed_tx": "Dikonfirmasi", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Kami menghasilkan alamat baru setiap kali Anda menggunakan satu, tetapi alamat sebelumnya tetap berfungsi", "email_address": "Alamat Email", "enable_replace_by_fee": "Aktifkan ganti-by-fee", + "enable_silent_payments_scanning": "Aktifkan pemindaian pembayaran diam", "enabled": "Diaktifkan", "enter_amount": "Masukkan Jumlah", "enter_backup_password": "Masukkan kata sandi cadangan di sini", @@ -275,6 +277,7 @@ "extracted_address_content": "Anda akan mengirim dana ke\n${recipient_name}", "failed_authentication": "Otentikasi gagal. ${state_error}", "faq": "Pertanyaan yang Sering Diajukan", + "features": "Fitur", "fetching": "Mengambil", "fiat_api": "API fiat", "fiat_balance": "Saldo Fiat", @@ -796,6 +799,7 @@ "warning": "Peringatan", "welcome": "Selamat datang di", "welcome_to_cakepay": "Selamat Datang di Cake Pay!", + "what_is_silent_payments": "Apa itu pembayaran diam?", "widgets_address": "Alamat", "widgets_or": "atau", "widgets_restore_from_blockheight": "Pulihkan dari tinggi blok", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 133ef62d5b..3b306b5ee5 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Conferma la detrazione delle commissioni", "confirm_fee_deduction_content": "Accetti di detrarre la commissione dall'output?", "confirm_sending": "Conferma l'invio", + "confirm_silent_payments_switch_node": "Attualmente è necessario cambiare nodi per scansionare i pagamenti silenziosi", "confirmations": "Conferme", "confirmed": "Saldo confermato", "confirmed_tx": "Confermato", @@ -219,6 +220,7 @@ "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare", "email_address": "Indirizzo e-mail", "enable_replace_by_fee": "Abilita sostituzione per fee", + "enable_silent_payments_scanning": "Abilita la scansione dei pagamenti silenziosi", "enabled": "Abilitato", "enter_amount": "Inserisci importo", "enter_backup_password": "Inserisci la password di backup qui", @@ -276,6 +278,7 @@ "extracted_address_content": "Invierai i tuoi fondi a\n${recipient_name}", "failed_authentication": "Autenticazione fallita. ${state_error}", "faq": "Domande Frequenti", + "features": "Caratteristiche", "fetching": "Recupero", "fiat_api": "Fiat API", "fiat_balance": "Equilibrio fiat", @@ -796,6 +799,7 @@ "warning": "Avvertimento", "welcome": "Benvenuto", "welcome_to_cakepay": "Benvenuto in Cake Pay!", + "what_is_silent_payments": "Che cos'è i pagamenti silenziosi?", "widgets_address": "Indirizzo", "widgets_or": "o", "widgets_restore_from_blockheight": "Recupera da altezza blocco", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index a6191460be..d1e70cb5ad 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "料金控除を確認します", "confirm_fee_deduction_content": "出力から料金を差し引くことに同意しますか?", "confirm_sending": "送信を確認", + "confirm_silent_payments_switch_node": "現在、ノードを切り替えてサイレント決済をスキャンする必要があります", "confirmations": "確認", "confirmed": "確認済み残高", "confirmed_tx": "確認済み", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します", "email_address": "メールアドレス", "enable_replace_by_fee": "交換ごとに有効にします", + "enable_silent_payments_scanning": "サイレントペイメントスキャンを有効にします", "enabled": "有効", "enter_amount": "金額を入力", "enter_backup_password": "ここにバックアップパスワードを入力してください", @@ -275,6 +277,7 @@ "extracted_address_content": "に送金します\n${recipient_name}", "failed_authentication": "認証失敗. ${state_error}", "faq": "FAQ", + "features": "特徴", "fetching": "フェッチング", "fiat_api": "不換紙幣 API", "fiat_balance": "フィアットバランス", @@ -794,6 +797,7 @@ "warning": "警告", "welcome": "ようこそ に", "welcome_to_cakepay": "Cake Payへようこそ!", + "what_is_silent_payments": "サイレント支払いとは何ですか?", "widgets_address": "住所", "widgets_or": "または", "widgets_restore_from_blockheight": "ブロックの高さから復元", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 9cd30a13f1..ccc164aa0a 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "수수료 공제를 확인하십시오", "confirm_fee_deduction_content": "출력에서 수수료를 공제하는 데 동의하십니까?", "confirm_sending": "전송 확인", + "confirm_silent_payments_switch_node": "현재 사일런트 결제를 스캔하려면 노드를 전환해야합니다.", "confirmations": "확인", "confirmed": "확인된 잔액", "confirmed_tx": "확인", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다.", "email_address": "이메일 주소", "enable_replace_by_fee": "대체별로 활성화하십시오", + "enable_silent_payments_scanning": "무음 지불 스캔을 활성화합니다", "enabled": "사용", "enter_amount": "금액 입력", "enter_backup_password": "여기에 백업 비밀번호를 입력하세요.", @@ -275,6 +277,7 @@ "extracted_address_content": "당신은에 자금을 보낼 것입니다\n${recipient_name}", "failed_authentication": "인증 실패. ${state_error}", "faq": "FAQ", + "features": "특징", "fetching": "가져 오는 중", "fiat_api": "명목 화폐 API", "fiat_balance": "피아트 잔액", @@ -794,6 +797,7 @@ "warning": "경고", "welcome": "환영 에", "welcome_to_cakepay": "Cake Pay에 오신 것을 환영합니다!", + "what_is_silent_payments": "조용한 지불이란 무엇입니까?", "widgets_address": "주소", "widgets_or": "또는", "widgets_restore_from_blockheight": "블록 높이에서 복원", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index ee1fc90c7f..8ffeb01d00 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "အခကြေးငွေကိုနှုတ်ယူခြင်း", "confirm_fee_deduction_content": "output မှအခကြေးငွေကိုယူရန်သဘောတူပါသလား။", "confirm_sending": "ပေးပို့အတည်ပြုပါ။", + "confirm_silent_payments_switch_node": "လောလောဆယ်အသံတိတ်ငွေပေးချေမှုကိုစကင်ဖတ်စစ်ဆေးရန် node များကိုပြောင်းရန်လိုအပ်သည်", "confirmations": "အတည်ပြုချက်များ", "confirmed": "အတည်ပြုထားသော လက်ကျန်ငွေ", "confirmed_tx": "အတည်ပြုသည်", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "သင်အသုံးပြုသည့်အချိန်တိုင်းတွင် ကျွန်ုပ်တို့သည် လိပ်စာအသစ်များကို ထုတ်ပေးသော်လည်း ယခင်လိပ်စာများသည် ဆက်လက်အလုပ်လုပ်နေပါသည်။", "email_address": "အီးမေးလ်လိပ်စာ", "enable_replace_by_fee": "အစားထိုး - by- အခကြေးငွေ enable", + "enable_silent_payments_scanning": "အသံတိတ်ငွေပေးချေမှုကို scanable လုပ်ပါ", "enabled": "ဖွင့်ထားသည်။", "enter_amount": "ပမာဏကို ထည့်ပါ။", "enter_backup_password": "အရန်စကားဝှက်ကို ဤနေရာတွင် ထည့်ပါ။", @@ -275,6 +277,7 @@ "extracted_address_content": "သင်သည် \n${recipient_name} သို့ ရန်ပုံငွေများ ပေးပို့ပါမည်", "failed_authentication": "အထောက်အထားစိစစ်ခြင်း မအောင်မြင်ပါ။. ${state_error}", "faq": "အမြဲမေးလေ့ရှိသောမေးခွန်းများ", + "features": "အင်္ဂါရပ်များ", "fetching": "ခေါ်ယူခြင်း။", "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", @@ -793,6 +796,7 @@ "warning": "သတိပေးချက်", "welcome": "မှကြိုဆိုပါတယ်။", "welcome_to_cakepay": "Cake Pay မှကြိုဆိုပါသည်။", + "what_is_silent_payments": "အသံတိတ်ငွေပေးချေမှုဆိုတာဘာလဲ", "widgets_address": "လိပ်စာ", "widgets_or": "သို့မဟုတ်", "widgets_restore_from_blockheight": "အမြင့်မှ ပြန်လည်ရယူပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index ae7a32dbb6..79ad955c2a 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Bevestig de aftrek van de kosten", "confirm_fee_deduction_content": "Stemt u ermee in om de vergoeding af te trekken van de output?", "confirm_sending": "Bevestig verzending", + "confirm_silent_payments_switch_node": "Momenteel is het vereist om knooppunten te schakelen om stille betalingen te scannen", "confirmations": "Bevestigingen", "confirmed": "Bevestigd saldo", "confirmed_tx": "Bevestigd", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "We genereren nieuwe adressen elke keer dat u er een gebruikt, maar eerdere adressen blijven werken", "email_address": "E-mailadres", "enable_replace_by_fee": "Schakel vervangen door een fee", + "enable_silent_payments_scanning": "Schakel stille betalingen in scannen in", "enabled": "Ingeschakeld", "enter_amount": "Voer Bedrag in", "enter_backup_password": "Voer hier een back-upwachtwoord in", @@ -275,6 +277,7 @@ "extracted_address_content": "U stuurt geld naar\n${recipient_name}", "failed_authentication": "Mislukte authenticatie. ${state_error}", "faq": "FAQ", + "features": "Functies", "fetching": "Ophalen", "fiat_api": "Fiat API", "fiat_balance": "Fiat Balans", @@ -794,6 +797,7 @@ "warning": "Waarschuwing", "welcome": "Welkom bij", "welcome_to_cakepay": "Welkom bij Cake Pay!", + "what_is_silent_payments": "Wat zijn stille betalingen?", "widgets_address": "Adres", "widgets_or": "of", "widgets_restore_from_blockheight": "Herstel vanaf blockheight", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 7e7cdfb758..0e21faf6d1 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Potwierdź odliczenie opłaty", "confirm_fee_deduction_content": "Czy zgadzasz się odliczyć opłatę od wyników?", "confirm_sending": "Potwierdź wysłanie", + "confirm_silent_payments_switch_node": "Obecnie wymagane jest zmiana węzłów w celu skanowania cichych płatności", "confirmations": "Potwierdzenia", "confirmed": "Potwierdzone saldo", "confirmed_tx": "Potwierdzony", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Za każdym razem, gdy wykorzystasz adres, dla wiekszej prywatności generujemy nowy, ale poprzednie adresy nadal działają, i moga odbierać środki", "email_address": "Adres e-mail", "enable_replace_by_fee": "Włącz wymianę po lewej", + "enable_silent_payments_scanning": "Włącz skanowanie cichych płatności", "enabled": "Włączone", "enter_amount": "Wprowadź kwotę", "enter_backup_password": "Wprowadź tutaj hasło kopii zapasowej", @@ -275,6 +277,7 @@ "extracted_address_content": "Wysyłasz środki na\n${recipient_name}", "failed_authentication": "Nieudane uwierzytelnienie. ${state_error}", "faq": "FAQ", + "features": "Cechy", "fetching": "Pobieranie", "fiat_api": "API Walut FIAT", "fiat_balance": "Bilans Fiata", @@ -793,6 +796,7 @@ "warning": "Ostrzeżenie", "welcome": "Witamy w", "welcome_to_cakepay": "Witamy w Cake Pay!", + "what_is_silent_payments": "Co to są ciche płatności?", "widgets_address": "Adres", "widgets_or": "lub", "widgets_restore_from_blockheight": "Przywróć z wysokości bloku", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 1526b6aad2..bef5bbf835 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Confirme dedução da taxa", "confirm_fee_deduction_content": "Você concorda em deduzir a taxa da saída?", "confirm_sending": "Confirmar o envio", + "confirm_silent_payments_switch_node": "Atualmente, é necessário trocar de nós para digitalizar pagamentos silenciosos", "confirmations": "Confirmações", "confirmed": "Saldo Confirmado", "confirmed_tx": "Confirmado", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando", "email_address": "Endereço de e-mail", "enable_replace_by_fee": "Habilite substituir por taxa", + "enable_silent_payments_scanning": "Ativar escaneamento de pagamentos silenciosos", "enabled": "Habilitado", "enter_amount": "Digite o valor", "enter_backup_password": "Digite a senha de backup aqui", @@ -275,6 +277,7 @@ "extracted_address_content": "Você enviará fundos para\n${recipient_name}", "failed_authentication": "Falha na autenticação. ${state_error}", "faq": "FAQ", + "features": "Funcionalidades", "fetching": "Buscando", "fiat_api": "API da Fiat", "fiat_balance": "Equilíbrio Fiat", @@ -796,6 +799,7 @@ "warning": "Aviso", "welcome": "Bem-vindo ao", "welcome_to_cakepay": "Bem-vindo ao Cake Pay!", + "what_is_silent_payments": "O que são pagamentos silenciosos?", "widgets_address": "Endereço", "widgets_or": "ou", "widgets_restore_from_blockheight": "Restaurar a partir de altura do bloco", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 0534f52d77..d4c049f593 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Подтвердите вычет платы", "confirm_fee_deduction_content": "Согласны ли вы вычесть плату из вывода?", "confirm_sending": "Подтвердить отправку", + "confirm_silent_payments_switch_node": "В настоящее время требуется переключение узлов для сканирования молчаливых платежей", "confirmations": "Подтверждения", "confirmed": "Подтвержденный баланс", "confirmed_tx": "Подтвержденный", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать.", "email_address": "Адрес электронной почты", "enable_replace_by_fee": "Включить замену за пикой", + "enable_silent_payments_scanning": "Включить сканирование безмолвных платежей", "enabled": "Включено", "enter_amount": "Введите сумму", "enter_backup_password": "Введите пароль резервной копии", @@ -275,6 +277,7 @@ "extracted_address_content": "Вы будете отправлять средства\n${recipient_name}", "failed_authentication": "Ошибка аутентификации. ${state_error}", "faq": "FAQ", + "features": "Функции", "fetching": "Загрузка", "fiat_api": "Фиат API", "fiat_balance": "Фиатный баланс", @@ -794,6 +797,7 @@ "warning": "Предупреждение", "welcome": "Приветствуем в", "welcome_to_cakepay": "Добро пожаловать в Cake Pay!", + "what_is_silent_payments": "Что такое молчаливые платежи?", "widgets_address": "Адрес", "widgets_or": "или", "widgets_restore_from_blockheight": "Восстановить на высоте блока", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 434940a312..a74c3c9e5e 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "ยืนยันการหักค่าธรรมเนียม", "confirm_fee_deduction_content": "คุณตกลงที่จะหักค่าธรรมเนียมจากผลลัพธ์หรือไม่?", "confirm_sending": "ยืนยันการส่ง", + "confirm_silent_payments_switch_node": "ขณะนี้จำเป็นต้องเปลี่ยนโหนดเพื่อสแกนการชำระเงินแบบเงียบ", "confirmations": "การยืนยัน", "confirmed": "ยอดคงเหลือที่ยืนยันแล้ว", "confirmed_tx": "ซึ่งยืนยันแล้ว", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "เราสร้างที่อยู่ใหม่ทุกครั้งที่คุณใช้หนึ่งอย่าง แต่ที่อยู่เก่ายังสามารถใช้ได้ต่อไป", "email_address": "ที่อยู่อีเมล", "enable_replace_by_fee": "เปิดใช้งานการเปลี่ยนโดยค่าธรรมเนียม", + "enable_silent_payments_scanning": "เปิดใช้งานการสแกนการชำระเงินแบบเงียบ", "enabled": "เปิดใช้งาน", "enter_amount": "กรอกจำนวน", "enter_backup_password": "ป้อนรหัสผ่านสำรองที่นี่", @@ -275,6 +277,7 @@ "extracted_address_content": "คุณกำลังจะส่งเงินไปยัง\n${recipient_name}", "failed_authentication": "การยืนยันสิทธิ์ล้มเหลว ${state_error}", "faq": "คำถามที่พบบ่อย", + "features": "คุณสมบัติ", "fetching": "กำลังโหลด", "fiat_api": "API สกุลเงินตรา", "fiat_balance": "เฟียต บาลานซ์", @@ -793,6 +796,7 @@ "warning": "คำเตือน", "welcome": "ยินดีต้อนรับสู่", "welcome_to_cakepay": "ยินดีต้อนรับสู่ Cake Pay!", + "what_is_silent_payments": "การชำระเงินเงียบคืออะไร?", "widgets_address": "ที่อยู่", "widgets_or": "หรือ", "widgets_restore_from_blockheight": "กู้คืนจากระดับบล็อก", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 08be1b7277..23c1758cf0 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Kumpirmahin ang pagbabawas ng bayad", "confirm_fee_deduction_content": "Sumasang -ayon ka bang bawasan ang bayad mula sa output?", "confirm_sending": "Kumpirmahin ang pagpapadala", + "confirm_silent_payments_switch_node": "Sa kasalukuyan kinakailangan itong lumipat ng mga node upang i -scan ang mga tahimik na pagbabayad", "confirmations": "Mga kumpirmasyon", "confirmed": "Nakumpirma na balanse", "confirmed_tx": "Nakumpirma", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Bumubuo kami ng mga bagong address sa tuwing gumagamit ka ng isa, ngunit ang mga nakaraang address ay patuloy na gumagana", "email_address": "Email address", "enable_replace_by_fee": "Paganahin ang palitan-by-fee", + "enable_silent_payments_scanning": "Paganahin ang pag -scan ng tahimik na pagbabayad", "enabled": "Pinagana", "enter_amount": "Ipasok ang halaga", "enter_backup_password": "Ipasok ang backup password dito", @@ -275,6 +277,7 @@ "extracted_address_content": "Magpapadala ka ng pondo sa\n${recipient_name}", "failed_authentication": "Nabigong pagpapatunay. ${state_error}", "faq": "FAQ", + "features": "Mga tampok", "fetching": "Pagkuha", "fiat_api": "Fiat API", "fiat_balance": "Balanse ng fiat", @@ -793,6 +796,7 @@ "warning": "Babala", "welcome": "Maligayang pagdating sa", "welcome_to_cakepay": "Maligayang pagdating sa cake pay!", + "what_is_silent_payments": "Ano ang tahimik na pagbabayad?", "widgets_address": "Address", "widgets_or": "o", "widgets_restore_from_blockheight": "Ibalik mula sa blockheight", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 24f79222e5..c349c1e53f 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Ücret kesintisini onaylayın", "confirm_fee_deduction_content": "Ücreti çıktıdan düşürmeyi kabul ediyor musunuz?", "confirm_sending": "Göndermeyi onayla", + "confirm_silent_payments_switch_node": "Şu anda sessiz ödemeleri taramak için düğümleri değiştirmek gerekiyor", "confirmations": "Onay", "confirmed": "Onaylanmış Bakiye", "confirmed_tx": "Onaylanmış", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Adresini her kullandığında yeni adres oluşturuyoruz, ancak önceki adresler de çalışmaya devam eder", "email_address": "E-posta Adresi", "enable_replace_by_fee": "Farklı Değiştir'i Etkinleştir", + "enable_silent_payments_scanning": "Sessiz ödeme taramasını etkinleştirin", "enabled": "Etkin", "enter_amount": "Miktar Girin", "enter_backup_password": "Yedekleme parolasını buraya gir", @@ -275,6 +277,7 @@ "extracted_address_content": "Parayı buraya gönderceksin:\n${recipient_name}", "failed_authentication": "Doğrulama başarısız oldu. ${state_error}", "faq": "SSS", + "features": "Özellikler", "fetching": "Getiriliyor", "fiat_api": "İtibari Para API", "fiat_balance": "Fiat Bakiyesi", @@ -793,6 +796,7 @@ "warning": "Uyarı", "welcome": "Hoş Geldiniz", "welcome_to_cakepay": "Cake Pay'e Hoş Geldiniz!", + "what_is_silent_payments": "Sessiz ödemeler nedir?", "widgets_address": "Adres", "widgets_or": "veya", "widgets_restore_from_blockheight": "Blok yüksekliğinden geri yükle", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index f3643a64b8..4bc71e0e77 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Підтвердьте відрахування комісії", "confirm_fee_deduction_content": "Чи погоджуєтесь ви вирахувати комісію з сумми одержувача?", "confirm_sending": "Підтвердити відправлення", + "confirm_silent_payments_switch_node": "В даний час потрібно перемикати вузли на сканування мовчазних платежів", "confirmations": "Підтвердження", "confirmed": "Підтверджений баланс", "confirmed_tx": "Підтверджений", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати", "email_address": "Адреса електронної пошти", "enable_replace_by_fee": "Увімкнути заміну з комісією", + "enable_silent_payments_scanning": "Увімкнути мовчазні платежі сканування", "enabled": "Увімкнено", "enter_amount": "Введіть суму", "enter_backup_password": "Введіть пароль резервної копії", @@ -275,6 +277,7 @@ "extracted_address_content": "Ви будете відправляти кошти\n${recipient_name}", "failed_authentication": "Помилка аутентифікації. ${state_error}", "faq": "FAQ", + "features": "Особливості", "fetching": "Завантаження", "fiat_api": "Фіат API", "fiat_balance": "Фіат Баланс", @@ -794,6 +797,7 @@ "warning": "УВАГА", "welcome": "Вітаємо в", "welcome_to_cakepay": "Ласкаво просимо до Cake Pay!", + "what_is_silent_payments": "Що таке мовчазні платежі?", "widgets_address": "Адреса", "widgets_or": "або", "widgets_restore_from_blockheight": "Відновити на висоті блоку", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index b39e211ef5..89ebb2a63f 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "فیس میں کٹوتی کی تصدیق کریں", "confirm_fee_deduction_content": "کیا آپ آؤٹ پٹ سے فیس کم کرنے پر راضی ہیں؟", "confirm_sending": "بھیجنے کی تصدیق کریں۔", + "confirm_silent_payments_switch_node": "فی الحال خاموش ادائیگیوں کو اسکین کرنے کے لئے نوڈس کو تبدیل کرنے کی ضرورت ہے", "confirmations": "تصدیقات", "confirmed": "تصدیق شدہ بیلنس", "confirmed_tx": "تصدیق", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "جب بھی آپ ایک کا استعمال کرتے ہیں تو ہم نئے پتے تیار کرتے ہیں، لیکن پچھلے پتے کام کرتے رہتے ہیں۔", "email_address": "ای میل اڈریس", "enable_replace_by_fee": "فی فیس کو تبدیل کریں", + "enable_silent_payments_scanning": "خاموش ادائیگیوں کو اسکیننگ کے قابل بنائیں", "enabled": "فعال", "enter_amount": "رقم درج کریں۔", "enter_backup_password": "یہاں بیک اپ پاس ورڈ درج کریں۔", @@ -275,6 +277,7 @@ "extracted_address_content": "آپ فنڈز بھیج رہے ہوں گے\n${recipient_name}", "failed_authentication": "ناکام تصدیق۔ ${state_error}", "faq": "عمومی سوالات", + "features": "خصوصیات", "fetching": "لا رہا ہے۔", "fiat_api": "Fiat API", "fiat_balance": "فیاٹ بیلنس", @@ -795,6 +798,7 @@ "warning": "وارننگ", "welcome": "میں خوش آمدید", "welcome_to_cakepay": "Cake پے میں خوش آمدید!", + "what_is_silent_payments": "خاموش ادائیگی کیا ہے؟", "widgets_address": "پتہ", "widgets_or": "یا", "widgets_restore_from_blockheight": "بلاک ہائیٹ سے بحال کریں۔", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index ea9caf6370..352cb40aad 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "Jẹrisi iyọkuro owo", "confirm_fee_deduction_content": "Ṣe o gba lati yọkuro idiyele naa kuro ni iṣejade?", "confirm_sending": "Jẹ́rìí sí ránṣẹ́", + "confirm_silent_payments_switch_node": "Lọwọlọwọ o nilo lati yi awọn apa pada si awọn sisanwo ipalọlọ", "confirmations": "Àwọn ẹ̀rí", "confirmed": "A ti jẹ́rìí ẹ̀", "confirmed_tx": "Jẹrisi", @@ -219,6 +220,7 @@ "electrum_address_disclaimer": "A dá àwọn àdírẹ́sì títun ní gbogbo àwọn ìgbà t'ẹ́ lo ó kan ṣùgbọ́n ẹ lè tẹ̀síwájú lo àwọn àdírẹ́sì tẹ́lẹ̀tẹ́lẹ̀.", "email_address": "Àdírẹ́sì ímeèlì", "enable_replace_by_fee": "Mu ki o rọpo", + "enable_silent_payments_scanning": "Mu ki awọn sisanwo ipalọlọ", "enabled": "Wọ́n tíwọn ti tan", "enter_amount": "Tẹ̀ iye", "enter_backup_password": "Tẹ̀ ọ̀rọ̀ aṣínà ti ẹ̀dà ḿbí", @@ -276,6 +278,7 @@ "extracted_address_content": "Ẹ máa máa fi owó ránṣẹ́ sí\n${recipient_name}", "failed_authentication": "Ìfẹ̀rílàdí pipòfo. ${state_error}", "faq": "Àwọn ìbéèrè l'a máa ń bèèrè", + "features": "Awọn ẹya", "fetching": "ń wá", "fiat_api": "Ojú ètò áàpù owó tí ìjọba pàṣẹ wa lò", "fiat_balance": "Fiat Iwontunws.funfun", @@ -794,6 +797,7 @@ "warning": "Ikilo", "welcome": "Ẹ káàbọ sí", "welcome_to_cakepay": "Ẹ káàbọ̀ sí Cake Pay!", + "what_is_silent_payments": "Kini awọn sisanwo ipalọlọ?", "widgets_address": "Àdírẹ́sì", "widgets_or": "tàbí", "widgets_restore_from_blockheight": "Dá padà sípò láti gíga àkójọpọ̀", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 822dc0b166..794128bf00 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -139,6 +139,7 @@ "confirm_fee_deduction": "确认费用扣除", "confirm_fee_deduction_content": "您是否同意从产出中扣除费用?", "confirm_sending": "确认发送", + "confirm_silent_payments_switch_node": "目前需要切换节点来扫描无声付款", "confirmations": "确认", "confirmed": "确认余额", "confirmed_tx": "确认的", @@ -218,6 +219,7 @@ "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效", "email_address": "电子邮件地址", "enable_replace_by_fee": "启用by-Fee替换", + "enable_silent_payments_scanning": "启用无声付款扫描", "enabled": "启用", "enter_amount": "输入金额", "enter_backup_password": "在此处输入備用密码", @@ -275,6 +277,7 @@ "extracted_address_content": "您将汇款至\n${recipient_name}", "failed_authentication": "身份验证失败. ${state_error}", "faq": "FAQ", + "features": "特征", "fetching": "正在获取", "fiat_api": "法币API", "fiat_balance": "法币余额", @@ -793,6 +796,7 @@ "warning": "警告", "welcome": "欢迎使用", "welcome_to_cakepay": "欢迎来到 Cake Pay!", + "what_is_silent_payments": "什么是无声付款?", "widgets_address": "地址", "widgets_or": "或者", "widgets_restore_from_blockheight": "从块高还原", diff --git a/tool/configure.dart b/tool/configure.dart index 9c5f4b5907..6eb8f0e6cd 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -72,6 +72,7 @@ import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:hive/hive.dart'; import 'package:bitcoin_base/bitcoin_base.dart';"""; const bitcoinCWHeaders = """ From fa5effd0ccf449ed0fb98429cff37b81c7963817 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 9 Apr 2024 19:27:26 -0300 Subject: [PATCH 027/242] feat: delete silent addresses --- cw_bitcoin/lib/electrum_wallet_addresses.dart | 40 ++++++++++++++++--- lib/bitcoin/cw_bitcoin.dart | 5 +++ lib/src/screens/receive/receive_page.dart | 29 ++++++++------ .../screens/receive/widgets/address_cell.dart | 33 ++++++++++----- .../wallet_address_list_view_model.dart | 10 ++++- 5 files changed, 89 insertions(+), 28 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index c21bc6c902..4d11792431 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -386,12 +386,31 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action void updateAddress(String address, String label) { - final addressRecord = - _addresses.firstWhere((addressRecord) => addressRecord.address == address); - addressRecord.setNewName(label); - final index = _addresses.indexOf(addressRecord); - _addresses.remove(addressRecord); - _addresses.insert(index, addressRecord); + BaseBitcoinAddressRecord? foundAddress; + _addresses.forEach((addressRecord) { + if (addressRecord.address == address) { + foundAddress = addressRecord; + } + }); + silentAddresses.forEach((addressRecord) { + if (addressRecord.address == address) { + foundAddress = addressRecord; + } + }); + + if (foundAddress != null) { + foundAddress!.setNewName(label); + + if (foundAddress is BitcoinAddressRecord) { + final index = _addresses.indexOf(foundAddress); + _addresses.remove(foundAddress); + _addresses.insert(index, foundAddress as BitcoinAddressRecord); + } else { + final index = silentAddresses.indexOf(foundAddress as BitcoinSilentPaymentAddressRecord); + silentAddresses.remove(foundAddress); + silentAddresses.insert(index, foundAddress as BitcoinSilentPaymentAddressRecord); + } + } } @action @@ -537,4 +556,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => !addr.isHidden && !addr.isUsed && addr.type == type; + + @action + void deleteSilentPaymentAddress(String address) { + final addressRecord = silentAddresses.firstWhere((addressRecord) => + addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address); + + silentAddresses.remove(addressRecord); + updateAddressesByMatch(); + } } diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index db1ebcf4ab..51db7c827d 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -365,4 +365,9 @@ class CWBitcoin extends Bitcoin { return node?.uri.host == '198.58.111.154' && node?.uri.port == 50002; } + + void deleteSilentPaymentAddress(Object wallet, String address) { + final bitcoinWallet = wallet as ElectrumWallet; + bitcoinWallet.walletAddresses.deleteSilentPaymentAddress(address); + } } diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 4d0c6185f1..e6009d0c21 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -206,18 +206,23 @@ class ReceivePage extends BasePage { .extension()! .tilesTextColor; - return AddressCell.fromItem(item, - isCurrent: isCurrent, - hasBalance: addressListViewModel.isElectrumWallet, - backgroundColor: backgroundColor, - textColor: textColor, - onTap: item.isOneTimeReceiveAddress == true - ? null - : (_) => addressListViewModel.setAddress(item), - onEdit: item.isOneTimeReceiveAddress == true - ? null - : () => Navigator.of(context) - .pushNamed(Routes.newSubaddress, arguments: item)); + return AddressCell.fromItem( + item, + isCurrent: isCurrent, + hasBalance: addressListViewModel.isElectrumWallet, + backgroundColor: backgroundColor, + textColor: textColor, + onTap: item.isOneTimeReceiveAddress == true + ? null + : (_) => addressListViewModel.setAddress(item), + onEdit: item.isOneTimeReceiveAddress == true || item.isPrimary + ? null + : () => Navigator.of(context) + .pushNamed(Routes.newSubaddress, arguments: item), + onDelete: !addressListViewModel.isSilentPayments || item.isPrimary + ? null + : () => addressListViewModel.deleteAddress(item), + ); }); } diff --git a/lib/src/screens/receive/widgets/address_cell.dart b/lib/src/screens/receive/widgets/address_cell.dart index 69e0119004..a7a19fb541 100644 --- a/lib/src/screens/receive/widgets/address_cell.dart +++ b/lib/src/screens/receive/widgets/address_cell.dart @@ -14,18 +14,22 @@ class AddressCell extends StatelessWidget { required this.textColor, this.onTap, this.onEdit, + this.onDelete, this.txCount, this.balance, this.isChange = false, this.hasBalance = false}); - factory AddressCell.fromItem(WalletAddressListItem item, - {required bool isCurrent, - required Color backgroundColor, - required Color textColor, - Function(String)? onTap, - bool hasBalance = false, - Function()? onEdit}) => + factory AddressCell.fromItem( + WalletAddressListItem item, { + required bool isCurrent, + required Color backgroundColor, + required Color textColor, + Function(String)? onTap, + bool hasBalance = false, + Function()? onEdit, + Function()? onDelete, + }) => AddressCell( address: item.address, name: item.name ?? '', @@ -35,6 +39,7 @@ class AddressCell extends StatelessWidget { textColor: textColor, onTap: onTap, onEdit: onEdit, + onDelete: onDelete, txCount: item.txCount, balance: item.balance, isChange: item.isChange, @@ -48,6 +53,7 @@ class AddressCell extends StatelessWidget { final Color textColor; final Function(String)? onTap; final Function()? onEdit; + final Function()? onDelete; final int? txCount; final String? balance; final bool isChange; @@ -63,7 +69,8 @@ class AddressCell extends StatelessWidget { } else { return formatIfCashAddr.substring(0, addressPreviewLength) + '...' + - formatIfCashAddr.substring(formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length); + formatIfCashAddr.substring( + formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length); } } @@ -175,7 +182,7 @@ class AddressCell extends StatelessWidget { ActionPane _actionPane(BuildContext context) => ActionPane( motion: const ScrollMotion(), - extentRatio: 0.3, + extentRatio: onDelete != null ? 0.4 : 0.3, children: [ SlidableAction( onPressed: (_) => onEdit?.call(), @@ -184,6 +191,14 @@ class AddressCell extends StatelessWidget { icon: Icons.edit, label: S.of(context).edit, ), + if (onDelete != null) + SlidableAction( + onPressed: (_) => onDelete!.call(), + backgroundColor: Colors.red, + foregroundColor: Colors.white, + icon: Icons.delete, + label: S.of(context).delete, + ), ], ); } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 9289701fc9..1eae60c993 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -320,7 +320,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo if (isElectrumWallet) { if (bitcoin!.hasSelectedSilentPayments(wallet)) { final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) { - final isPrimary = address.index == 0; + // Silent Payments index 0 is change per BIP + final isPrimary = address.index == 1; return WalletAddressListItem( id: address.index, @@ -497,4 +498,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo amount = ''; } } + + @action + void deleteAddress(ListItem item) { + if (wallet.type == WalletType.bitcoin && item is WalletAddressListItem) { + bitcoin!.deleteSilentPaymentAddress(wallet, item.address); + } + } } From 0777db81c7ab16b588f679d5f5a1551879528487 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 9 Apr 2024 19:38:39 -0300 Subject: [PATCH 028/242] fix: red dot in non ssl nodes --- cw_core/lib/node.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 585bc3c38b..7e182623bb 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -232,8 +232,12 @@ class Node extends HiveObject with Keyable { Future requestElectrumServer() async { try { - await SecureSocket.connect(uri.host, uri.port, - timeout: Duration(seconds: 5), onBadCertificate: (_) => true); + if (useSSL == true) { + await SecureSocket.connect(uri.host, uri.port, + timeout: Duration(seconds: 5), onBadCertificate: (_) => true); + } else { + await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5)); + } return true; } catch (_) { return false; From 65d6a890d025edf67e8153b4a1b3874dc8dfacda Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 9 Apr 2024 20:35:00 -0300 Subject: [PATCH 029/242] fix: inconsistent connection states, fix tx history --- cw_bitcoin/lib/electrum.dart | 7 +++-- cw_bitcoin/lib/electrum_transaction_info.dart | 8 ++++-- cw_bitcoin/lib/electrum_wallet.dart | 26 ++++++++++--------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 5f6363c755..6c532a7cef 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -64,7 +64,8 @@ class ElectrumClient { } catch (_) {} if (useSSL == true) { - socket = await SecureSocket.connect(host, port, timeout: connectionTimeout); + socket = await SecureSocket.connect(host, port, + timeout: connectionTimeout, onBadCertificate: (_) => true); } else { socket = await Socket.connect(host, port, timeout: connectionTimeout); } @@ -418,7 +419,9 @@ class ElectrumClient { Future close() async { _aliveTimer?.cancel(); - await socket?.close(); + try { + await socket?.close(); + } catch (_) {} onConnectionStatusChange = null; } diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index bd2ca731c0..a0390c64a7 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -158,6 +158,8 @@ class ElectrumTransactionInfo extends TransactionInfo { } factory ElectrumTransactionInfo.fromJson(Map data, WalletType type) { + final inputAddresses = data['inputAddresses'] as List; + final outputAddresses = data['outputAddresses'] as List; return ElectrumTransactionInfo( type, id: data['id'] as String, @@ -168,8 +170,10 @@ class ElectrumTransactionInfo extends TransactionInfo { date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), isPending: data['isPending'] as bool, confirmations: data['confirmations'] as int, - inputAddresses: data['inputAddresses'] as List, - outputAddresses: data['outputAddresses'] as List, + inputAddresses: + inputAddresses.isEmpty ? [] : inputAddresses.map((e) => e.toString()).toList(), + outputAddresses: + outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(), to: data['to'] as String?, unspents: data['unspents'] != null ? (data['unspents'] as List) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index a5a29b6a6f..5168d10551 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -322,31 +322,32 @@ abstract class ElectrumWalletBase Node? node; @action - Future _electrumConnect(Node node, {bool? attemptedReconnect}) async { + @override + Future connectToNode({required Node node}) async { this.node = node; try { syncStatus = ConnectingSyncStatus(); - await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); + + if (!electrumClient.isConnected) { + await electrumClient.close(); + } + electrumClient.onConnectionStatusChange = (bool isConnected) async { - if (!isConnected) { + if (isConnected) { + syncStatus = ConnectedSyncStatus(); + } else if (isConnected == false) { syncStatus = LostConnectionSyncStatus(); - if (attemptedReconnect == false) { - await _electrumConnect(node, attemptedReconnect: true); - } } }; - syncStatus = ConnectedSyncStatus(); + + await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); } catch (e) { print(e.toString()); syncStatus = FailedSyncStatus(); } } - @action - @override - Future connectToNode({required Node node}) => _electrumConnect(node); - int get _dustAmount => 546; bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet; @@ -1420,6 +1421,7 @@ abstract class ElectrumWalletBase Future updateTransactions() async { try { if (_isTransactionUpdating) { + _isTransactionUpdating = false; return; } @@ -1713,7 +1715,7 @@ Future startRefresh(ScanData scanData) async { final electrumClient = await getElectrumConnection(); final scanningBlockCount = - scanData.isSingleScan ? 1 : (scanData.network == BitcoinNetwork.testnet ? 1 : 10); + scanData.isSingleScan ? 1 : (scanData.network == BitcoinNetwork.testnet ? 50 : 10); Map? tweaks; try { From 465c7efa73aebdbae623a41436ba872162dae337 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Wed, 10 Apr 2024 18:05:36 -0300 Subject: [PATCH 030/242] fix: tx & balance displays, cpfp sending --- cw_bitcoin/lib/electrum_wallet.dart | 70 +++++++++++---------- cw_core/lib/unspent_transaction_output.dart | 1 + 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 5168d10551..a0fc96633d 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -238,9 +238,15 @@ abstract class ElectrumWalletBase final existingTxInfo = transactionHistory.transactions[txid]; if (existingTxInfo != null) { + existingTxInfo.amount = tx.amount; + existingTxInfo.confirmations = tx.confirmations; + existingTxInfo.height = tx.height; + final newUnspents = tx.unspents! .where((unspent) => !(existingTxInfo.unspents?.any((element) => - element.hash.contains(unspent.hash) && element.vout == unspent.vout) ?? + element.hash.contains(unspent.hash) && + element.vout == unspent.vout && + element.value == unspent.value) ?? false)) .toList(); @@ -290,11 +296,13 @@ abstract class ElectrumWalletBase try { syncStatus = AttemptingSyncStatus(); - if (silentPaymentsScanningActive) { + if (hasSilentPaymentsScanning) { try { await _setInitialHeight(); } catch (_) {} + } + if (silentPaymentsScanningActive) { if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); } @@ -412,11 +420,14 @@ abstract class ElectrumWalletBase List inputPrivKeyInfos = []; List inputPubKeys = []; bool spendsSilentPayment = false; + bool spendsCPFP = false; for (int i = 0; i < unspentCoins.length; i++) { final utx = unspentCoins[i]; if (utx.isSending) { + spendsCPFP = utx.confirmations == 0; + allInputsAmount += utx.value; final address = addressTypeFromStr(utx.address, network); @@ -523,6 +534,7 @@ abstract class ElectrumWalletBase hasChange: false, memo: memo, spendsSilentPayment: spendsSilentPayment, + spendsCPFP: spendsCPFP, ); } @@ -540,12 +552,14 @@ abstract class ElectrumWalletBase List inputPubKeys = []; int allInputsAmount = 0; bool spendsSilentPayment = false; + bool spendsCPFP = false; int leftAmount = credentialsAmount; final sendingCoins = unspentCoins.where((utx) => utx.isSending).toList(); for (int i = 0; i < sendingCoins.length; i++) { final utx = sendingCoins[i]; + spendsCPFP = utx.confirmations == 0; allInputsAmount += utx.value; leftAmount = leftAmount - utx.value; @@ -725,6 +739,7 @@ abstract class ElectrumWalletBase isSendAll: false, memo: memo, spendsSilentPayment: spendsSilentPayment, + spendsCPFP: spendsCPFP, ); } @@ -802,7 +817,7 @@ abstract class ElectrumWalletBase network: network, memo: estimatedTx.memo, outputOrdering: BitcoinOrdering.none, - enableRBF: true, + enableRBF: !estimatedTx.spendsCPFP, ); } else { txb = BitcoinTransactionBuilder( @@ -812,7 +827,7 @@ abstract class ElectrumWalletBase network: network, memo: estimatedTx.memo, outputOrdering: BitcoinOrdering.none, - enableRBF: true, + enableRBF: !estimatedTx.spendsCPFP, ); } @@ -1021,6 +1036,7 @@ abstract class ElectrumWalletBase final tx = await fetchTransactionInfo( hash: coin.hash, height: 0, myAddresses: addressesSet); coin.isChange = tx?.direction == TransactionDirection.outgoing; + coin.confirmations = tx?.confirmations; updatedUnspentCoins.add(coin); } catch (_) {} })))); @@ -1425,6 +1441,12 @@ abstract class ElectrumWalletBase return; } + transactionHistory.transactions.values.forEach((tx) { + if (tx.unspents != null && currentChainTip != null) { + tx.confirmations = currentChainTip! - tx.height + 1; + } + }); + _isTransactionUpdating = true; await fetchTransactions(); walletAddresses.updateReceiveAddresses(); @@ -1526,22 +1548,6 @@ abstract class ElectrumWalletBase Future updateBalance() async { balance[currency] = await _fetchBalances(); - - if (hasSilentPaymentsScanning) { - // Update balance stored from scanned silent payment transactions - try { - transactionHistory.transactions.values.forEach((tx) { - if (tx.unspents != null) { - balance[currency]!.confirmed += tx.unspents! - .where( - (unspent) => unspent.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) - .map((e) => e.value) - .reduce((value, element) => value + element); - } - }); - } catch (_) {} - } - await save(); } @@ -1660,15 +1666,9 @@ Future startRefresh(ScanData scanData) async { return electrumClient; } - Future getNodeHeightOrUpdate(int baseHeight) async { - if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) { - final electrumClient = await getElectrumConnection(); - - cachedBlockchainHeight = - await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; - } - - return cachedBlockchainHeight; + Future getUpdatedNodeHeight() async { + final electrumClient = await getElectrumConnection(); + return await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; } var lastKnownBlockHeight = 0; @@ -1700,14 +1700,14 @@ Future startRefresh(ScanData scanData) async { if (scanData.isSingleScan) { syncingStatus = SyncingSyncStatus(1, 0); } else { - syncingStatus = - SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight); + syncingStatus = SyncingSyncStatus.fromHeightValues( + await getUpdatedNodeHeight(), initialSyncHeight, syncHeight); } scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); if (syncingStatus.blocksLeft <= 0 || (scanData.isSingleScan && scanData.height != syncHeight)) { - scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); + scanData.sendPort.send(SyncResponse(await getUpdatedNodeHeight(), SyncedSyncStatus())); return; } @@ -1806,12 +1806,12 @@ Future startRefresh(ScanData scanData) async { WalletType.bitcoin, id: tx.hash, height: syncHeight, - amount: 0, // will be added later via unspent + amount: amount!, fee: 0, direction: TransactionDirection.incoming, isPending: false, date: DateTime.now(), - confirmations: currentChainTip - syncHeight - 1, + confirmations: await getUpdatedNodeHeight() - int.parse(blockHeight) + 1, to: value.label != null ? SilentPaymentAddress( version: scanData.silentAddress.version, @@ -1857,6 +1857,7 @@ class EstimatedTxResult { required this.isSendAll, this.memo, required this.spendsSilentPayment, + required this.spendsCPFP, }); final List utxos; @@ -1867,6 +1868,7 @@ class EstimatedTxResult { final bool hasChange; final bool isSendAll; final String? memo; + final bool spendsCPFP; } BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) { diff --git a/cw_core/lib/unspent_transaction_output.dart b/cw_core/lib/unspent_transaction_output.dart index 01b26cdcc6..d225493e98 100644 --- a/cw_core/lib/unspent_transaction_output.dart +++ b/cw_core/lib/unspent_transaction_output.dart @@ -14,6 +14,7 @@ class Unspent { bool isChange; bool isSending; bool isFrozen; + int? confirmations; String note; bool get isP2wpkh => From a4f8cdfa99251961a7cfae63357cfd3a952ebff8 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 12 Apr 2024 15:28:01 -0300 Subject: [PATCH 031/242] feat: new rust lib --- cw_bitcoin/lib/electrum_wallet.dart | 155 ++++++++++-------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 4 +- cw_bitcoin/pubspec.lock | 53 +++++- cw_bitcoin/pubspec.yaml | 4 + lib/bitcoin/cw_bitcoin.dart | 9 + pubspec_base.yaml | 2 +- tool/configure.dart | 8 +- 7 files changed, 162 insertions(+), 73 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index a0fc96633d..439a51a133 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -42,6 +42,7 @@ import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:http/http.dart' as http; +import 'package:sp_scanner/sp_scanner.dart'; part 'electrum_wallet.g.dart'; @@ -223,6 +224,10 @@ abstract class ElectrumWalletBase transactionHistoryIds: transactionHistory.transactions.keys.toList(), node: ScanNode(node!.uri, node!.useSSL), labels: walletAddresses.labels, + labelIndexes: walletAddresses.silentAddresses + .where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1) + .map((addr) => addr.index) + .toList(), isSingleScan: doSingleScan ?? false, )); @@ -238,6 +243,10 @@ abstract class ElectrumWalletBase final existingTxInfo = transactionHistory.transactions[txid]; if (existingTxInfo != null) { + final addressRecord = + walletAddresses.silentAddresses.firstWhere((addr) => addr.address == tx.to); + addressRecord.txCount += 1; + existingTxInfo.amount = tx.amount; existingTxInfo.confirmations = tx.confirmations; existingTxInfo.height = tx.height; @@ -1616,6 +1625,7 @@ class ScanData { final ElectrumClient electrumClient; final List transactionHistoryIds; final Map labels; + final List labelIndexes; final bool isSingleScan; ScanData({ @@ -1628,6 +1638,7 @@ class ScanData { required this.electrumClient, required this.transactionHistoryIds, required this.labels, + required this.labelIndexes, required this.isSingleScan, }); @@ -1642,6 +1653,7 @@ class ScanData { transactionHistoryIds: scanData.transactionHistoryIds, electrumClient: scanData.electrumClient, labels: scanData.labels, + labelIndexes: scanData.labelIndexes, isSingleScan: scanData.isSingleScan, ); } @@ -1714,8 +1726,9 @@ Future startRefresh(ScanData scanData) async { try { final electrumClient = await getElectrumConnection(); + // TODO: hardcoded values, if timed out decrease amount of blocks per request? final scanningBlockCount = - scanData.isSingleScan ? 1 : (scanData.network == BitcoinNetwork.testnet ? 50 : 10); + scanData.isSingleScan ? 1 : (scanData.network == BitcoinNetwork.testnet ? 25 : 10); Map? tweaks; try { @@ -1752,14 +1765,16 @@ Future startRefresh(ScanData scanData) async { final tweak = details["tweak"].toString(); try { - final spb = SilentPaymentBuilder(receiverTweak: tweak); - final addToWallet = spb.scanOutputs( - scanData.silentAddress.b_scan, - scanData.silentAddress.B_spend, - outputPubkeys.values - .map((o) => getScriptFromOutput(o[0].toString(), int.parse(o[1].toString()))) - .toList(), - precomputedLabels: scanData.labels, + final addToWallet = scanOutputs( + outputPubkeys.values.map((o) => o[0].toString()).toList(), + tweak, + Receiver( + scanData.silentAddress.b_scan.toHex(), + scanData.silentAddress.B_spend.toHex(), + scanData.network == BitcoinNetwork.testnet, + scanData.labelIndexes, + scanData.labelIndexes.length, + ), ); if (addToWallet.isEmpty) { @@ -1767,64 +1782,72 @@ Future startRefresh(ScanData scanData) async { continue; } - addToWallet.forEach((key, value) async { - final t_k = value.tweak; - - final addressRecord = BitcoinSilentPaymentAddressRecord( - value.output.address.toAddress(scanData.network), - index: 0, - isHidden: false, - isUsed: true, - network: scanData.network, - silentPaymentTweak: t_k, - type: SegwitAddresType.p2tr, - ); - - int? amount; - int? pos; - outputPubkeys.entries.firstWhere((k) { - final matches = k.value[0] == key; - if (matches) { - amount = int.parse(k.value[1].toString()); - pos = int.parse(k.key.toString()); - return true; - } - return false; + addToWallet.forEach((label, value) async { + (value as Map).forEach((output, tweak) async { + final t_k = tweak.toString(); + + final receivingOutputAddress = ECPublic.fromHex(output) + .toTaprootAddress(tweak: false) + .toAddress(scanData.network); + + final receivedAddressRecord = BitcoinSilentPaymentAddressRecord( + receivingOutputAddress, + index: 0, + isHidden: false, + isUsed: true, + network: scanData.network, + silentPaymentTweak: t_k, + type: SegwitAddresType.p2tr, + ); + + int? amount; + int? pos; + outputPubkeys.entries.firstWhere((k) { + final matches = k.value[0] == output; + if (matches) { + amount = int.parse(k.value[1].toString()); + pos = int.parse(k.key.toString()); + return true; + } + return false; + }); + + final json = { + 'address_record': receivedAddressRecord.toJSON(), + 'tx_hash': txid, + 'value': amount!, + 'tx_pos': pos!, + 'silent_payment_tweak': t_k, + }; + + final tx = BitcoinUnspent.fromJSON(receivedAddressRecord, json); + + final silentPaymentAddress = SilentPaymentAddress( + version: scanData.silentAddress.version, + B_scan: scanData.silentAddress.B_scan, + B_spend: label == "None" + ? scanData.silentAddress.B_spend + : scanData.silentAddress.B_spend + .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(label))), + hrp: scanData.silentAddress.hrp, + ); + + final txInfo = ElectrumTransactionInfo( + WalletType.bitcoin, + id: tx.hash, + height: syncHeight, + amount: amount!, + fee: 0, + direction: TransactionDirection.incoming, + isPending: false, + date: DateTime.now(), + confirmations: await getUpdatedNodeHeight() - int.parse(blockHeight) + 1, + to: silentPaymentAddress.toString(), + unspents: [tx], + ); + + scanData.sendPort.send({txInfo.id: txInfo}); }); - - final json = { - 'address_record': addressRecord.toJSON(), - 'tx_hash': txid, - 'value': amount!, - 'tx_pos': pos!, - 'silent_payment_tweak': t_k, - }; - - final tx = BitcoinUnspent.fromJSON(addressRecord, json); - - final txInfo = ElectrumTransactionInfo( - WalletType.bitcoin, - id: tx.hash, - height: syncHeight, - amount: amount!, - fee: 0, - direction: TransactionDirection.incoming, - isPending: false, - date: DateTime.now(), - confirmations: await getUpdatedNodeHeight() - int.parse(blockHeight) + 1, - to: value.label != null - ? SilentPaymentAddress( - version: scanData.silentAddress.version, - B_scan: scanData.silentAddress.B_scan.tweakAdd( - BigintUtils.fromBytes(BytesUtils.fromHexString(value.tweak))), - B_spend: scanData.silentAddress.B_spend, - hrp: scanData.silentAddress.hrp, - ).toString() - : scanData.silentAddress.toString(), - unspents: [tx], - ); - - scanData.sendPort.send({txInfo.id: txInfo}); }); } catch (_) {} } diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 4d11792431..e9099ddeec 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -60,7 +60,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { if (silentAddresses.length == 0) silentAddresses.add(BitcoinSilentPaymentAddressRecord( silentAddress.toString(), - index: 1, + index: 0, isHidden: false, name: "", silentPaymentTweak: null, @@ -268,7 +268,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) { final currentSilentAddressIndex = silentAddresses .where((addressRecord) => addressRecord.type != SegwitAddresType.p2tr) - .length + + .length - 1; this.currentSilentAddressIndex = currentSilentAddressIndex; diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 0cf836a115..bd3dd41894 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -80,7 +80,7 @@ packages: description: path: "." ref: cake-update-v3 - resolved-ref: fb4c0a0b6cf24628ddad7d3cdc58e4c918eff714 + resolved-ref: "3ddad3d1a9b78f49c9ef542962758400315d64a7" url: "https://github.com/cake-tech/bitcoin_base" source: git version: "4.0.0" @@ -198,6 +198,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" clock: dependency: transitive description: @@ -285,6 +293,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + ffigen: + dependency: transitive + description: + name: ffigen + sha256: d3e76c2ad48a4e7f93a29a162006f00eba46ce7c08194a77bb5c5e97d1b5ff0a + url: "https://pub.dev" + source: hosted + version: "8.0.2" file: dependency: transitive description: @@ -607,6 +623,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.3" + quiver: + dependency: transitive + description: + name: quiver + sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + url: "https://pub.dev" + source: hosted + version: "3.2.1" rxdart: dependency: "direct main" description: @@ -668,6 +692,15 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + sp_scanner: + dependency: "direct main" + description: + path: "." + ref: master + resolved-ref: de90b20f4250647d0f55f6bd5e7203710d0d5678 + url: "https://github.com/rafael-xmr/sp_scanner" + source: git + version: "0.0.1" stack_trace: dependency: transitive description: @@ -740,6 +773,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" vector_math: dependency: transitive description: @@ -788,6 +829,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: c566f4f804215d84a7a2c377667f546c6033d5b34b4f9e60dfb09d17c4e97826 + url: "https://pub.dev" + source: hosted + version: "2.2.0" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.0.6 <4.0.0" flutter: ">=3.10.0" diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index bf749ba9bf..4d4f5f6390 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -38,6 +38,10 @@ dependencies: git: url: https://github.com/cake-tech/blockchain_utils ref: cake-update-v1 + sp_scanner: + git: + url: https://github.com/rafael-xmr/sp_scanner + ref: master dev_dependencies: flutter_test: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 51db7c827d..40018d9693 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -297,6 +297,7 @@ class CWBitcoin extends Bitcoin { ); } + @override List getSilentPaymentAddresses(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.walletAddresses.silentAddresses @@ -304,6 +305,7 @@ class CWBitcoin extends Bitcoin { .toList(); } + @override List getSilentPaymentReceivedAddresses(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.walletAddresses.silentAddresses @@ -311,10 +313,12 @@ class CWBitcoin extends Bitcoin { .toList(); } + @override bool isBitcoinReceivePageOption(ReceivePageOption option) { return option is BitcoinReceivePageOption; } + @override BitcoinAddressType getOptionToType(ReceivePageOption option) { return (option as BitcoinReceivePageOption).toType(); } @@ -326,6 +330,7 @@ class CWBitcoin extends Bitcoin { return bitcoinWallet.silentPaymentsScanningActive; } + @override Future setScanningActive(Object wallet, SettingsStore settingsStore, bool active) async { final bitcoinWallet = wallet as ElectrumWallet; // TODO: always when setting to scanning active, will force switch nodes. Remove when not needed anymore @@ -338,6 +343,7 @@ class CWBitcoin extends Bitcoin { bitcoinWallet.setSilentPaymentsScanning(active); } + @override bool isTestnet(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.isTestnet ?? false; @@ -346,6 +352,7 @@ class CWBitcoin extends Bitcoin { @override int getHeightByDate({required DateTime date}) => getBitcoinHeightByDate(date: date); + @override Future rescan(Object wallet, SettingsStore settingsStore, {required int height, bool? doSingleScan}) async { final bitcoinWallet = wallet as ElectrumWallet; @@ -359,6 +366,7 @@ class CWBitcoin extends Bitcoin { bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan); } + @override bool getNodeIsCakeElectrs(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; final node = bitcoinWallet.node; @@ -366,6 +374,7 @@ class CWBitcoin extends Bitcoin { return node?.uri.host == '198.58.111.154' && node?.uri.port == 50002; } + @override void deleteSilentPaymentAddress(Object wallet, String address) { final bitcoinWallet = wallet as ElectrumWallet; bitcoinWallet.walletAddresses.deleteSilentPaymentAddress(address); diff --git a/pubspec_base.yaml b/pubspec_base.yaml index edc0f6f905..cd5e3cba0f 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -120,7 +120,7 @@ dev_dependencies: mobx_codegen: ^2.1.1 build_resolvers: ^2.0.9 hive_generator: ^1.1.3 - flutter_launcher_icons: ^0.11.0 + # flutter_launcher_icons: ^0.11.0 # check flutter_launcher_icons for usage pedantic: ^1.8.0 # replace https://github.com/dart-lang/lints#migrating-from-packagepedantic diff --git a/tool/configure.dart b/tool/configure.dart index 6eb8f0e6cd..a1a09c5bfd 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -89,6 +89,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/litecoin_wallet_service.dart'; import 'package:cw_core/get_height_by_date.dart'; +import 'package:cw_core/node.dart'; import 'package:mobx/mobx.dart'; """; const bitcoinCwPart = "part 'cw_bitcoin.dart';"; @@ -160,7 +161,7 @@ abstract class Bitcoin { BitcoinAddressType getOptionToType(ReceivePageOption option); bool hasTaprootInput(PendingTransaction pendingTransaction); bool getScanningActive(Object wallet); - void setScanningActive(Object wallet, bool active); + Future setScanningActive(Object wallet, SettingsStore settingsStore, bool active); bool isTestnet(Object wallet); Future replaceByFee(Object wallet, String transactionHash, String fee); @@ -169,7 +170,10 @@ abstract class Bitcoin { int getFeeAmountForPriority(Object wallet, TransactionPriority priority, int inputsCount, int outputsCount, {int? size}); int getFeeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, {int? size}); int getHeightByDate({required DateTime date}); - void rescan(Object wallet, {required int height, bool? doSingleScan}); + Future rescan(Object wallet, SettingsStore settingsStore, + {required int height, bool? doSingleScan}); + bool getNodeIsCakeElectrs(Object wallet); + void deleteSilentPaymentAddress(Object wallet, String address); } """; From 7f792fcd60679622f07ff6f42da3b180a79e12cf Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 12 Apr 2024 16:29:09 -0300 Subject: [PATCH 032/242] chore: node path --- lib/src/screens/nodes/widgets/node_form.dart | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/src/screens/nodes/widgets/node_form.dart b/lib/src/screens/nodes/widgets/node_form.dart index e8c4b0ab34..c83b82d668 100644 --- a/lib/src/screens/nodes/widgets/node_form.dart +++ b/lib/src/screens/nodes/widgets/node_form.dart @@ -95,18 +95,20 @@ class NodeForm extends StatelessWidget { ) ], ), - SizedBox(height: 10.0), - Row( - children: [ - Expanded( - child: BaseTextFormField( - controller: _pathController, - hintText: "/path", - validator: NodePathValidator(), - ), - ) - ], - ), + // if () ...[ + // SizedBox(height: 10.0), + // Row( + // children: [ + // Expanded( + // child: BaseTextFormField( + // controller: _pathController, + // hintText: "/path", + // validator: NodePathValidator(), + // ), + // ) + // ], + // ), + // ], SizedBox(height: 10.0), Row( children: [ From 2df4a17d5eb058b740677f680b26779377581335 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 12 Apr 2024 16:55:14 -0300 Subject: [PATCH 033/242] fix: check node based on network --- lib/bitcoin/cw_bitcoin.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 40018d9693..cf0cb783e4 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -371,7 +371,8 @@ class CWBitcoin extends Bitcoin { final bitcoinWallet = wallet as ElectrumWallet; final node = bitcoinWallet.node; - return node?.uri.host == '198.58.111.154' && node?.uri.port == 50002; + return node?.uri.host == '198.58.111.154' && + node?.uri.port == (wallet.network == BitcoinNetwork.testnet ? 50002 : 50001); } @override From 2b42b55bd016b43a03f689e097af5413f4a2fa45 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 12 Apr 2024 18:31:23 -0300 Subject: [PATCH 034/242] fix: missing txcount from addresses --- cw_bitcoin/lib/electrum_wallet.dart | 31 ++++++++++++++++------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 439a51a133..d3aac7ca9f 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -150,15 +150,15 @@ abstract class ElectrumWalletBase bool silentPaymentsScanningActive = false; @action - void setSilentPaymentsScanning(bool active) { + Future setSilentPaymentsScanning(bool active) async { silentPaymentsScanningActive = active; if (active) { - _setInitialHeight().then((_) { - if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { - _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); - } - }); + await _setInitialHeight(); + + if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { + _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); + } } else { _isolate?.then((runningIsolate) => runningIsolate.kill(priority: Isolate.immediate)); @@ -166,9 +166,8 @@ abstract class ElectrumWalletBase syncStatus = SyncedSyncStatus(); } else { if (electrumClient.uri != null) { - electrumClient.connectToUri(electrumClient.uri!).then((_) { - startSync(); - }); + await electrumClient.connectToUri(electrumClient.uri!); + startSync(); } } } @@ -242,11 +241,10 @@ abstract class ElectrumWalletBase [unspent.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord])); final existingTxInfo = transactionHistory.transactions[txid]; - if (existingTxInfo != null) { - final addressRecord = - walletAddresses.silentAddresses.firstWhere((addr) => addr.address == tx.to); - addressRecord.txCount += 1; + final txAlreadyExisted = existingTxInfo != null; + // Updating tx after re-scanned + if (txAlreadyExisted) { existingTxInfo.amount = tx.amount; existingTxInfo.confirmations = tx.confirmations; existingTxInfo.height = tx.height; @@ -276,6 +274,10 @@ abstract class ElectrumWalletBase transactionHistory.addOne(existingTxInfo); } } else { + final addressRecord = + walletAddresses.silentAddresses.firstWhere((addr) => addr.address == tx.to); + addressRecord.txCount += 1; + transactionHistory.addMany(message); } @@ -1726,7 +1728,7 @@ Future startRefresh(ScanData scanData) async { try { final electrumClient = await getElectrumConnection(); - // TODO: hardcoded values, if timed out decrease amount of blocks per request? + // TODO: hardcoded values, if timed out decrease amount of blocks per request? final scanningBlockCount = scanData.isSingleScan ? 1 : (scanData.network == BitcoinNetwork.testnet ? 25 : 10); @@ -1798,6 +1800,7 @@ Future startRefresh(ScanData scanData) async { network: scanData.network, silentPaymentTweak: t_k, type: SegwitAddresType.p2tr, + txCount: 1, ); int? amount; From 021347ae87567cdef8d2e7f9abcd3a40a25d98be Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 12 Apr 2024 18:32:04 -0300 Subject: [PATCH 035/242] style: padding in feature page cards --- lib/src/widgets/dashboard_card_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/widgets/dashboard_card_widget.dart b/lib/src/widgets/dashboard_card_widget.dart index 87d0430400..b0831c7cf9 100644 --- a/lib/src/widgets/dashboard_card_widget.dart +++ b/lib/src/widgets/dashboard_card_widget.dart @@ -33,7 +33,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget { child: Stack( children: [ Container( - padding: EdgeInsets.fromLTRB(20, 20, 40, 20), + padding: EdgeInsets.all(20), width: double.infinity, decoration: BoxDecoration( color: Theme.of(context).extension()!.syncedBackgroundColor, From 23d62213022858a36812fdb6a459323ff0a889fc Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 12 Apr 2024 19:33:00 -0300 Subject: [PATCH 036/242] fix: restore not getting all wallet addresses by type --- cw_bitcoin/lib/electrum_wallet.dart | 66 +++++++++++++++++------------ 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index d3aac7ca9f..0ec535ab33 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -1359,34 +1359,8 @@ abstract class ElectrumWalletBase final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); currentChainTip ??= await electrumClient.getCurrentBlockChainTip() ?? 0; - await Future.wait(ADDRESS_TYPES.map((type) { - final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type); - - return Future.wait(addressesByType.map((addressRecord) async { - final history = await _fetchAddressHistory(addressRecord, addressesSet, currentChainTip!); - - if (history.isNotEmpty) { - addressRecord.txCount = history.length; - historiesWithDetails.addAll(history); - - final matchedAddresses = - addressesByType.where((addr) => addr.isHidden == addressRecord.isHidden); - - final isLastUsedAddress = - history.isNotEmpty && addressRecord.address == matchedAddresses.last.address; - - if (isLastUsedAddress) { - await walletAddresses.discoverAddresses( - matchedAddresses.toList(), - addressRecord.isHidden, - (address, addressesSet) => - _fetchAddressHistory(address, addressesSet, currentChainTip!) - .then((history) => history.isNotEmpty ? address.address : null), - type: type); - } - } - })); - })); + await Future.wait(ADDRESS_TYPES.map( + (type) => fetchTransactionsForAddressType(addressesSet, historiesWithDetails, type))); return historiesWithDetails; } catch (e) { @@ -1395,6 +1369,42 @@ abstract class ElectrumWalletBase } } + Future fetchTransactionsForAddressType( + Set addressesSet, + Map historiesWithDetails, + BitcoinAddressType type, + ) async { + final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type); + final hiddenAddresses = addressesByType.where((addr) => addr.isHidden == true); + final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false); + + await Future.wait(addressesByType.map((addressRecord) async { + final history = await _fetchAddressHistory(addressRecord, addressesSet, currentChainTip!); + + if (history.isNotEmpty) { + addressRecord.txCount = history.length; + historiesWithDetails.addAll(history); + + final matchedAddresses = addressRecord.isHidden ? hiddenAddresses : receiveAddresses; + final isLastUsedAddress = history.isNotEmpty && matchedAddresses.last == addressRecord; + + if (isLastUsedAddress) { + // The last address by gap limit is used, discover new addresses for the same address type + await walletAddresses.discoverAddresses( + matchedAddresses.toList(), + addressRecord.isHidden, + (address, addressesSet) => _fetchAddressHistory(address, addressesSet, currentChainTip!) + .then((history) => history.isNotEmpty ? address.address : null), + type: type, + ); + + // Continue until the last address by this address type is not used yet + await fetchTransactionsForAddressType(addressesSet, historiesWithDetails, type); + } + } + })); + } + Future> _fetchAddressHistory( BitcoinAddressRecord addressRecord, Set addressesSet, int currentHeight) async { try { From 615db5a6d40bd26106781aa342d65eb68a87bef4 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 12 Apr 2024 20:02:46 -0300 Subject: [PATCH 037/242] fix: auto switch node broken --- lib/bitcoin/cw_bitcoin.dart | 20 ++++++++++++------- .../dashboard/dashboard_view_model.dart | 2 +- lib/view_model/rescan_view_model.dart | 2 +- tool/configure.dart | 5 ++--- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index cf0cb783e4..8ee3e16879 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -331,15 +331,20 @@ class CWBitcoin extends Bitcoin { } @override - Future setScanningActive(Object wallet, SettingsStore settingsStore, bool active) async { + Future setScanningActive(Object wallet, bool active) async { final bitcoinWallet = wallet as ElectrumWallet; + // TODO: always when setting to scanning active, will force switch nodes. Remove when not needed anymore if (!getNodeIsCakeElectrs(wallet)) { - final node = Node(useSSL: false, uri: '198.58.111.154:50002'); + final node = Node( + useSSL: false, + uri: '198.58.111.154:${(wallet.network == BitcoinNetwork.testnet ? 50002 : 50001)}', + ); node.type = WalletType.bitcoin; - settingsStore.nodes[WalletType.bitcoin] = node; + await bitcoinWallet.connectToNode(node: node); } + bitcoinWallet.setSilentPaymentsScanning(active); } @@ -353,14 +358,15 @@ class CWBitcoin extends Bitcoin { int getHeightByDate({required DateTime date}) => getBitcoinHeightByDate(date: date); @override - Future rescan(Object wallet, SettingsStore settingsStore, - {required int height, bool? doSingleScan}) async { + Future rescan(Object wallet, {required int height, bool? doSingleScan}) async { final bitcoinWallet = wallet as ElectrumWallet; // TODO: always when setting to scanning active, will force switch nodes. Remove when not needed anymore if (!getNodeIsCakeElectrs(wallet)) { - final node = Node(useSSL: false, uri: '198.58.111.154:50002'); + final node = Node( + useSSL: false, + uri: '198.58.111.154:${(wallet.network == BitcoinNetwork.testnet ? 50002 : 50001)}', + ); node.type = WalletType.bitcoin; - settingsStore.nodes[WalletType.bitcoin] = node; await bitcoinWallet.connectToNode(node: node); } bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan); diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 3e679c3810..f91086ef52 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -319,7 +319,7 @@ abstract class DashboardViewModelBase with Store { silentPaymentsScanningActive = active; if (hasSilentPayments) { - bitcoin!.setScanningActive(wallet, settingsStore, active); + bitcoin!.setScanningActive(wallet, active); } } diff --git a/lib/view_model/rescan_view_model.dart b/lib/view_model/rescan_view_model.dart index 4008ee9f1a..f78c51f681 100644 --- a/lib/view_model/rescan_view_model.dart +++ b/lib/view_model/rescan_view_model.dart @@ -39,7 +39,7 @@ abstract class RescanViewModelBase with Store { wallet.rescan(height: restoreHeight); wallet.transactionHistory.clear(); } else { - bitcoin!.rescan(wallet, settingsStore, height: restoreHeight, doSingleScan: doSingleScan); + bitcoin!.rescan(wallet, height: restoreHeight, doSingleScan: doSingleScan); } state = RescanWalletState.none; } diff --git a/tool/configure.dart b/tool/configure.dart index ae9ab0c355..70a74123c0 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -161,7 +161,7 @@ abstract class Bitcoin { BitcoinAddressType getOptionToType(ReceivePageOption option); bool hasTaprootInput(PendingTransaction pendingTransaction); bool getScanningActive(Object wallet); - Future setScanningActive(Object wallet, SettingsStore settingsStore, bool active); + Future setScanningActive(Object wallet, bool active); bool isTestnet(Object wallet); Future replaceByFee(Object wallet, String transactionHash, String fee); @@ -170,8 +170,7 @@ abstract class Bitcoin { int getFeeAmountForPriority(Object wallet, TransactionPriority priority, int inputsCount, int outputsCount, {int? size}); int getFeeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, {int? size}); int getHeightByDate({required DateTime date}); - Future rescan(Object wallet, SettingsStore settingsStore, - {required int height, bool? doSingleScan}); + Future rescan(Object wallet, {required int height, bool? doSingleScan}); bool getNodeIsCakeElectrs(Object wallet); void deleteSilentPaymentAddress(Object wallet, String address); } From a887ea74b5267111e89ab801f63cc6ec2dcaf9c4 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 15 Apr 2024 08:31:26 -0300 Subject: [PATCH 038/242] fix: silent payment txs not being restored --- cw_bitcoin/lib/electrum_transaction_info.dart | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index a0390c64a7..d98d7a5b4c 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -158,8 +158,10 @@ class ElectrumTransactionInfo extends TransactionInfo { } factory ElectrumTransactionInfo.fromJson(Map data, WalletType type) { - final inputAddresses = data['inputAddresses'] as List; - final outputAddresses = data['outputAddresses'] as List; + final inputAddresses = data['inputAddresses'] as List? ?? []; + final outputAddresses = data['outputAddresses'] as List? ?? []; + final unspents = data['unspents'] as List? ?? []; + return ElectrumTransactionInfo( type, id: data['id'] as String, @@ -175,13 +177,11 @@ class ElectrumTransactionInfo extends TransactionInfo { outputAddresses: outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(), to: data['to'] as String?, - unspents: data['unspents'] != null - ? (data['unspents'] as List) - .map((unspent) => BitcoinUnspent.fromJSON( - BitcoinSilentPaymentAddressRecord.fromJSON(unspent['address_record'].toString()), - unspent as Map)) - .toList() - : null, + unspents: unspents + .map((unspent) => BitcoinUnspent.fromJSON( + BitcoinSilentPaymentAddressRecord.fromJSON(unspent['address_record'].toString()), + unspent as Map)) + .toList(), ); } From e4156ba28270a4cf5657976f91a5048fe5b10245 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Wed, 17 Apr 2024 16:35:11 -0300 Subject: [PATCH 039/242] feat: change scanning to subscription model, sync improvements --- cw_bitcoin/lib/bitcoin_address_record.dart | 3 +- cw_bitcoin/lib/bitcoin_unspent.dart | 45 +- cw_bitcoin/lib/bitcoin_wallet.dart | 10 - cw_bitcoin/lib/electrum.dart | 38 +- cw_bitcoin/lib/electrum_balance.dart | 2 +- cw_bitcoin/lib/electrum_transaction_info.dart | 7 +- cw_bitcoin/lib/electrum_wallet.dart | 738 ++++++++++-------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 17 +- cw_bitcoin/lib/exceptions.dart | 6 +- .../lib/pending_bitcoin_transaction.dart | 4 + cw_core/lib/exceptions.dart | 7 +- cw_core/lib/sync_status.dart | 23 +- cw_core/lib/unspent_coins_info.dart | 8 +- lib/bitcoin/cw_bitcoin.dart | 8 +- lib/core/sync_status_title.dart | 4 + lib/entities/default_settings_migration.dart | 12 +- .../screens/receive/widgets/address_cell.dart | 2 +- lib/src/screens/send/send_page.dart | 11 +- .../unspent_coins_list_page.dart | 1 + .../widgets/unspent_coins_list_item.dart | 28 +- lib/view_model/send/send_view_model.dart | 6 + .../unspent_coins/unspent_coins_item.dart | 6 +- .../unspent_coins_list_view_model.dart | 44 +- .../wallet_address_list_view_model.dart | 7 +- res/values/strings_ar.arb | 2 + res/values/strings_bg.arb | 2 + res/values/strings_cs.arb | 2 + res/values/strings_de.arb | 2 + res/values/strings_en.arb | 2 + res/values/strings_es.arb | 2 + res/values/strings_fr.arb | 2 + res/values/strings_ha.arb | 2 + res/values/strings_hi.arb | 2 + res/values/strings_hr.arb | 2 + res/values/strings_id.arb | 2 + res/values/strings_it.arb | 2 + res/values/strings_ja.arb | 2 + res/values/strings_ko.arb | 2 + res/values/strings_my.arb | 2 + res/values/strings_nl.arb | 2 + res/values/strings_pl.arb | 2 + res/values/strings_pt.arb | 4 +- res/values/strings_ru.arb | 2 + res/values/strings_th.arb | 2 + res/values/strings_tl.arb | 2 + res/values/strings_tr.arb | 2 + res/values/strings_uk.arb | 2 + res/values/strings_ur.arb | 2 + res/values/strings_yo.arb | 2 + res/values/strings_zh.arb | 2 + tool/configure.dart | 1 + 51 files changed, 691 insertions(+), 401 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 220298e55a..bf36e6fb99 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -90,7 +90,8 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { String? scriptHash; - String updateScriptHash(BasedUtxoNetwork network) { + String getScriptHash(BasedUtxoNetwork network) { + if (scriptHash != null) return scriptHash!; scriptHash = sh.scriptHash(address, network: network); return scriptHash!; } diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index b2c1d90c4c..3691a7a22a 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -2,20 +2,54 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_core/unspent_transaction_output.dart'; class BitcoinUnspent extends Unspent { - BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout, - {this.silentPaymentTweak}) + BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout) : bitcoinAddressRecord = addressRecord, super(addressRecord.address, hash, value, vout, null); - factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord address, Map json) => + factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map json) => BitcoinUnspent( - address, + address ?? BitcoinAddressRecord.fromJSON(json['address_record'].toString()), + json['tx_hash'] as String, + json['value'] as int, + json['tx_pos'] as int, + ); + + Map toJson() { + final json = { + 'address_record': bitcoinAddressRecord.toJSON(), + 'tx_hash': hash, + 'value': value, + 'tx_pos': vout, + }; + return json; + } + + final BaseBitcoinAddressRecord bitcoinAddressRecord; +} + +class BitcoinSilentPaymentsUnspent extends BitcoinUnspent { + BitcoinSilentPaymentsUnspent( + BitcoinSilentPaymentAddressRecord addressRecord, + String hash, + int value, + int vout, { + required this.silentPaymentTweak, + required this.silentPaymentLabel, + }) : super(addressRecord, hash, value, vout); + + @override + factory BitcoinSilentPaymentsUnspent.fromJSON( + BitcoinSilentPaymentAddressRecord? address, Map json) => + BitcoinSilentPaymentsUnspent( + address ?? BitcoinSilentPaymentAddressRecord.fromJSON(json['address_record'].toString()), json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int, silentPaymentTweak: json['silent_payment_tweak'] as String?, + silentPaymentLabel: json['silent_payment_label'] as String?, ); + @override Map toJson() { final json = { 'address_record': bitcoinAddressRecord.toJSON(), @@ -23,10 +57,11 @@ class BitcoinUnspent extends Unspent { 'value': value, 'tx_pos': vout, 'silent_payment_tweak': silentPaymentTweak, + 'silent_payment_label': silentPaymentLabel, }; return json; } - final BaseBitcoinAddressRecord bitcoinAddressRecord; String? silentPaymentTweak; + String? silentPaymentLabel; } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index e0218eff55..a319258177 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -64,19 +64,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { ), ); - hasSilentPaymentsScanning = true; - autorun((_) { this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; }); - - reaction((_) => walletAddresses.addressPageType, (BitcoinAddressType addressPageType) { - final prev = hasSilentPaymentsScanning; - hasSilentPaymentsScanning = addressPageType == SilentPaymentsAddresType.p2sp; - if (prev != hasSilentPaymentsScanning) { - startSync(); - } - }); } static Future create({ diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 6c532a7cef..e9383dda59 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -36,14 +36,15 @@ class ElectrumClient { _errors = {}, unterminatedString = ''; - static const connectionTimeout = Duration(seconds: 300); - static const aliveTimerDuration = Duration(seconds: 300); + static const connectionTimeout = Duration(seconds: 10); + static const aliveTimerDuration = Duration(seconds: 10); bool get isConnected => _isConnected; Socket? socket; void Function(bool)? onConnectionStatusChange; int _id; final Map _tasks; + Map get tasks => _tasks; final Map _errors; bool _isConnected; Timer? _aliveTimer; @@ -277,11 +278,18 @@ class ElectrumClient { Future> getHeader({required int height}) async => await call(method: 'blockchain.block.get_header', params: [height]) as Map; - Future> getTweaks({required int height, required int count}) async => - await callWithTimeout( - method: 'blockchain.block.tweaks', - params: [height, count], - timeout: 10000) as Map; + BehaviorSubject? tweaksSubscribe({required int height}) { + _id += 1; + return subscribe( + id: 'blockchain.tweaks.subscribe', + method: 'blockchain.tweaks.subscribe', + params: [height], + ); + } + + Future> getTweaks({required int height}) async => + await callWithTimeout(method: 'blockchain.tweaks.get', params: [height], timeout: 10000) + as Map; Future estimatefee({required int p}) => call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) { @@ -325,9 +333,6 @@ class ElectrumClient { }); Future> feeRates({BasedUtxoNetwork? network}) async { - if (network == BitcoinNetwork.testnet) { - return [1, 1, 1]; - } try { final topDoubleString = await estimatefee(p: 1); final middleDoubleString = await estimatefee(p: 5); @@ -349,7 +354,7 @@ class ElectrumClient { // "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4" // } Future getCurrentBlockChainTip() => - call(method: 'blockchain.headers.subscribe').then((result) { + callWithTimeout(method: 'blockchain.headers.subscribe').then((result) { if (result is Map) { return result["height"] as int; } @@ -357,6 +362,12 @@ class ElectrumClient { return null; }); + BehaviorSubject? chainTipSubscribe() { + _id += 1; + return subscribe( + id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe'); + } + BehaviorSubject? chainTipUpdate() { _id += 1; return subscribe( @@ -456,6 +467,11 @@ class ElectrumClient { _tasks[id]?.subject?.add(params.last); break; + case 'blockchain.tweaks.subscribe': + case 'blockchain.headers.subscribe': + final params = request['params'] as List; + _tasks[method]?.subject?.add(params.last); + break; default: break; } diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart index 45de7de6d5..15d6843d87 100644 --- a/cw_bitcoin/lib/electrum_balance.dart +++ b/cw_bitcoin/lib/electrum_balance.dart @@ -23,7 +23,7 @@ class ElectrumBalance extends Balance { } int confirmed; - final int unconfirmed; + int unconfirmed; final int frozen; @override diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index d98d7a5b4c..d06cfe9de8 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -18,7 +18,7 @@ class ElectrumTransactionBundle { } class ElectrumTransactionInfo extends TransactionInfo { - List? unspents; + List? unspents; ElectrumTransactionInfo(this.type, {required String id, @@ -178,9 +178,8 @@ class ElectrumTransactionInfo extends TransactionInfo { outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(), to: data['to'] as String?, unspents: unspents - .map((unspent) => BitcoinUnspent.fromJSON( - BitcoinSilentPaymentAddressRecord.fromJSON(unspent['address_record'].toString()), - unspent as Map)) + .map((unspent) => + BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map)) .toList(), ); } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 0ec535ab33..b2962f1e0d 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -91,7 +91,13 @@ abstract class ElectrumWalletBase transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password); reaction((_) => syncStatus, (SyncStatus syncStatus) { - silentPaymentsScanningActive = syncStatus is SyncingSyncStatus; + if (syncStatus is! AttemptingSyncStatus) + silentPaymentsScanningActive = syncStatus is SyncingSyncStatus; + + if (syncStatus is NotConnectedSyncStatus) { + // Needs to re-subscribe to all scripthashes when reconnected + _scripthashesUpdateSubject = {}; + } }); } @@ -122,14 +128,7 @@ abstract class ElectrumWalletBase @observable SyncStatus syncStatus; - List get scriptHashes => walletAddresses.allAddresses - .map((addr) => scriptHash(addr.address, network: network)) - .toList(); - - List get publicScriptHashes => walletAddresses.allAddresses - .where((addr) => !addr.isHidden) - .map((addr) => scriptHash(addr.address, network: network)) - .toList(); + Set get addressesSet => walletAddresses.allAddresses.map((addr) => addr.address).toSet(); String get xpub => hd.base58!; @@ -142,8 +141,8 @@ abstract class ElectrumWalletBase @override bool? isTestnet; - @observable - bool hasSilentPaymentsScanning = false; + bool get hasSilentPaymentsScanning => type == WalletType.bitcoin; + @observable bool nodeSupportsSilentPayments = true; @observable @@ -151,13 +150,14 @@ abstract class ElectrumWalletBase @action Future setSilentPaymentsScanning(bool active) async { + syncStatus = AttemptingSyncStatus(); silentPaymentsScanningActive = active; if (active) { await _setInitialHeight(); - if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { - _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); + if (await currentChainTip > walletInfo.restoreHeight) { + _setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip); } } else { _isolate?.then((runningIsolate) => runningIsolate.kill(priority: Isolate.immediate)); @@ -174,7 +174,11 @@ abstract class ElectrumWalletBase } @observable - int? currentChainTip; + int? _currentChainTip; + + @computed + Future get currentChainTip async => + _currentChainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; @override BitcoinWalletKeys get keys => @@ -183,7 +187,9 @@ abstract class ElectrumWalletBase String _password; List unspentCoins; List _feeRates; + // ignore: prefer_final_fields Map?> _scripthashesUpdateSubject; + // ignore: prefer_final_fields BehaviorSubject? _chainTipUpdateSubject; bool _isTransactionUpdating; Future? _isolate; @@ -201,8 +207,8 @@ abstract class ElectrumWalletBase } @action - Future _setListeners(int height, {int? chainTip, bool? doSingleScan}) async { - final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; + Future _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async { + final chainTip = chainTipParam ?? await currentChainTip; syncStatus = AttemptingSyncStatus(); if (_isolate != null) { @@ -218,7 +224,7 @@ abstract class ElectrumWalletBase silentAddress: walletAddresses.silentAddress!, network: network, height: height, - chainTip: currentChainTip, + chainTip: chainTip, electrumClient: ElectrumClient(), transactionHistoryIds: transactionHistory.transactions.keys.toList(), node: ScanNode(node!.uri, node!.useSSL), @@ -237,12 +243,33 @@ abstract class ElectrumWalletBase final tx = map.value; if (tx.unspents != null) { - tx.unspents!.forEach((unspent) => walletAddresses.addSilentAddresses( - [unspent.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord])); - final existingTxInfo = transactionHistory.transactions[txid]; final txAlreadyExisted = existingTxInfo != null; + void updateSilentAddressRecord(BitcoinSilentPaymentsUnspent unspent) { + final silentAddress = walletAddresses.silentAddress!; + final silentPaymentAddress = SilentPaymentAddress( + version: silentAddress.version, + B_scan: silentAddress.B_scan, + B_spend: unspent.silentPaymentLabel != null + ? silentAddress.B_spend.tweakAdd( + BigintUtils.fromBytes( + BytesUtils.fromHexString(unspent.silentPaymentLabel!)), + ) + : silentAddress.B_spend, + hrp: silentAddress.hrp, + ); + + final addressRecord = walletAddresses.silentAddresses.firstWhereOrNull( + (address) => address.address == silentPaymentAddress.toString()); + addressRecord?.txCount += 1; + addressRecord?.balance += unspent.value; + + walletAddresses.addSilentAddresses( + [unspent.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord], + ); + } + // Updating tx after re-scanned if (txAlreadyExisted) { existingTxInfo.amount = tx.amount; @@ -258,37 +285,39 @@ abstract class ElectrumWalletBase .toList(); if (newUnspents.isNotEmpty) { + newUnspents.forEach(updateSilentAddressRecord); + existingTxInfo.unspents ??= []; existingTxInfo.unspents!.addAll(newUnspents); - final amount = newUnspents.length > 1 + final newAmount = newUnspents.length > 1 ? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent) : newUnspents[0].value; if (existingTxInfo.direction == TransactionDirection.incoming) { - existingTxInfo.amount += amount; - } else { - existingTxInfo.amount = amount; - existingTxInfo.direction = TransactionDirection.incoming; + existingTxInfo.amount += newAmount; } + + // Updates existing TX transactionHistory.addOne(existingTxInfo); + // Update balance record + balance[currency]!.confirmed += newAmount; } } else { - final addressRecord = - walletAddresses.silentAddresses.firstWhere((addr) => addr.address == tx.to); - addressRecord.txCount += 1; + // else: First time seeing this TX after scanning + tx.unspents!.forEach(updateSilentAddressRecord); + // Add new TX record transactionHistory.addMany(message); + // Update balance record + balance[currency]!.confirmed += tx.amount; } - await transactionHistory.save(); - await updateUnspent(); - await updateBalance(); + await updateAllUnspents(); } } } - // check if is a SyncStatus type since "is SyncStatus" doesn't work here if (message is SyncResponse) { if (message.syncStatus is UnsupportedSyncStatus) { nodeSupportsSilentPayments = false; @@ -296,7 +325,6 @@ abstract class ElectrumWalletBase syncStatus = message.syncStatus; walletInfo.restoreHeight = message.height; - await walletInfo.save(); } } } @@ -305,32 +333,25 @@ abstract class ElectrumWalletBase @override Future startSync() async { try { - syncStatus = AttemptingSyncStatus(); + syncStatus = SyncronizingSyncStatus(); if (hasSilentPaymentsScanning) { - try { - await _setInitialHeight(); - } catch (_) {} + await _setInitialHeight(); } - if (silentPaymentsScanningActive) { - if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { - _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); - } + await _subscribeForUpdates(); + + int finished = 0; + void checkFinishedAllUpdates(void _) { + finished++; + if (finished == 2) syncStatus = SyncedSyncStatus(); } + // Always await first as it discovers new addresses in the process await updateTransactions(); - _subscribeForUpdates(); - await updateUnspent(); - await updateBalance(); - _feeRates = await electrumClient.feeRates(network: network); - - Timer.periodic( - const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates()); - if (!silentPaymentsScanningActive || walletInfo.restoreHeight == currentChainTip) { - syncStatus = SyncedSyncStatus(); - } + updateAllUnspents().then(checkFinishedAllUpdates); + updateBalance().then(checkFinishedAllUpdates); } catch (e, stacktrace) { print(stacktrace); print(e.toString()); @@ -338,29 +359,39 @@ abstract class ElectrumWalletBase } } + @action + Future updateFeeRates() async { + final feeRates = await electrumClient.feeRates(network: network); + if (feeRates != [0, 0, 0]) { + _feeRates = feeRates; + } + } + Node? node; @action @override Future connectToNode({required Node node}) async { + final differentNode = this.node?.uri != node.uri || this.node?.useSSL != node.useSSL; this.node = node; try { syncStatus = ConnectingSyncStatus(); - if (!electrumClient.isConnected) { - await electrumClient.close(); - } + electrumClient.onConnectionStatusChange = null; + await electrumClient.close(); - electrumClient.onConnectionStatusChange = (bool isConnected) async { - if (isConnected) { - syncStatus = ConnectedSyncStatus(); - } else if (isConnected == false) { - syncStatus = LostConnectionSyncStatus(); - } - }; + await Timer(Duration(seconds: differentNode ? 0 : 10), () async { + electrumClient.onConnectionStatusChange = (bool isConnected) async { + if (isConnected && syncStatus is! SyncedSyncStatus) { + syncStatus = ConnectedSyncStatus(); + } else if (!isConnected) { + syncStatus = LostConnectionSyncStatus(); + } + }; - await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); + await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); + }); } catch (e) { print(e.toString()); syncStatus = FailedSyncStatus(); @@ -436,8 +467,15 @@ abstract class ElectrumWalletBase for (int i = 0; i < unspentCoins.length; i++) { final utx = unspentCoins[i]; - if (utx.isSending) { - spendsCPFP = utx.confirmations == 0; + if (utx.isSending && !utx.isFrozen) { + if (hasSilentPayment) { + // Check inputs for shared secret derivation + if (utx.bitcoinAddressRecord.type == SegwitAddresType.p2wsh) { + throw BitcoinTransactionSilentPaymentsNotSupported(); + } + } + + if (!spendsCPFP) spendsCPFP = utx.confirmations == 0; allInputsAmount += utx.value; @@ -447,11 +485,10 @@ abstract class ElectrumWalletBase bool? isSilentPayment = false; if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { + final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( BigintUtils.fromBytes( - BytesUtils.fromHexString( - (utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord).silentPaymentTweak!, - ), + BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!), ), ); spendsSilentPayment = true; @@ -464,7 +501,11 @@ abstract class ElectrumWalletBase ); } - inputPrivKeyInfos.add(ECPrivateInfo(privkey, address.type == SegwitAddresType.p2tr)); + inputPrivKeyInfos.add(ECPrivateInfo( + privkey, + address.type == SegwitAddresType.p2tr, + tweak: !isSilentPayment, + )); inputPubKeys.add(privkey.getPublic()); vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); @@ -520,6 +561,10 @@ abstract class ElectrumWalletBase // Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change int amount = allInputsAmount - fee; + if (amount <= 0) { + throw BitcoinTransactionWrongBalanceException(); + } + // Attempting to send less than the dust limit if (_isBelowDust(amount)) { throw BitcoinTransactionNoDustException(); @@ -580,11 +625,10 @@ abstract class ElectrumWalletBase bool? isSilentPayment = false; if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { + final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( BigintUtils.fromBytes( - BytesUtils.fromHexString( - (utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord).silentPaymentTweak!, - ), + BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!), ), ); spendsSilentPayment = true; @@ -597,7 +641,11 @@ abstract class ElectrumWalletBase ); } - inputPrivKeyInfos.add(ECPrivateInfo(privkey, address.type == SegwitAddresType.p2tr)); + inputPrivKeyInfos.add(ECPrivateInfo( + privkey, + address.type == SegwitAddresType.p2tr, + tweak: !isSilentPayment, + )); inputPubKeys.add(privkey.getPublic()); vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); @@ -637,6 +685,16 @@ abstract class ElectrumWalletBase int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount; if (amountLeftForChangeAndFee <= 0) { + if (!spendingAllCoins) { + return estimateTxForAmount( + credentialsAmount, + outputs, + feeRate, + inputsCount: utxos.length + 1, + memo: memo, + hasSilentPayment: hasSilentPayment, + ); + } throw BitcoinTransactionWrongBalanceException(); } @@ -684,6 +742,7 @@ abstract class ElectrumWalletBase // Still has inputs to spend before failing if (!spendingAllCoins) { + outputs.removeLast(); return estimateTxForAmount( credentialsAmount, outputs, @@ -730,6 +789,7 @@ abstract class ElectrumWalletBase outputs.removeLast(); } + outputs.removeLast(); return estimateTxForAmount( credentialsAmount, outputs, @@ -1025,7 +1085,8 @@ abstract class ElectrumWalletBase Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - Future updateUnspent() async { + @action + Future updateAllUnspents() async { List updatedUnspentCoins = []; if (hasSilentPaymentsScanning) { @@ -1037,20 +1098,9 @@ abstract class ElectrumWalletBase }); } - final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); - - await Future.wait(walletAddresses.allAddresses.map((address) => electrumClient - .getListUnspentWithAddress(address.address, network) - .then((unspent) => Future.forEach>(unspent, (unspent) async { - try { - final coin = BitcoinUnspent.fromJSON(address, unspent); - final tx = await fetchTransactionInfo( - hash: coin.hash, height: 0, myAddresses: addressesSet); - coin.isChange = tx?.direction == TransactionDirection.outgoing; - coin.confirmations = tx?.confirmations; - updatedUnspentCoins.add(coin); - } catch (_) {} - })))); + await Future.wait(walletAddresses.allAddresses.map((address) async { + updatedUnspentCoins.addAll(await fetchUnspent(address)); + })); unspentCoins = updatedUnspentCoins; @@ -1072,6 +1122,7 @@ abstract class ElectrumWalletBase coin.isFrozen = coinInfo.isFrozen; coin.isSending = coinInfo.isSending; coin.note = coinInfo.note; + coin.bitcoinAddressRecord.balance += coinInfo.value; } else { _addCoinInfo(coin); } @@ -1081,6 +1132,55 @@ abstract class ElectrumWalletBase await _refreshUnspentCoinsInfo(); } + @action + Future updateUnspents(BitcoinAddressRecord address) async { + final newUnspentCoins = await fetchUnspent(address); + + if (newUnspentCoins.isNotEmpty) { + unspentCoins.addAll(newUnspentCoins); + + newUnspentCoins.forEach((coin) { + final coinInfoList = unspentCoinsInfo.values.where( + (element) => + element.walletId.contains(id) && + element.hash.contains(coin.hash) && + element.vout == coin.vout, + ); + + if (coinInfoList.isNotEmpty) { + final coinInfo = coinInfoList.first; + + coin.isFrozen = coinInfo.isFrozen; + coin.isSending = coinInfo.isSending; + coin.note = coinInfo.note; + coin.bitcoinAddressRecord.balance += coinInfo.value; + } else { + _addCoinInfo(coin); + } + }); + } + } + + @action + Future> fetchUnspent(BitcoinAddressRecord address) async { + final unspents = await electrumClient.getListUnspent(address.getScriptHash(network)); + + List updatedUnspentCoins = []; + + await Future.wait(unspents.map((unspent) async { + try { + final coin = BitcoinUnspent.fromJSON(address, unspent); + final tx = await fetchTransactionInfo(hash: coin.hash, height: 0); + coin.isChange = address.isHidden; + coin.confirmations = tx?.confirmations; + + updatedUnspentCoins.add(coin); + } catch (_) {} + })); + + return updatedUnspentCoins; + } + @action Future _addCoinInfo(BitcoinUnspent coin) async { final newInfo = UnspentCoinsInfo( @@ -1093,6 +1193,7 @@ abstract class ElectrumWalletBase value: coin.value, vout: coin.vout, isChange: coin.isChange, + isSilentPayment: coin is BitcoinSilentPaymentsUnspent, ); await unspentCoinsInfo.add(newInfo); @@ -1309,8 +1410,8 @@ abstract class ElectrumWalletBase time = status["block_time"] as int?; final height = status["block_height"] as int? ?? 0; - confirmations = - height > 0 ? (await electrumClient.getCurrentBlockChainTip())! - height + 1 : 0; + final tip = await currentChainTip; + if (tip > 0) confirmations = height > 0 ? tip - height + 1 : 0; } else { final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); @@ -1335,18 +1436,15 @@ abstract class ElectrumWalletBase } Future fetchTransactionInfo( - {required String hash, - required int height, - required Set myAddresses, - bool? retryOnFailure}) async { + {required String hash, required int height, bool? retryOnFailure}) async { try { return ElectrumTransactionInfo.fromElectrumBundle( await getTransactionExpanded(hash: hash), walletInfo.type, network, - addresses: myAddresses, height: height); + addresses: addressesSet, height: height); } catch (e) { if (e is FormatException && retryOnFailure == true) { await Future.delayed(const Duration(seconds: 2)); - return fetchTransactionInfo(hash: hash, height: height, myAddresses: myAddresses); + return fetchTransactionInfo(hash: hash, height: height); } return null; } @@ -1356,11 +1454,15 @@ abstract class ElectrumWalletBase Future> fetchTransactions() async { try { final Map historiesWithDetails = {}; - final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); - currentChainTip ??= await electrumClient.getCurrentBlockChainTip() ?? 0; - await Future.wait(ADDRESS_TYPES.map( - (type) => fetchTransactionsForAddressType(addressesSet, historiesWithDetails, type))); + if (type == WalletType.bitcoin) { + await Future.wait(ADDRESS_TYPES + .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); + } else if (type == WalletType.bitcoinCash) { + await fetchTransactionsForAddressType(historiesWithDetails, P2pkhAddressType.p2pkh); + } else if (type == WalletType.litecoin) { + await fetchTransactionsForAddressType(historiesWithDetails, SegwitAddresType.p2wpkh); + } return historiesWithDetails; } catch (e) { @@ -1370,7 +1472,6 @@ abstract class ElectrumWalletBase } Future fetchTransactionsForAddressType( - Set addressesSet, Map historiesWithDetails, BitcoinAddressType type, ) async { @@ -1379,39 +1480,50 @@ abstract class ElectrumWalletBase final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false); await Future.wait(addressesByType.map((addressRecord) async { - final history = await _fetchAddressHistory(addressRecord, addressesSet, currentChainTip!); + final history = await _fetchAddressHistory(addressRecord, await currentChainTip); if (history.isNotEmpty) { addressRecord.txCount = history.length; historiesWithDetails.addAll(history); final matchedAddresses = addressRecord.isHidden ? hiddenAddresses : receiveAddresses; - final isLastUsedAddress = history.isNotEmpty && matchedAddresses.last == addressRecord; + final isUsedAddressUnderGap = matchedAddresses.toList().indexOf(addressRecord) >= + matchedAddresses.length - + (addressRecord.isHidden + ? ElectrumWalletAddressesBase.defaultChangeAddressesCount + : ElectrumWalletAddressesBase.defaultReceiveAddressesCount); - if (isLastUsedAddress) { - // The last address by gap limit is used, discover new addresses for the same address type + if (isUsedAddressUnderGap) { + final prevLength = walletAddresses.allAddresses.length; + + // Discover new addresses for the same address type until the gap limit is respected await walletAddresses.discoverAddresses( matchedAddresses.toList(), addressRecord.isHidden, - (address, addressesSet) => _fetchAddressHistory(address, addressesSet, currentChainTip!) - .then((history) => history.isNotEmpty ? address.address : null), + (address) async { + await _subscribeForUpdates(); + return _fetchAddressHistory(address, await currentChainTip) + .then((history) => history.isNotEmpty ? address.address : null); + }, type: type, ); - // Continue until the last address by this address type is not used yet - await fetchTransactionsForAddressType(addressesSet, historiesWithDetails, type); + final newLength = walletAddresses.allAddresses.length; + + if (newLength > prevLength) { + await fetchTransactionsForAddressType(historiesWithDetails, type); + } } } })); } Future> _fetchAddressHistory( - BitcoinAddressRecord addressRecord, Set addressesSet, int currentHeight) async { + BitcoinAddressRecord addressRecord, int? currentHeight) async { try { final Map historiesWithDetails = {}; - final history = await electrumClient - .getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network)); + final history = await electrumClient.getHistory(addressRecord.getScriptHash(network)); if (history.isNotEmpty) { addressRecord.setAsUsed(); @@ -1425,14 +1537,13 @@ abstract class ElectrumWalletBase if (height > 0) { storedTx.height = height; // the tx's block itself is the first confirmation so add 1 - storedTx.confirmations = currentHeight - height + 1; + if (currentHeight != null) storedTx.confirmations = currentHeight - height + 1; storedTx.isPending = storedTx.confirmations == 0; } historiesWithDetails[txid] = storedTx; } else { - final tx = await fetchTransactionInfo( - hash: txid, height: height, myAddresses: addressesSet, retryOnFailure: true); + final tx = await fetchTransactionInfo(hash: txid, height: height, retryOnFailure: true); if (tx != null) { historiesWithDetails[txid] = tx; @@ -1462,9 +1573,9 @@ abstract class ElectrumWalletBase return; } - transactionHistory.transactions.values.forEach((tx) { - if (tx.unspents != null && currentChainTip != null) { - tx.confirmations = currentChainTip! - tx.height + 1; + transactionHistory.transactions.values.forEach((tx) async { + if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) { + tx.confirmations = await currentChainTip - tx.height + 1; } }); @@ -1479,15 +1590,24 @@ abstract class ElectrumWalletBase } } - void _subscribeForUpdates() async { - scriptHashes.forEach((sh) async { + Future _subscribeForUpdates() async { + final unsubscribedScriptHashes = walletAddresses.allAddresses.where( + (address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)), + ); + + await Future.wait(unsubscribedScriptHashes.map((address) async { + final sh = address.getScriptHash(network); await _scripthashesUpdateSubject[sh]?.close(); - _scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh); + _scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh); _scripthashesUpdateSubject[sh]?.listen((event) async { try { - await updateUnspent(); - await updateBalance(); - await updateTransactions(); + await updateUnspents(address); + + final newBalance = await _fetchBalance(sh); + balance[currency]?.confirmed += newBalance.confirmed; + balance[currency]?.unconfirmed += newBalance.unconfirmed; + + await _fetchAddressHistory(address, await currentChainTip); } catch (e, s) { print(e.toString()); _onError?.call(FlutterErrorDetails( @@ -1497,24 +1617,7 @@ abstract class ElectrumWalletBase )); } }); - }); - - await _chainTipUpdateSubject?.close(); - _chainTipUpdateSubject = electrumClient.chainTipUpdate(); - _chainTipUpdateSubject?.listen((_) async { - try { - final currentHeight = await electrumClient.getCurrentBlockChainTip(); - if (currentHeight != null) walletInfo.restoreHeight = currentHeight; - _setListeners(walletInfo.restoreHeight, chainTip: currentHeight); - } catch (e, s) { - print(e.toString()); - _onError?.call(FlutterErrorDetails( - exception: e, - stack: s, - library: this.runtimeType.toString(), - )); - } - }); + })); } Future _fetchBalances() async { @@ -1534,17 +1637,15 @@ abstract class ElectrumWalletBase if (hasSilentPaymentsScanning) { // Add values from unspent coins that are not fetched by the address list // i.e. scanned silent payments - unspentCoinsInfo.values.forEach((info) { - unspentCoins.forEach((element) { - if (element.hash == info.hash && - element.bitcoinAddressRecord.address == info.address && - element.value == info.value) { - if (info.isFrozen) totalFrozen += element.value; - if (element.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { - totalConfirmed += element.value; + transactionHistory.transactions.values.forEach((tx) { + if (tx.unspents != null) { + tx.unspents!.forEach((unspent) { + if (unspent.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { + if (unspent.isFrozen) totalFrozen += unspent.value; + totalConfirmed += unspent.value; } - } - }); + }); + } }); } @@ -1567,6 +1668,13 @@ abstract class ElectrumWalletBase confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen); } + Future _fetchBalance(String sh) async { + final balance = await electrumClient.getBalance(sh); + final confirmed = balance['confirmed'] as int? ?? 0; + final unconfirmed = balance['unconfirmed'] as int? ?? 0; + return ElectrumBalance(confirmed: confirmed, unconfirmed: unconfirmed, frozen: 0); + } + Future updateBalance() async { balance[currency] = await _fetchBalances(); await save(); @@ -1597,10 +1705,19 @@ abstract class ElectrumWalletBase } Future _setInitialHeight() async { - currentChainTip = await electrumClient.getCurrentBlockChainTip(); - if (currentChainTip != null && walletInfo.restoreHeight == 0) { - walletInfo.restoreHeight = currentChainTip!; - } + if (_chainTipUpdateSubject != null) return; + + _chainTipUpdateSubject = await electrumClient.chainTipSubscribe(); + _chainTipUpdateSubject?.listen((e) async { + final event = e as Map; + final height = int.parse(event['height'].toString()); + + _currentChainTip = height; + + if (_currentChainTip != null && _currentChainTip! > 0 && walletInfo.restoreHeight == 0) { + walletInfo.restoreHeight = _currentChainTip!; + } + }); } static BasedUtxoNetwork _getNetwork(bitcoin.NetworkType networkType, CryptoCurrency? currency) { @@ -1679,8 +1796,6 @@ class SyncResponse { } Future startRefresh(ScanData scanData) async { - var cachedBlockchainHeight = scanData.chainTip; - Future getElectrumConnection() async { final electrumClient = scanData.electrumClient; if (!electrumClient.isConnected) { @@ -1690,11 +1805,6 @@ Future startRefresh(ScanData scanData) async { return electrumClient; } - Future getUpdatedNodeHeight() async { - final electrumClient = await getElectrumConnection(); - return await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; - } - var lastKnownBlockHeight = 0; var initialSyncHeight = 0; @@ -1714,172 +1824,182 @@ Future startRefresh(ScanData scanData) async { return; } - // Run this until no more blocks left to scan txs. At first this was recursive - // i.e. re-calling the startRefresh function but this was easier for the above values to retain - // their initial values - while (true) { - lastKnownBlockHeight = syncHeight; + BehaviorSubject? tweaksSubscription = null; - SyncingSyncStatus syncingStatus; - if (scanData.isSingleScan) { - syncingStatus = SyncingSyncStatus(1, 0); - } else { - syncingStatus = SyncingSyncStatus.fromHeightValues( - await getUpdatedNodeHeight(), initialSyncHeight, syncHeight); - } + lastKnownBlockHeight = syncHeight; - scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); + SyncingSyncStatus syncingStatus; + if (scanData.isSingleScan) { + syncingStatus = SyncingSyncStatus(1, 0); + } else { + syncingStatus = + SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight); + } - if (syncingStatus.blocksLeft <= 0 || (scanData.isSingleScan && scanData.height != syncHeight)) { - scanData.sendPort.send(SyncResponse(await getUpdatedNodeHeight(), SyncedSyncStatus())); - return; - } + scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); - try { - final electrumClient = await getElectrumConnection(); + if (syncingStatus.blocksLeft <= 0 || (scanData.isSingleScan && scanData.height != syncHeight)) { + scanData.sendPort.send(SyncResponse(scanData.chainTip, SyncedSyncStatus())); + return; + } - // TODO: hardcoded values, if timed out decrease amount of blocks per request? - final scanningBlockCount = - scanData.isSingleScan ? 1 : (scanData.network == BitcoinNetwork.testnet ? 25 : 10); + try { + final electrumClient = await getElectrumConnection(); - Map? tweaks; + if (tweaksSubscription == null) { try { - tweaks = await electrumClient.getTweaks(height: syncHeight, count: scanningBlockCount); - } catch (e) { - if (e is RequestFailedTimeoutException) { - return scanData.sendPort.send( - SyncResponse(syncHeight, TimedOutSyncStatus()), - ); - } - } + tweaksSubscription = await electrumClient.tweaksSubscribe(height: syncHeight); + + tweaksSubscription?.listen((t) { + final tweaks = t as Map; + + if (tweaks.isEmpty) { + syncHeight += 1; + scanData.sendPort.send( + SyncResponse( + syncHeight, + SyncingSyncStatus.fromHeightValues( + currentChainTip, + initialSyncHeight, + syncHeight, + ), + ), + ); - if (tweaks == null) { - return scanData.sendPort.send( - SyncResponse(syncHeight, UnsupportedSyncStatus()), - ); - } + return; + } - if (tweaks.isEmpty) { - syncHeight += scanningBlockCount; - continue; - } + final blockHeight = tweaks.keys.first.toString(); - final blockHeights = tweaks.keys; - for (var i = 0; i < blockHeights.length; i++) { - try { - final blockHeight = blockHeights.elementAt(i).toString(); - final blockTweaks = tweaks[blockHeight] as Map; - - for (var j = 0; j < blockTweaks.keys.length; j++) { - final txid = blockTweaks.keys.elementAt(j); - final details = blockTweaks[txid] as Map; - final outputPubkeys = (details["output_pubkeys"] as Map); - final tweak = details["tweak"].toString(); - - try { - final addToWallet = scanOutputs( - outputPubkeys.values.map((o) => o[0].toString()).toList(), - tweak, - Receiver( - scanData.silentAddress.b_scan.toHex(), - scanData.silentAddress.B_spend.toHex(), - scanData.network == BitcoinNetwork.testnet, - scanData.labelIndexes, - scanData.labelIndexes.length, - ), - ); + try { + final blockTweaks = tweaks[blockHeight] as Map; - if (addToWallet.isEmpty) { - // no results tx, continue to next tx - continue; - } + for (var j = 0; j < blockTweaks.keys.length; j++) { + final txid = blockTweaks.keys.elementAt(j); + final details = blockTweaks[txid] as Map; + final outputPubkeys = (details["output_pubkeys"] as Map); + final tweak = details["tweak"].toString(); - addToWallet.forEach((label, value) async { - (value as Map).forEach((output, tweak) async { - final t_k = tweak.toString(); - - final receivingOutputAddress = ECPublic.fromHex(output) - .toTaprootAddress(tweak: false) - .toAddress(scanData.network); - - final receivedAddressRecord = BitcoinSilentPaymentAddressRecord( - receivingOutputAddress, - index: 0, - isHidden: false, - isUsed: true, - network: scanData.network, - silentPaymentTweak: t_k, - type: SegwitAddresType.p2tr, - txCount: 1, - ); - - int? amount; - int? pos; - outputPubkeys.entries.firstWhere((k) { - final matches = k.value[0] == output; - if (matches) { - amount = int.parse(k.value[1].toString()); - pos = int.parse(k.key.toString()); - return true; - } - return false; - }); + try { + // scanOutputs called from rust here + final addToWallet = scanOutputs( + outputPubkeys.values.map((o) => o[0].toString()).toList(), + tweak, + Receiver( + scanData.silentAddress.b_scan.toHex(), + scanData.silentAddress.B_spend.toHex(), + scanData.network == BitcoinNetwork.testnet, + scanData.labelIndexes, + scanData.labelIndexes.length, + ), + ); + + if (addToWallet.isEmpty) { + // no results tx, continue to next tx + continue; + } - final json = { - 'address_record': receivedAddressRecord.toJSON(), - 'tx_hash': txid, - 'value': amount!, - 'tx_pos': pos!, - 'silent_payment_tweak': t_k, - }; - - final tx = BitcoinUnspent.fromJSON(receivedAddressRecord, json); - - final silentPaymentAddress = SilentPaymentAddress( - version: scanData.silentAddress.version, - B_scan: scanData.silentAddress.B_scan, - B_spend: label == "None" - ? scanData.silentAddress.B_spend - : scanData.silentAddress.B_spend - .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(label))), - hrp: scanData.silentAddress.hrp, - ); - - final txInfo = ElectrumTransactionInfo( - WalletType.bitcoin, - id: tx.hash, - height: syncHeight, - amount: amount!, - fee: 0, - direction: TransactionDirection.incoming, - isPending: false, - date: DateTime.now(), - confirmations: await getUpdatedNodeHeight() - int.parse(blockHeight) + 1, - to: silentPaymentAddress.toString(), - unspents: [tx], - ); - - scanData.sendPort.send({txInfo.id: txInfo}); + // placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s) + final txInfo = ElectrumTransactionInfo( + WalletType.bitcoin, + id: txid, + height: syncHeight, + amount: 0, + fee: 0, + direction: TransactionDirection.incoming, + isPending: false, + date: DateTime.now(), + confirmations: scanData.chainTip - int.parse(blockHeight) + 1, + unspents: [], + ); + + addToWallet.forEach((label, value) { + (value as Map).forEach((output, tweak) { + final t_k = tweak.toString(); + + final receivingOutputAddress = ECPublic.fromHex(output) + .toTaprootAddress(tweak: false) + .toAddress(scanData.network); + + final receivedAddressRecord = BitcoinSilentPaymentAddressRecord( + receivingOutputAddress, + index: 0, + isHidden: false, + isUsed: true, + network: scanData.network, + silentPaymentTweak: t_k, + type: SegwitAddresType.p2tr, + txCount: 1, + ); + + int? amount; + int? pos; + outputPubkeys.entries.firstWhere((k) { + final isMatchingOutput = k.value[0] == output; + if (isMatchingOutput) { + amount = int.parse(k.value[1].toString()); + pos = int.parse(k.key.toString()); + return true; + } + return false; + }); + + final unspent = BitcoinSilentPaymentsUnspent( + receivedAddressRecord, + txid, + amount!, + pos!, + silentPaymentTweak: t_k, + silentPaymentLabel: label == "None" ? null : label, + ); + + txInfo.unspents!.add(unspent); + txInfo.amount += unspent.value; + }); }); - }); - } catch (_) {} + + scanData.sendPort.send({txInfo.id: txInfo}); + } catch (_) {} + } + } catch (_) {} + + syncHeight += 1; + scanData.sendPort.send( + SyncResponse( + syncHeight, + SyncingSyncStatus.fromHeightValues( + currentChainTip, + initialSyncHeight, + syncHeight, + ), + ), + ); + + if (int.parse(blockHeight) >= scanData.chainTip) { + scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus())); } - } catch (e, s) { - print([e, s]); - } - // Finished scanning block, add 1 to height and continue to next block in loop - syncHeight += 1; - scanData.sendPort.send(SyncResponse(syncHeight, - SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); + return; + }); + } catch (e) { + if (e is RequestFailedTimeoutException) { + return scanData.sendPort.send( + SyncResponse(syncHeight, TimedOutSyncStatus()), + ); + } } - } catch (e, stacktrace) { - print(stacktrace); - print(e.toString()); + } - scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus())); - break; + if (tweaksSubscription == null) { + return scanData.sendPort.send( + SyncResponse(syncHeight, UnsupportedSyncStatus()), + ); } + } catch (e, stacktrace) { + print(stacktrace); + print(e.toString()); + + scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus())); } } diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index e9099ddeec..160c4ed48a 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -57,7 +57,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!), hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'); - if (silentAddresses.length == 0) + if (silentAddresses.length == 0) { silentAddresses.add(BitcoinSilentPaymentAddressRecord( silentAddress.toString(), index: 0, @@ -67,6 +67,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { network: network, type: SilentPaymentsAddresType.p2sp, )); + silentAddresses.add(BitcoinSilentPaymentAddressRecord( + silentAddress!.toLabeledSilentPaymentAddress(0).toString(), + index: 0, + isHidden: true, + name: "", + silentPaymentTweak: BytesUtils.toHexString(silentAddress!.generateLabel(0)), + network: network, + type: SilentPaymentsAddresType.p2sp, + )); + } } updateAddressesByMatch(); @@ -446,7 +456,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action Future discoverAddresses(List addressList, bool isHidden, - Future Function(BitcoinAddressRecord, Set) getAddressHistory, + Future Function(BitcoinAddressRecord) getAddressHistory, {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async { if (!isHidden) { _validateSideHdAddresses(addressList.toList()); @@ -456,8 +466,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { startIndex: addressList.length, isHidden: isHidden, type: type); addAddresses(newAddresses); - final addressesWithHistory = await Future.wait(newAddresses - .map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet()))); + final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory)); final isLastAddressUsed = addressesWithHistory.last == addressList.last.address; if (isLastAddressUsed) { diff --git a/cw_bitcoin/lib/exceptions.dart b/cw_bitcoin/lib/exceptions.dart index 4b03eb9221..830f56c241 100644 --- a/cw_bitcoin/lib/exceptions.dart +++ b/cw_bitcoin/lib/exceptions.dart @@ -2,7 +2,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/exceptions.dart'; class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException { - BitcoinTransactionWrongBalanceException() : super(CryptoCurrency.btc); + BitcoinTransactionWrongBalanceException({super.amount}) : super(CryptoCurrency.btc); } class BitcoinTransactionNoInputsException extends TransactionNoInputsException {} @@ -25,3 +25,7 @@ class BitcoinTransactionCommitFailedDustOutputSendAll extends TransactionCommitFailedDustOutputSendAll {} class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {} + +class BitcoinTransactionCommitFailedBIP68Final extends TransactionCommitFailedBIP68Final {} + +class BitcoinTransactionSilentPaymentsNotSupported extends TransactionInputNotSupported {} diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index 529ac61da6..020919e43b 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -73,6 +73,10 @@ class PendingBitcoinTransaction with PendingTransaction { if (error.contains("bad-txns-vout-negative")) { throw BitcoinTransactionCommitFailedVoutNegative(); } + + if (error.contains("non-BIP68-final")) { + throw BitcoinTransactionCommitFailedBIP68Final(); + } } throw BitcoinTransactionCommitFailed(); } diff --git a/cw_core/lib/exceptions.dart b/cw_core/lib/exceptions.dart index 848ac40e63..eeb43bc384 100644 --- a/cw_core/lib/exceptions.dart +++ b/cw_core/lib/exceptions.dart @@ -1,9 +1,10 @@ import 'package:cw_core/crypto_currency.dart'; class TransactionWrongBalanceException implements Exception { - TransactionWrongBalanceException(this.currency); + TransactionWrongBalanceException(this.currency, {this.amount}); final CryptoCurrency currency; + final int? amount; } class TransactionNoInputsException implements Exception {} @@ -28,3 +29,7 @@ class TransactionCommitFailedDustOutput implements Exception {} class TransactionCommitFailedDustOutputSendAll implements Exception {} class TransactionCommitFailedVoutNegative implements Exception {} + +class TransactionCommitFailedBIP68Final implements Exception {} + +class TransactionInputNotSupported implements Exception {} diff --git a/cw_core/lib/sync_status.dart b/cw_core/lib/sync_status.dart index dd2d9ca67b..40c2109d16 100644 --- a/cw_core/lib/sync_status.dart +++ b/cw_core/lib/sync_status.dart @@ -31,6 +31,11 @@ class SyncedSyncStatus extends SyncStatus { double progress() => 1.0; } +class SyncronizingSyncStatus extends SyncStatus { + @override + double progress() => 0.0; +} + class NotConnectedSyncStatus extends SyncStatus { const NotConnectedSyncStatus(); @@ -43,10 +48,7 @@ class AttemptingSyncStatus extends SyncStatus { double progress() => 0.0; } -class FailedSyncStatus extends SyncStatus { - @override - double progress() => 1.0; -} +class FailedSyncStatus extends NotConnectedSyncStatus {} class ConnectingSyncStatus extends SyncStatus { @override @@ -58,21 +60,14 @@ class ConnectedSyncStatus extends SyncStatus { double progress() => 0.0; } -class UnsupportedSyncStatus extends SyncStatus { - @override - double progress() => 1.0; -} +class UnsupportedSyncStatus extends NotConnectedSyncStatus {} -class TimedOutSyncStatus extends SyncStatus { - @override - double progress() => 1.0; +class TimedOutSyncStatus extends NotConnectedSyncStatus { @override String toString() => 'Timed out'; } -class LostConnectionSyncStatus extends SyncStatus { - @override - double progress() => 1.0; +class LostConnectionSyncStatus extends NotConnectedSyncStatus { @override String toString() => 'Reconnecting'; } diff --git a/cw_core/lib/unspent_coins_info.dart b/cw_core/lib/unspent_coins_info.dart index 25abd3e485..ed09e17e0c 100644 --- a/cw_core/lib/unspent_coins_info.dart +++ b/cw_core/lib/unspent_coins_info.dart @@ -16,7 +16,8 @@ class UnspentCoinsInfo extends HiveObject { required this.value, this.keyImage = null, this.isChange = false, - this.accountIndex = 0 + this.accountIndex = 0, + this.isSilentPayment = false, }); static const typeId = UNSPENT_COINS_INFO_TYPE_ID; @@ -49,13 +50,16 @@ class UnspentCoinsInfo extends HiveObject { @HiveField(8, defaultValue: null) String? keyImage; - + @HiveField(9, defaultValue: false) bool isChange; @HiveField(10, defaultValue: 0) int accountIndex; + @HiveField(11, defaultValue: false) + bool? isSilentPayment; + String get note => noteRaw ?? ''; set note(String value) => noteRaw = value; diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 8ee3e16879..e66da479b9 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -187,7 +187,7 @@ class CWBitcoin extends Bitcoin { Future updateUnspents(Object wallet) async { final bitcoinWallet = wallet as ElectrumWallet; - await bitcoinWallet.updateUnspent(); + await bitcoinWallet.updateAllUnspents(); } WalletService createBitcoinWalletService( @@ -386,4 +386,10 @@ class CWBitcoin extends Bitcoin { final bitcoinWallet = wallet as ElectrumWallet; bitcoinWallet.walletAddresses.deleteSilentPaymentAddress(address); } + + @override + Future updateFeeRates(Object wallet) async { + final bitcoinWallet = wallet as ElectrumWallet; + await bitcoinWallet.updateFeeRates(); + } } diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index c743caf55d..c431963372 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -44,5 +44,9 @@ String syncStatusTitle(SyncStatus syncStatus) { return S.current.sync_status_timed_out; } + if (syncStatus is SyncronizingSyncStatus) { + return S.current.sync_status_syncronizing; + } + return ''; } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 6033c24a65..792fd6f894 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -818,14 +818,16 @@ Future checkCurrentNodes( } if (currentBitcoinElectrumServer == null) { - final cakeWalletElectrum = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin); + final cakeWalletElectrum = + Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: true); await nodeSource.add(cakeWalletElectrum); await sharedPreferences.setInt( PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int); } if (currentLitecoinElectrumServer == null) { - final cakeWalletElectrum = Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin); + final cakeWalletElectrum = + Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin, useSSL: true); await nodeSource.add(cakeWalletElectrum); await sharedPreferences.setInt( PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int); @@ -860,7 +862,8 @@ Future checkCurrentNodes( } if (currentBitcoinCashNodeServer == null) { - final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash); + final node = + Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash, useSSL: true); await nodeSource.add(node); await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); } @@ -888,7 +891,8 @@ Future resetBitcoinElectrumServer( .firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri); if (cakeWalletNode == null) { - cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin); + cakeWalletNode = + Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: true); await nodeSource.add(cakeWalletNode); } diff --git a/lib/src/screens/receive/widgets/address_cell.dart b/lib/src/screens/receive/widgets/address_cell.dart index fd34b00ac9..850c082099 100644 --- a/lib/src/screens/receive/widgets/address_cell.dart +++ b/lib/src/screens/receive/widgets/address_cell.dart @@ -146,7 +146,7 @@ class AddressCell extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: [ Text( - '${S.of(context).balance}: $txCount', + '${S.of(context).balance}: $balance', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 970bb31f27..38fe184439 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; @@ -400,6 +401,10 @@ class SendPage extends BasePage { return; } + if (sendViewModel.isElectrumWallet) { + bitcoin!.updateFeeRates(sendViewModel.wallet); + } + reaction((_) => sendViewModel.state, (ExecutionState state) { if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -456,10 +461,12 @@ class SendPage extends BasePage { sendViewModel.selectedCryptoCurrency.toString()); final waitMessage = sendViewModel.walletType == WalletType.solana - ? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : ''; + ? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' + : ''; final newContactMessage = newContactAddress != null - ? '\n${S.of(_dialogContext).add_contact_to_address_book}' : ''; + ? '\n${S.of(_dialogContext).add_contact_to_address_book}' + : ''; final alertContent = "$successMessage$waitMessage$newContactMessage"; diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart index 70ae7ce3f4..ee6d6dc730 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -57,6 +57,7 @@ class UnspentCoinsListFormState extends State { isSending: item.isSending, isFrozen: item.isFrozen, isChange: item.isChange, + isSilentPayment: item.isSilentPayment, onCheckBoxTap: item.isFrozen ? null : () async { diff --git a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart index e160260736..60a23c99b9 100644 --- a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart +++ b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart @@ -12,6 +12,7 @@ class UnspentCoinsListItem extends StatelessWidget { required this.isSending, required this.isFrozen, required this.isChange, + required this.isSilentPayment, this.onCheckBoxTap, }); @@ -21,18 +22,16 @@ class UnspentCoinsListItem extends StatelessWidget { final bool isSending; final bool isFrozen; final bool isChange; + final bool isSilentPayment; final Function()? onCheckBoxTap; @override Widget build(BuildContext context) { final unselectedItemColor = Theme.of(context).cardColor; final selectedItemColor = Theme.of(context).primaryColor; - final itemColor = isSending - ? selectedItemColor - : unselectedItemColor; - final amountColor = isSending - ? Colors.white - : Theme.of(context).extension()!.buttonTextColor; + final itemColor = isSending ? selectedItemColor : unselectedItemColor; + final amountColor = + isSending ? Colors.white : Theme.of(context).extension()!.buttonTextColor; final addressColor = isSending ? Colors.white.withOpacity(0.5) : Theme.of(context).extension()!.buttonSecondaryTextColor; @@ -121,6 +120,23 @@ class UnspentCoinsListItem extends StatelessWidget { ), ), ), + if (isSilentPayment) + Container( + height: 17, + padding: EdgeInsets.only(left: 6, right: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8.5)), + color: Colors.white), + alignment: Alignment.center, + child: Text( + S.of(context).silent_payments, + style: TextStyle( + color: itemColor, + fontSize: 7, + fontWeight: FontWeight.w600, + ), + ), + ), ], ), ), diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 038301db4e..0bea0c59bf 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -567,9 +567,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor if (error is TransactionCommitFailedVoutNegative) { return S.current.tx_rejected_vout_negative; } + if (error is TransactionCommitFailedBIP68Final) { + return S.current.tx_rejected_bip68_final; + } if (error is TransactionNoDustOnChangeException) { return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max); } + if (error is TransactionInputNotSupported) { + return S.current.tx_invalid_input; + } } return errorMessage; diff --git a/lib/view_model/unspent_coins/unspent_coins_item.dart b/lib/view_model/unspent_coins/unspent_coins_item.dart index bb5c4dd7ba..4ca5a10a27 100644 --- a/lib/view_model/unspent_coins/unspent_coins_item.dart +++ b/lib/view_model/unspent_coins/unspent_coins_item.dart @@ -15,7 +15,8 @@ abstract class UnspentCoinsItemBase with Store { required this.isChange, required this.amountRaw, required this.vout, - required this.keyImage + required this.keyImage, + required this.isSilentPayment, }); @observable @@ -47,4 +48,7 @@ abstract class UnspentCoinsItemBase with Store { @observable String? keyImage; + + @observable + bool isSilentPayment; } diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart index 3b90aff41f..bb04cfe5c8 100644 --- a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -86,22 +86,32 @@ abstract class UnspentCoinsListViewModelBase with Store { @action void _updateUnspentCoinsInfo() { _items.clear(); - _items.addAll(_getUnspents().map((elem) { - final info = - getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage); - - return UnspentCoinsItem( - address: elem.address, - amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}', - hash: elem.hash, - isFrozen: info.isFrozen, - note: info.note, - isSending: info.isSending, - amountRaw: elem.value, - vout: elem.vout, - keyImage: elem.keyImage, - isChange: elem.isChange, - ); - })); + + List unspents = []; + _getUnspents().forEach((elem) { + try { + final info = + getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage); + + unspents.add(UnspentCoinsItem( + address: elem.address, + amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}', + hash: elem.hash, + isFrozen: info.isFrozen, + note: info.note, + isSending: info.isSending, + amountRaw: elem.value, + vout: elem.vout, + keyImage: elem.keyImage, + isChange: elem.isChange, + isSilentPayment: info.isSilentPayment ?? false, + )); + } catch (e, s) { + print(s); + print(e.toString()); + } + }); + + _items.addAll(unspents); } } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 94dcd609de..f1c7d64d2f 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -316,8 +316,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo if (isElectrumWallet) { if (bitcoin!.hasSelectedSilentPayments(wallet)) { final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) { - // Silent Payments index 0 is change per BIP - final isPrimary = address.index == 1; + final isPrimary = address.index == 0; return WalletAddressListItem( id: address.index, @@ -335,11 +334,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo final receivedAddressItems = bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) { - final isPrimary = address.index == 0; - return WalletAddressListItem( id: address.index, - isPrimary: isPrimary, + isPrimary: false, name: address.name, address: address.address, txCount: address.txCount, diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index d7d68dfc92..1d1e2916a8 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -745,8 +745,10 @@ "trusted": "موثوق به", "tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.", "tx_commit_failed": "فشل ارتكاب المعاملة. يرجى الاتصال بالدعم.", + "tx_invalid_input": "أنت تستخدم نوع الإدخال الخاطئ لهذا النوع من الدفع", "tx_no_dust_exception": "يتم رفض المعاملة عن طريق إرسال مبلغ صغير جدًا. يرجى محاولة زيادة المبلغ.", "tx_not_enough_inputs_exception": "لا يكفي المدخلات المتاحة. الرجاء تحديد المزيد تحت التحكم في العملة", + "tx_rejected_bip68_final": "تحتوي المعاملة على مدخلات غير مؤكدة وفشلت في استبدال الرسوم.", "tx_rejected_dust_change": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، ومبلغ التغيير المنخفض (الغبار). حاول إرسال كل أو تقليل المبلغ.", "tx_rejected_dust_output": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى زيادة المبلغ.", "tx_rejected_dust_output_send_all": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى التحقق من رصيد العملات المعدنية المحددة تحت التحكم في العملة.", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 2bc6caa4a6..261d4fbc87 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -745,8 +745,10 @@ "trusted": "Надежден", "tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.", "tx_commit_failed": "Компетацията на транзакцията не успя. Моля, свържете се с поддръжката.", + "tx_invalid_input": "Използвате грешен тип вход за този тип плащане", "tx_no_dust_exception": "Сделката се отхвърля чрез изпращане на сума твърде малка. Моля, опитайте да увеличите сумата.", "tx_not_enough_inputs_exception": "Няма достатъчно налични входове. Моля, изберете повече под контрол на монети", + "tx_rejected_bip68_final": "Сделката има непотвърдени входове и не успя да се замени с такса.", "tx_rejected_dust_change": "Транзакция, отхвърлена от мрежови правила, ниска сума на промяна (прах). Опитайте да изпратите всички или да намалите сумата.", "tx_rejected_dust_output": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, увеличете сумата.", "tx_rejected_dust_output_send_all": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, проверете баланса на монетите, избрани под контрол на монети.", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 1ce8d1fcd7..b368947c0a 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -745,8 +745,10 @@ "trusted": "Důvěřovat", "tx_commit_exception_no_dust_on_change": "Transakce je zamítnuta s touto částkou. S těmito mincemi můžete odeslat ${min} bez změny nebo ${max}, které se vrátí změna.", "tx_commit_failed": "Transakce COMPORT selhala. Kontaktujte prosím podporu.", + "tx_invalid_input": "Pro tento typ platby používáte nesprávný typ vstupu", "tx_no_dust_exception": "Transakce je zamítnuta odesláním příliš malé. Zkuste prosím zvýšit částku.", "tx_not_enough_inputs_exception": "Není k dispozici dostatek vstupů. Vyberte prosím více pod kontrolou mincí", + "tx_rejected_bip68_final": "Transakce má nepotvrzené vstupy a nepodařilo se nahradit poplatkem.", "tx_rejected_dust_change": "Transakce zamítnuta podle síťových pravidel, množství nízké změny (prach). Zkuste odeslat vše nebo snížit částku.", "tx_rejected_dust_output": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zvyšte prosím částku.", "tx_rejected_dust_output_send_all": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zkontrolujte prosím zůstatek mincí vybraných pod kontrolou mincí.", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index b508e691e4..05b6636f54 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -746,8 +746,10 @@ "trusted": "Vertrauenswürdige", "tx_commit_exception_no_dust_on_change": "Die Transaktion wird diesen Betrag abgelehnt. Mit diesen Münzen können Sie ${min} ohne Veränderung oder ${max} senden, die Änderungen zurückgeben.", "tx_commit_failed": "Transaktionsausschüsse ist fehlgeschlagen. Bitte wenden Sie sich an Support.", + "tx_invalid_input": "Sie verwenden den falschen Eingangstyp für diese Art von Zahlung", "tx_no_dust_exception": "Die Transaktion wird abgelehnt, indem eine Menge zu klein gesendet wird. Bitte versuchen Sie, die Menge zu erhöhen.", "tx_not_enough_inputs_exception": "Nicht genügend Eingänge verfügbar. Bitte wählen Sie mehr unter Münzkontrolle aus", + "tx_rejected_bip68_final": "Die Transaktion hat unbestätigte Inputs und konnte nicht durch Gebühr ersetzt werden.", "tx_rejected_dust_change": "Transaktion abgelehnt durch Netzwerkregeln, niedriger Änderungsbetrag (Staub). Versuchen Sie, alle zu senden oder die Menge zu reduzieren.", "tx_rejected_dust_output": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte erhöhen Sie den Betrag.", "tx_rejected_dust_output_send_all": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte überprüfen Sie den Gleichgewicht der unter Münzkontrolle ausgewählten Münzen.", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 8110181bb4..7635583257 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -745,8 +745,10 @@ "trusted": "Trusted", "tx_commit_exception_no_dust_on_change": "The transaction is rejected with this amount. With these coins you can send ${min} without change or ${max} that returns change.", "tx_commit_failed": "Transaction commit failed. Please contact support.", + "tx_invalid_input": "You are using the wrong input type for this type of payment", "tx_no_dust_exception": "The transaction is rejected by sending an amount too small. Please try increasing the amount.", "tx_not_enough_inputs_exception": "Not enough inputs available. Please select more under Coin Control", + "tx_rejected_bip68_final": "Transaction has unconfirmed inputs and failed to replace by fee.", "tx_rejected_dust_change": "Transaction rejected by network rules, low change amount (dust). Try sending ALL or reducing the amount.", "tx_rejected_dust_output": "Transaction rejected by network rules, low output amount (dust). Please increase the amount.", "tx_rejected_dust_output_send_all": "Transaction rejected by network rules, low output amount (dust). Please check the balance of coins selected under Coin Control.", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index b29eef59ce..71b7ad423d 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -746,8 +746,10 @@ "trusted": "de confianza", "tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.", "tx_commit_failed": "La confirmación de transacción falló. Póngase en contacto con el soporte.", + "tx_invalid_input": "Está utilizando el tipo de entrada incorrecto para este tipo de pago", "tx_no_dust_exception": "La transacción se rechaza enviando una cantidad demasiado pequeña. Intente aumentar la cantidad.", "tx_not_enough_inputs_exception": "No hay suficientes entradas disponibles. Seleccione más bajo control de monedas", + "tx_rejected_bip68_final": "La transacción tiene entradas no confirmadas y no ha podido reemplazar por tarifa.", "tx_rejected_dust_change": "Transacción rechazada por reglas de red, bajo cambio de cambio (polvo). Intente enviar todo o reducir la cantidad.", "tx_rejected_dust_output": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Aumente la cantidad.", "tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Verifique el saldo de monedas seleccionadas bajo control de monedas.", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f2b30a8e49..a034116027 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -745,8 +745,10 @@ "trusted": "de confiance", "tx_commit_exception_no_dust_on_change": "La transaction est rejetée avec ce montant. Avec ces pièces, vous pouvez envoyer ${min} sans changement ou ${max} qui renvoie le changement.", "tx_commit_failed": "La validation de la transaction a échoué. Veuillez contacter l'assistance.", + "tx_invalid_input": "Vous utilisez le mauvais type d'entrée pour ce type de paiement", "tx_no_dust_exception": "La transaction est rejetée en envoyant un montant trop faible. Veuillez essayer d'augmenter le montant.", "tx_not_enough_inputs_exception": "Pas assez d'entrées disponibles. Veuillez sélectionner plus sous Control Control", + "tx_rejected_bip68_final": "La transaction a des entrées non confirmées et n'a pas réussi à remplacer par les frais.", "tx_rejected_dust_change": "Transaction rejetée par les règles du réseau, montant de faible variation (poussière). Essayez d'envoyer tout ou de réduire le montant.", "tx_rejected_dust_output": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez augmenter le montant.", "tx_rejected_dust_output_send_all": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez vérifier le solde des pièces sélectionnées sous le contrôle des pièces de monnaie.", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 09365a462d..36e0f8a3db 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -747,8 +747,10 @@ "trusted": "Amintacce", "tx_commit_exception_no_dust_on_change": "An ƙi ma'amala da wannan adadin. Tare da waɗannan tsabar kudi Zaka iya aika ${min}, ba tare da canji ba ko ${max} wanda ya dawo canzawa.", "tx_commit_failed": "Ma'amala ya kasa. Da fatan za a tuntuɓi goyan baya.", + "tx_invalid_input": "Kuna amfani da nau'in shigar da ba daidai ba don wannan nau'in biyan kuɗi", "tx_no_dust_exception": "An ƙi ma'amala ta hanyar aika adadin ƙarami. Da fatan za a gwada ƙara adadin.", "tx_not_enough_inputs_exception": "Bai isa ba hanyoyin da ake samu. Da fatan za selectiari a karkashin Kwarewar Coin", + "tx_rejected_bip68_final": "Ma'amala tana da abubuwan da basu dace ba kuma sun kasa maye gurbin ta.", "tx_rejected_dust_change": "Ma'amala ta ƙi ta dokokin cibiyar sadarwa, ƙarancin canji (ƙura). Gwada aikawa da duka ko rage adadin.", "tx_rejected_dust_output": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a ƙara adadin.", "tx_rejected_dust_output_send_all": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 4472b0aae1..3bfe67695b 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -747,8 +747,10 @@ "trusted": "भरोसा", "tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।", "tx_commit_failed": "लेन -देन प्रतिबद्ध विफल। कृपया संपर्क समर्थन करें।", + "tx_invalid_input": "आप इस प्रकार के भुगतान के लिए गलत इनपुट प्रकार का उपयोग कर रहे हैं", "tx_no_dust_exception": "लेनदेन को बहुत छोटी राशि भेजकर अस्वीकार कर दिया जाता है। कृपया राशि बढ़ाने का प्रयास करें।", "tx_not_enough_inputs_exception": "पर्याप्त इनपुट उपलब्ध नहीं है। कृपया सिक्का नियंत्रण के तहत अधिक चुनें", + "tx_rejected_bip68_final": "लेन -देन में अपुष्ट इनपुट हैं और शुल्क द्वारा प्रतिस्थापित करने में विफल रहे हैं।", "tx_rejected_dust_change": "नेटवर्क नियमों, कम परिवर्तन राशि (धूल) द्वारा खारिज किए गए लेनदेन। सभी भेजने या राशि को कम करने का प्रयास करें।", "tx_rejected_dust_output": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया राशि बढ़ाएं।", "tx_rejected_dust_output_send_all": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया सिक्का नियंत्रण के तहत चुने गए सिक्कों के संतुलन की जाँच करें।", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index fd305cf50d..ad52db1fac 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -745,8 +745,10 @@ "trusted": "vjerovao", "tx_commit_exception_no_dust_on_change": "Transakcija se odbija s tim iznosom. Pomoću ovih kovanica možete poslati ${min} bez promjene ili ${max} koja vraća promjenu.", "tx_commit_failed": "Obveza transakcije nije uspjela. Molimo kontaktirajte podršku.", + "tx_invalid_input": "Koristite pogrešnu vrstu ulaza za ovu vrstu plaćanja", "tx_no_dust_exception": "Transakcija se odbija slanjem iznosa premalo. Pokušajte povećati iznos.", "tx_not_enough_inputs_exception": "Nema dovoljno unosa. Molimo odaberite više pod kontrolom novčića", + "tx_rejected_bip68_final": "Transakcija ima nepotvrđene unose i nije zamijenila naknadom.", "tx_rejected_dust_change": "Transakcija odbijena mrežnim pravilima, niska količina promjene (prašina). Pokušajte poslati sve ili smanjiti iznos.", "tx_rejected_dust_output": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo povećajte iznos.", "tx_rejected_dust_output_send_all": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo provjerite ravnotežu kovanica odabranih pod kontrolom novčića.", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index dd5acfed7f..2db131a1ac 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -748,8 +748,10 @@ "trusted": "Dipercayai", "tx_commit_exception_no_dust_on_change": "Transaksi ditolak dengan jumlah ini. Dengan koin ini Anda dapat mengirim ${min} tanpa perubahan atau ${max} yang mengembalikan perubahan.", "tx_commit_failed": "Transaksi Gagal. Silakan hubungi Dukungan.", + "tx_invalid_input": "Anda menggunakan jenis input yang salah untuk jenis pembayaran ini", "tx_no_dust_exception": "Transaksi ditolak dengan mengirimkan jumlah yang terlalu kecil. Silakan coba tingkatkan jumlahnya.", "tx_not_enough_inputs_exception": "Tidak cukup input yang tersedia. Pilih lebih banyak lagi di bawah Kontrol Koin", + "tx_rejected_bip68_final": "Transaksi memiliki input yang belum dikonfirmasi dan gagal mengganti dengan biaya.", "tx_rejected_dust_change": "Transaksi ditolak oleh aturan jaringan, jumlah perubahan rendah (debu). Coba kirim semua atau mengurangi jumlahnya.", "tx_rejected_dust_output": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Harap tingkatkan jumlahnya.", "tx_rejected_dust_output_send_all": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Silakan periksa saldo koin yang dipilih di bawah kontrol koin.", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 9d88e979d7..e66bceff5b 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -747,8 +747,10 @@ "trusted": "di fiducia", "tx_commit_exception_no_dust_on_change": "La transazione viene respinta con questo importo. Con queste monete è possibile inviare ${min} senza modifiche o ${max} che restituisce il cambiamento.", "tx_commit_failed": "Commit di transazione non riuscita. Si prega di contattare il supporto.", + "tx_invalid_input": "Stai usando il tipo di input sbagliato per questo tipo di pagamento", "tx_no_dust_exception": "La transazione viene respinta inviando un importo troppo piccolo. Per favore, prova ad aumentare l'importo.", "tx_not_enough_inputs_exception": "Input non sufficienti disponibili. Seleziona di più sotto il controllo delle monete", + "tx_rejected_bip68_final": "La transazione ha input non confermati e non è stata sostituita per tassa.", "tx_rejected_dust_change": "Transazione respinta dalle regole di rete, quantità bassa variazione (polvere). Prova a inviare tutto o ridurre l'importo.", "tx_rejected_dust_output": "Transazione respinta dalle regole di rete, bassa quantità di output (polvere). Si prega di aumentare l'importo.", "tx_rejected_dust_output_send_all": "Transazione respinta dalle regole di rete, bassa quantità di output (polvere). Si prega di controllare il saldo delle monete selezionate sotto controllo delle monete.", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index d11c468510..192ea09be0 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -746,8 +746,10 @@ "trusted": "信頼できる", "tx_commit_exception_no_dust_on_change": "この金額ではトランザクションは拒否されます。 これらのコインを使用すると、おつりなしの ${min} またはおつりを返す ${max} を送信できます。", "tx_commit_failed": "トランザクションコミットは失敗しました。サポートに連絡してください。", + "tx_invalid_input": "このタイプの支払いに間違った入力タイプを使用しています", "tx_no_dust_exception": "トランザクションは、小さすぎる金額を送信することにより拒否されます。量を増やしてみてください。", "tx_not_enough_inputs_exception": "利用可能な入力が十分ではありません。コイン制御下でもっと選択してください", + "tx_rejected_bip68_final": "トランザクションには未確認の入力があり、料金で交換できませんでした。", "tx_rejected_dust_change": "ネットワークルール、低い変更量(ほこり)によって拒否されたトランザクション。すべてを送信するか、金額を減らしてみてください。", "tx_rejected_dust_output": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。金額を増やしてください。", "tx_rejected_dust_output_send_all": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。コイン管理下で選択されたコインのバランスを確認してください。", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index f0ab69a8d5..ef8424c16b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -746,8 +746,10 @@ "trusted": "신뢰할 수 있는", "tx_commit_exception_no_dust_on_change": "이 금액으로 거래가 거부되었습니다. 이 코인을 사용하면 거스름돈 없이 ${min}를 보내거나 거스름돈을 반환하는 ${max}를 보낼 수 있습니다.", "tx_commit_failed": "거래 커밋이 실패했습니다. 지원에 연락하십시오.", + "tx_invalid_input": "이 유형의 지불에 잘못 입력 유형을 사용하고 있습니다.", "tx_no_dust_exception": "너무 작은 금액을 보내면 거래가 거부됩니다. 금액을 늘리십시오.", "tx_not_enough_inputs_exception": "사용 가능한 입력이 충분하지 않습니다. 코인 컨트롤에서 더 많은 것을 선택하십시오", + "tx_rejected_bip68_final": "거래는 확인되지 않은 입력을 받았으며 수수료로 교체하지 못했습니다.", "tx_rejected_dust_change": "네트워크 규칙, 낮은 변경 금액 (먼지)에 의해 거부 된 거래. 전부를 보내거나 금액을 줄이십시오.", "tx_rejected_dust_output": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 금액을 늘리십시오.", "tx_rejected_dust_output_send_all": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 동전 제어에서 선택한 동전의 균형을 확인하십시오.", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index fcc482bae0..b64f2ebd13 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -745,8 +745,10 @@ "trusted": "ယုံတယ်။", "tx_commit_exception_no_dust_on_change": "အဆိုပါငွေပေးငွေယူကဒီပမာဏနှင့်အတူပယ်ချခံရသည်။ ဤဒင်္ဂါးပြားများနှင့်အတူပြောင်းလဲမှုကိုပြန်လည်ပြောင်းလဲခြင်းသို့မဟုတ် ${min} မပါဘဲ ${max} ပေးပို့နိုင်သည်။", "tx_commit_failed": "ငွေပေးငွေယူကျူးလွန်မှုပျက်ကွက်။ ကျေးဇူးပြုပြီးပံ့ပိုးမှုဆက်သွယ်ပါ။", + "tx_invalid_input": "သင်သည်ဤငွေပေးချေမှုအမျိုးအစားအတွက်မှားယွင်းသော input type ကိုအသုံးပြုနေသည်", "tx_no_dust_exception": "ငွေပမာဏကိုသေးငယ်လွန်းသောငွေပမာဏကိုပေးပို့ခြင်းဖြင့်ပယ်ဖျက်ခြင်းကိုငြင်းပယ်သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ကြိုးစားပါ။", "tx_not_enough_inputs_exception": "အလုံအလောက်သွင်းအားစုများမလုံလောက်။ ကျေးဇူးပြုပြီးဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ပိုမိုရွေးချယ်ပါ", + "tx_rejected_bip68_final": "ငွေပေးငွေယူသည်အတည်မပြုရသေးသောသွင်းအားစုများရှိပြီးအခကြေးငွေဖြင့်အစားထိုးရန်ပျက်ကွက်ခဲ့သည်။", "tx_rejected_dust_change": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့်ပယ်ဖျက်ခြင်းသည် Network စည်းမျဉ်းစည်းကမ်းများဖြင့်ငြင်းပယ်ခြင်း, အားလုံးပေးပို့ခြင်းသို့မဟုတ်ငွေပမာဏကိုလျှော့ချကြိုးစားပါ။", "tx_rejected_dust_output": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ပေးပါ။", "tx_rejected_dust_output_send_all": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ရွေးချယ်ထားသောဒင်္ဂါးများ၏လက်ကျန်ငွေကိုစစ်ဆေးပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index c0220bd807..b49bdc4da5 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -745,8 +745,10 @@ "trusted": "vertrouwd", "tx_commit_exception_no_dust_on_change": "De transactie wordt afgewezen met dit bedrag. Met deze munten kunt u ${min} verzenden zonder verandering of ${max} die wijziging retourneert.", "tx_commit_failed": "Transactiebewissing is mislukt. Neem contact op met de ondersteuning.", + "tx_invalid_input": "U gebruikt het verkeerde invoertype voor dit type betaling", "tx_no_dust_exception": "De transactie wordt afgewezen door een te klein bedrag te verzenden. Probeer het bedrag te verhogen.", "tx_not_enough_inputs_exception": "Niet genoeg ingangen beschikbaar. Selecteer meer onder muntenbesturing", + "tx_rejected_bip68_final": "Transactie heeft onbevestigde ingangen en niet vervangen door vergoeding.", "tx_rejected_dust_change": "Transactie afgewezen door netwerkregels, laag wijzigingsbedrag (stof). Probeer alles te verzenden of het bedrag te verminderen.", "tx_rejected_dust_output": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Verhoog het bedrag.", "tx_rejected_dust_output_send_all": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Controleer het saldo van munten die zijn geselecteerd onder muntcontrole.", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index d92a193e72..dee8d0fdd8 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -745,8 +745,10 @@ "trusted": "Zaufany", "tx_commit_exception_no_dust_on_change": "Transakcja jest odrzucana z tą kwotą. Za pomocą tych monet możesz wysłać ${min} bez zmiany lub ${max}, które zwraca zmianę.", "tx_commit_failed": "Zatwierdzenie transakcji nie powiodło się. Skontaktuj się z obsługą.", + "tx_invalid_input": "Używasz niewłaściwego typu wejściowego dla tego rodzaju płatności", "tx_no_dust_exception": "Transakcja jest odrzucana przez wysyłanie zbyt małej ilości. Spróbuj zwiększyć kwotę.", "tx_not_enough_inputs_exception": "Za mało dostępnych danych wejściowych. Wybierz więcej pod kontrolą monet", + "tx_rejected_bip68_final": "Transakcja niepotwierdza wejściów i nie zastąpiła opłaty.", "tx_rejected_dust_change": "Transakcja odrzucona według reguł sieciowych, niska ilość zmiany (kurz). Spróbuj wysłać całość lub zmniejszyć kwotę.", "tx_rejected_dust_output": "Transakcja odrzucona według reguł sieciowych, niskiej ilości wyjściowej (pyłu). Zwiększ kwotę.", "tx_rejected_dust_output_send_all": "Transakcja odrzucona według reguł sieciowych, niskiej ilości wyjściowej (pyłu). Sprawdź saldo monet wybranych pod kontrolą monet.", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index cf23b4b117..0bf4e4422c 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -747,8 +747,10 @@ "trusted": "confiável", "tx_commit_exception_no_dust_on_change": "A transação é rejeitada com esse valor. Com essas moedas, você pode enviar ${min} sem alteração ou ${max} que retorna alterações.", "tx_commit_failed": "A confirmação da transação falhou. Entre em contato com o suporte.", + "tx_invalid_input": "Você está usando o tipo de entrada errado para este tipo de pagamento", "tx_no_dust_exception": "A transação é rejeitada enviando uma quantia pequena demais. Por favor, tente aumentar o valor.", "tx_not_enough_inputs_exception": "Não há entradas disponíveis. Selecione mais sob controle de moedas", + "tx_rejected_bip68_final": "A transação tem entradas não confirmadas e não substituiu por taxa.", "tx_rejected_dust_change": "Transação rejeitada pelas regras de rede, baixa quantidade de troco (poeira). Tente enviar tudo ou reduzir o valor.", "tx_rejected_dust_output": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, aumente o valor.", "tx_rejected_dust_output_send_all": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, verifique o saldo de moedas selecionadas sob controle de moedas.", @@ -760,7 +762,7 @@ "unconfirmed": "Saldo não confirmado", "understand": "Entendo", "unmatched_currencies": "A moeda da sua carteira atual não corresponde à do QR digitalizado", - "unspent_change": "Mudar", + "unspent_change": "Troco", "unspent_coins_details_title": "Detalhes de moedas não gastas", "unspent_coins_title": "Moedas não gastas", "unsupported_asset": "Não oferecemos suporte a esta ação para este recurso. Crie ou mude para uma carteira de um tipo de ativo compatível.", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index cccd725904..006ec2a7ed 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -746,8 +746,10 @@ "trusted": "доверенный", "tx_commit_exception_no_dust_on_change": "Транзакция отклоняется с этой суммой. С этими монетами вы можете отправлять ${min} без изменения или ${max}, которые возвращают изменение.", "tx_commit_failed": "Комплект транзакции не удался. Пожалуйста, свяжитесь с поддержкой.", + "tx_invalid_input": "Вы используете неправильный тип ввода для этого типа оплаты", "tx_no_dust_exception": "Транзакция отклоняется путем отправки слишком маленькой суммы. Пожалуйста, попробуйте увеличить сумму.", "tx_not_enough_inputs_exception": "Недостаточно входов доступны. Пожалуйста, выберите больше под контролем монет", + "tx_rejected_bip68_final": "Транзакция имеет неподтвержденные входные данные и не смогли заменить на плату.", "tx_rejected_dust_change": "Транзакция отклоняется в соответствии с правилами сети, низкой суммой изменений (пыль). Попробуйте отправить все или уменьшить сумму.", "tx_rejected_dust_output": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, увеличьте сумму.", "tx_rejected_dust_output_send_all": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, проверьте баланс монет, выбранных под контролем монет.", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index dbf461990c..a5d7552052 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -745,8 +745,10 @@ "trusted": "มั่นคง", "tx_commit_exception_no_dust_on_change": "ธุรกรรมถูกปฏิเสธด้วยจำนวนเงินนี้ ด้วยเหรียญเหล่านี้คุณสามารถส่ง ${min} โดยไม่ต้องเปลี่ยนแปลงหรือ ${max} ที่ส่งคืนการเปลี่ยนแปลง", "tx_commit_failed": "การทำธุรกรรมล้มเหลว กรุณาติดต่อฝ่ายสนับสนุน", + "tx_invalid_input": "คุณกำลังใช้ประเภทอินพุตที่ไม่ถูกต้องสำหรับการชำระเงินประเภทนี้", "tx_no_dust_exception": "การทำธุรกรรมถูกปฏิเสธโดยการส่งจำนวนน้อยเกินไป โปรดลองเพิ่มจำนวนเงิน", "tx_not_enough_inputs_exception": "มีอินพุตไม่เพียงพอ โปรดเลือกเพิ่มเติมภายใต้การควบคุมเหรียญ", + "tx_rejected_bip68_final": "การทำธุรกรรมมีอินพุตที่ไม่ได้รับการยืนยันและไม่สามารถแทนที่ด้วยค่าธรรมเนียม", "tx_rejected_dust_change": "ธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนการเปลี่ยนแปลงต่ำ (ฝุ่น) ลองส่งทั้งหมดหรือลดจำนวนเงิน", "tx_rejected_dust_output": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดเพิ่มจำนวนเงิน", "tx_rejected_dust_output_send_all": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดตรวจสอบยอดคงเหลือของเหรียญที่เลือกภายใต้การควบคุมเหรียญ", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 904942bffb..49ea9d842d 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -745,8 +745,10 @@ "trusted": "Pinagkakatiwalaan", "tx_commit_exception_no_dust_on_change": "Ang transaksyon ay tinanggihan sa halagang ito. Sa mga barya na ito maaari kang magpadala ng ${min} nang walang pagbabago o ${max} na nagbabalik ng pagbabago.", "tx_commit_failed": "Nabigo ang transaksyon sa transaksyon. Mangyaring makipag -ugnay sa suporta.", + "tx_invalid_input": "Gumagamit ka ng maling uri ng pag -input para sa ganitong uri ng pagbabayad", "tx_no_dust_exception": "Ang transaksyon ay tinanggihan sa pamamagitan ng pagpapadala ng isang maliit na maliit. Mangyaring subukang dagdagan ang halaga.", "tx_not_enough_inputs_exception": "Hindi sapat na magagamit ang mga input. Mangyaring pumili ng higit pa sa ilalim ng control ng barya", + "tx_rejected_bip68_final": "Ang transaksyon ay hindi nakumpirma na mga input at nabigo na palitan ng bayad.", "tx_rejected_dust_change": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng pagbabago (alikabok). Subukang ipadala ang lahat o bawasan ang halaga.", "tx_rejected_dust_output": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (alikabok). Mangyaring dagdagan ang halaga.", "tx_rejected_dust_output_send_all": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (alikabok). Mangyaring suriin ang balanse ng mga barya na napili sa ilalim ng kontrol ng barya.", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index d4cb237708..27e281cb1a 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -745,8 +745,10 @@ "trusted": "Güvenilir", "tx_commit_exception_no_dust_on_change": "İşlem bu miktarla reddedilir. Bu madeni paralarla değişiklik yapmadan ${min} veya değişikliği döndüren ${max} gönderebilirsiniz.", "tx_commit_failed": "İşlem taahhüdü başarısız oldu. Lütfen Destek ile iletişime geçin.", + "tx_invalid_input": "Bu tür ödeme için yanlış giriş türünü kullanıyorsunuz", "tx_no_dust_exception": "İşlem, çok küçük bir miktar gönderilerek reddedilir. Lütfen miktarı artırmayı deneyin.", "tx_not_enough_inputs_exception": "Yeterli giriş yok. Lütfen madeni para kontrolü altında daha fazlasını seçin", + "tx_rejected_bip68_final": "İşlemin doğrulanmamış girdileri var ve ücrete göre değiştirilemedi.", "tx_rejected_dust_change": "Ağ kurallarına göre reddedilen işlem, düşük değişim miktarı (toz). Tümünü göndermeyi veya miktarı azaltmayı deneyin.", "tx_rejected_dust_output": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen miktarı artırın.", "tx_rejected_dust_output_send_all": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen madeni para kontrolü altında seçilen madeni para dengesini kontrol edin.", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 830027c915..ad41e030f6 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -746,8 +746,10 @@ "trusted": "довіряють", "tx_commit_exception_no_dust_on_change": "Транзакція відхилена цією сумою. За допомогою цих монет ви можете надіслати ${min} без змін або ${max}, що повертає зміни.", "tx_commit_failed": "Транзакційна комісія не вдалося. Будь ласка, зв'яжіться з підтримкою.", + "tx_invalid_input": "Ви використовуєте неправильний тип введення для цього типу оплати", "tx_no_dust_exception": "Угода відхиляється, відправивши суму занадто мала. Будь ласка, спробуйте збільшити суму.", "tx_not_enough_inputs_exception": "Недостатньо доступних входів. Виберіть більше під контролем монети", + "tx_rejected_bip68_final": "Трансакція має непідтверджені входи і не замінила плату.", "tx_rejected_dust_change": "Транзакція відхилена за допомогою мережевих правил, низька кількість змін (пил). Спробуйте надіслати все або зменшити суму.", "tx_rejected_dust_output": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, збільшуйте суму.", "tx_rejected_dust_output_send_all": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, перевірте баланс монет, вибраних під контролем монет.", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 97e8ba4581..12bd1ccfed 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -747,8 +747,10 @@ "trusted": "قابل اعتماد", "tx_commit_exception_no_dust_on_change": "اس رقم سے لین دین کو مسترد کردیا گیا ہے۔ ان سککوں کے ذریعہ آپ بغیر کسی تبدیلی کے ${min} یا ${max} بھیج سکتے ہیں جو لوٹتے ہیں۔", "tx_commit_failed": "ٹرانزیکشن کمٹ ناکام ہوگیا۔ براہ کرم سپورٹ سے رابطہ کریں۔", + "tx_invalid_input": "آپ اس قسم کی ادائیگی کے لئے غلط ان پٹ کی قسم استعمال کررہے ہیں", "tx_no_dust_exception": "لین دین کو بہت چھوٹی رقم بھیج کر مسترد کردیا جاتا ہے۔ براہ کرم رقم میں اضافہ کرنے کی کوشش کریں۔", "tx_not_enough_inputs_exception": "کافی ان پٹ دستیاب نہیں ہے۔ براہ کرم سکے کے کنٹرول میں مزید منتخب کریں", + "tx_rejected_bip68_final": "لین دین میں غیر مصدقہ آدانوں کی ہے اور وہ فیس کے ذریعہ تبدیل کرنے میں ناکام رہا ہے۔", "tx_rejected_dust_change": "نیٹ ورک کے قواعد ، کم تبدیلی کی رقم (دھول) کے ذریعہ لین دین کو مسترد کردیا گیا۔ سب کو بھیجنے یا رقم کو کم کرنے کی کوشش کریں۔", "tx_rejected_dust_output": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم رقم میں اضافہ کریں۔", "tx_rejected_dust_output_send_all": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم سکے کے کنٹرول میں منتخب کردہ سکے کا توازن چیک کریں۔", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 6b268a8c10..20e415b264 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -746,8 +746,10 @@ "trusted": "A ti fọkàn ẹ̀ tán", "tx_commit_exception_no_dust_on_change": "Iṣowo naa ti kọ pẹlu iye yii. Pẹlu awọn owó wọnyi o le firanṣẹ ${min} laisi ayipada tabi ${max} ni iyipada iyipada.", "tx_commit_failed": "Idunadura iṣowo kuna. Jọwọ kan si atilẹyin.", + "tx_invalid_input": "O nlo iru titẹ nkan ti ko tọ fun iru isanwo yii", "tx_no_dust_exception": "Iṣowo naa ni kọ nipa fifiranṣẹ iye ti o kere ju. Jọwọ gbiyanju pọ si iye naa.", "tx_not_enough_inputs_exception": "Ko to awọn titẹsi to. Jọwọ yan diẹ sii labẹ iṣakoso owo", + "tx_rejected_bip68_final": "Iṣowo ni awọn igbewọle gbangba ati kuna lati rọpo nipasẹ owo.", "tx_rejected_dust_change": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye iyipada kekere (eruku). Gbiyanju lati firanṣẹ gbogbo rẹ tabi dinku iye.", "tx_rejected_dust_output": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ mu iye naa pọ si.", "tx_rejected_dust_output_send_all": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ ṣayẹwo dọgbadọgba ti awọn owo ti a yan labẹ iṣakoso owo.", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index c847fc8b9d..c38ddbe764 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -745,8 +745,10 @@ "trusted": "值得信赖", "tx_commit_exception_no_dust_on_change": "交易被此金额拒绝。使用这些硬币,您可以发送${min}无需更改或返回${max}的变化。", "tx_commit_failed": "交易承诺失败。请联系支持。", + "tx_invalid_input": "您正在使用错误的输入类型进行此类付款", "tx_no_dust_exception": "通过发送太小的金额来拒绝交易。请尝试增加金额。", "tx_not_enough_inputs_exception": "没有足够的输入。请在硬币控制下选择更多", + "tx_rejected_bip68_final": "交易未确认投入,未能取代费用。", "tx_rejected_dust_change": "交易被网络规则拒绝,较低的变化数量(灰尘)。尝试发送全部或减少金额。", "tx_rejected_dust_output": "交易被网络规则,低输出量(灰尘)拒绝。请增加金额。", "tx_rejected_dust_output_send_all": "交易被网络规则,低输出量(灰尘)拒绝。请检查在硬币控制下选择的硬币的余额。", diff --git a/tool/configure.dart b/tool/configure.dart index 70a74123c0..4ac467811c 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -173,6 +173,7 @@ abstract class Bitcoin { Future rescan(Object wallet, {required int height, bool? doSingleScan}); bool getNodeIsCakeElectrs(Object wallet); void deleteSilentPaymentAddress(Object wallet, String address); + Future updateFeeRates(Object wallet); } """; From eb64a753bf6ec3da5d3b54a406beb391a0508903 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Wed, 17 Apr 2024 21:26:42 -0300 Subject: [PATCH 040/242] fix: scan re-subscription --- cw_bitcoin/lib/electrum.dart | 11 +++-- cw_bitcoin/lib/electrum_wallet.dart | 48 ++++++++++---------- lib/entities/default_settings_migration.dart | 4 +- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index e9383dda59..d7c35c6ee6 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -8,6 +8,8 @@ import 'package:cw_bitcoin/script_hash.dart'; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; +const int TWEAKS_COUNT = 20; + String jsonrpcparams(List params) { final _params = params.map((val) => '"${val.toString()}"').join(','); return '[$_params]'; @@ -281,9 +283,9 @@ class ElectrumClient { BehaviorSubject? tweaksSubscribe({required int height}) { _id += 1; return subscribe( - id: 'blockchain.tweaks.subscribe', + id: 'blockchain.tweaks.subscribe:${height + TWEAKS_COUNT}', method: 'blockchain.tweaks.subscribe', - params: [height], + params: [height, TWEAKS_COUNT], ); } @@ -467,11 +469,14 @@ class ElectrumClient { _tasks[id]?.subject?.add(params.last); break; - case 'blockchain.tweaks.subscribe': case 'blockchain.headers.subscribe': final params = request['params'] as List; _tasks[method]?.subject?.add(params.last); break; + case 'blockchain.tweaks.subscribe': + final params = request['params'] as List; + _tasks[_tasks.keys.first]?.subject?.add(params.last); + break; default: break; } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index b2962f1e0d..38d6c5fec6 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -156,7 +156,7 @@ abstract class ElectrumWalletBase if (active) { await _setInitialHeight(); - if (await currentChainTip > walletInfo.restoreHeight) { + if ((await getCurrentChainTip()) > walletInfo.restoreHeight) { _setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip); } } else { @@ -176,9 +176,9 @@ abstract class ElectrumWalletBase @observable int? _currentChainTip; - @computed - Future get currentChainTip async => - _currentChainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; + Future getCurrentChainTip() async { + return _currentChainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; + } @override BitcoinWalletKeys get keys => @@ -208,7 +208,7 @@ abstract class ElectrumWalletBase @action Future _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async { - final chainTip = chainTipParam ?? await currentChainTip; + final chainTip = chainTipParam ?? await getCurrentChainTip(); syncStatus = AttemptingSyncStatus(); if (_isolate != null) { @@ -373,6 +373,12 @@ abstract class ElectrumWalletBase @override Future connectToNode({required Node node}) async { final differentNode = this.node?.uri != node.uri || this.node?.useSSL != node.useSSL; + + if (differentNode) { + _scripthashesUpdateSubject = {}; + _chainTipUpdateSubject = null; + } + this.node = node; try { @@ -383,6 +389,8 @@ abstract class ElectrumWalletBase await Timer(Duration(seconds: differentNode ? 0 : 10), () async { electrumClient.onConnectionStatusChange = (bool isConnected) async { + if (syncStatus is SyncingSyncStatus) return; + if (isConnected && syncStatus is! SyncedSyncStatus) { syncStatus = ConnectedSyncStatus(); } else if (!isConnected) { @@ -1410,7 +1418,7 @@ abstract class ElectrumWalletBase time = status["block_time"] as int?; final height = status["block_height"] as int? ?? 0; - final tip = await currentChainTip; + final tip = await getCurrentChainTip(); if (tip > 0) confirmations = height > 0 ? tip - height + 1 : 0; } else { final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); @@ -1480,7 +1488,7 @@ abstract class ElectrumWalletBase final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false); await Future.wait(addressesByType.map((addressRecord) async { - final history = await _fetchAddressHistory(addressRecord, await currentChainTip); + final history = await _fetchAddressHistory(addressRecord, await getCurrentChainTip()); if (history.isNotEmpty) { addressRecord.txCount = history.length; @@ -1502,7 +1510,7 @@ abstract class ElectrumWalletBase addressRecord.isHidden, (address) async { await _subscribeForUpdates(); - return _fetchAddressHistory(address, await currentChainTip) + return _fetchAddressHistory(address, await getCurrentChainTip()) .then((history) => history.isNotEmpty ? address.address : null); }, type: type, @@ -1575,7 +1583,7 @@ abstract class ElectrumWalletBase transactionHistory.transactions.values.forEach((tx) async { if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) { - tx.confirmations = await currentChainTip - tx.height + 1; + tx.confirmations = await getCurrentChainTip() - tx.height + 1; } }); @@ -1607,7 +1615,7 @@ abstract class ElectrumWalletBase balance[currency]?.confirmed += newBalance.confirmed; balance[currency]?.unconfirmed += newBalance.unconfirmed; - await _fetchAddressHistory(address, await currentChainTip); + await _fetchAddressHistory(address, await getCurrentChainTip()); } catch (e, s) { print(e.toString()); _onError?.call(FlutterErrorDetails( @@ -1853,23 +1861,13 @@ Future startRefresh(ScanData scanData) async { tweaksSubscription?.listen((t) { final tweaks = t as Map; - if (tweaks.isEmpty) { - syncHeight += 1; - scanData.sendPort.send( - SyncResponse( - syncHeight, - SyncingSyncStatus.fromHeightValues( - currentChainTip, - initialSyncHeight, - syncHeight, - ), - ), - ); - + if (tweaks["message"] != null) { + // re-subscribe to continue receiving messages + electrumClient.tweaksSubscribe(height: syncHeight); return; } - final blockHeight = tweaks.keys.first.toString(); + final blockHeight = tweaks.keys.first; try { final blockTweaks = tweaks[blockHeight] as Map; @@ -1963,7 +1961,7 @@ Future startRefresh(ScanData scanData) async { } } catch (_) {} - syncHeight += 1; + syncHeight = int.parse(blockHeight); scanData.sendPort.send( SyncResponse( syncHeight, diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 792fd6f894..0117200526 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -23,7 +23,7 @@ import 'package:encrypt/encrypt.dart' as encrypt; import 'package:collection/collection.dart'; const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081'; -const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002'; +const cakeWalletBitcoinElectrumUri = '198.58.111.154:50002'; const publicBitcoinTestnetElectrumAddress = '198.58.111.154'; const publicBitcoinTestnetElectrumPort = '50002'; const publicBitcoinTestnetElectrumUri = @@ -36,7 +36,7 @@ const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002'; const nanoDefaultNodeUri = 'rpc.nano.to'; const nanoDefaultPowNodeUri = 'rpc.nano.to'; const solanaDefaultNodeUri = 'rpc.ankr.com'; -const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002'; +const newCakeWalletBitcoinUri = '198.58.111.154:50002'; Future defaultSettingsMigration( {required int version, From 5b3128f7519586b23946a6861e432f357540ee58 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Wed, 17 Apr 2024 22:29:53 -0300 Subject: [PATCH 041/242] fix: default nodes --- lib/entities/default_settings_migration.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 0117200526..882cced1b6 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -23,7 +23,7 @@ import 'package:encrypt/encrypt.dart' as encrypt; import 'package:collection/collection.dart'; const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081'; -const cakeWalletBitcoinElectrumUri = '198.58.111.154:50002'; +const cakeWalletBitcoinElectrumUri = '198.58.111.154:50001'; const publicBitcoinTestnetElectrumAddress = '198.58.111.154'; const publicBitcoinTestnetElectrumPort = '50002'; const publicBitcoinTestnetElectrumUri = @@ -36,7 +36,7 @@ const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002'; const nanoDefaultNodeUri = 'rpc.nano.to'; const nanoDefaultPowNodeUri = 'rpc.nano.to'; const solanaDefaultNodeUri = 'rpc.ankr.com'; -const newCakeWalletBitcoinUri = '198.58.111.154:50002'; +const newCakeWalletBitcoinUri = '198.58.111.154:50001'; Future defaultSettingsMigration( {required int version, @@ -766,7 +766,8 @@ Future changeDefaultBitcoinNode( final needToReplaceCurrentBitcoinNode = currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern); - final newCakeWalletBitcoinNode = Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin); + final newCakeWalletBitcoinNode = + Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false); await nodeSource.add(newCakeWalletBitcoinNode); @@ -819,7 +820,7 @@ Future checkCurrentNodes( if (currentBitcoinElectrumServer == null) { final cakeWalletElectrum = - Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: true); + Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false); await nodeSource.add(cakeWalletElectrum); await sharedPreferences.setInt( PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int); @@ -892,7 +893,7 @@ Future resetBitcoinElectrumServer( if (cakeWalletNode == null) { cakeWalletNode = - Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: true); + Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false); await nodeSource.add(cakeWalletNode); } From ca81d44e6c39feeba3c6bc5234450f8124a1fc57 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Thu, 18 Apr 2024 08:57:16 -0300 Subject: [PATCH 042/242] fix: improve scanning by date, fix single block scan --- cw_bitcoin/lib/electrum.dart | 8 +++----- cw_bitcoin/lib/electrum_wallet.dart | 26 ++++++++++++++------------ cw_core/lib/get_height_by_date.dart | 12 ++++++++---- lib/reactions/check_connection.dart | 21 +++++++++++---------- 4 files changed, 36 insertions(+), 31 deletions(-) diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index d7c35c6ee6..a76e9b7bab 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -8,8 +8,6 @@ import 'package:cw_bitcoin/script_hash.dart'; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; -const int TWEAKS_COUNT = 20; - String jsonrpcparams(List params) { final _params = params.map((val) => '"${val.toString()}"').join(','); return '[$_params]'; @@ -280,12 +278,12 @@ class ElectrumClient { Future> getHeader({required int height}) async => await call(method: 'blockchain.block.get_header', params: [height]) as Map; - BehaviorSubject? tweaksSubscribe({required int height}) { + BehaviorSubject? tweaksSubscribe({required int height, required int count}) { _id += 1; return subscribe( - id: 'blockchain.tweaks.subscribe:${height + TWEAKS_COUNT}', + id: 'blockchain.tweaks.subscribe:${height + count}', method: 'blockchain.tweaks.subscribe', - params: [height, TWEAKS_COUNT], + params: [height, count], ); } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 38d6c5fec6..4fa640afe6 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -48,6 +48,8 @@ part 'electrum_wallet.g.dart'; class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; +const int TWEAKS_COUNT = 25; + abstract class ElectrumWalletBase extends WalletBase with Store { @@ -154,8 +156,6 @@ abstract class ElectrumWalletBase silentPaymentsScanningActive = active; if (active) { - await _setInitialHeight(); - if ((await getCurrentChainTip()) > walletInfo.restoreHeight) { _setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip); } @@ -1855,19 +1855,22 @@ Future startRefresh(ScanData scanData) async { final electrumClient = await getElectrumConnection(); if (tweaksSubscription == null) { + final count = scanData.isSingleScan ? 1 : TWEAKS_COUNT; + try { - tweaksSubscription = await electrumClient.tweaksSubscribe(height: syncHeight); + tweaksSubscription = await electrumClient.tweaksSubscribe(height: syncHeight, count: count); - tweaksSubscription?.listen((t) { + tweaksSubscription?.listen((t) async { final tweaks = t as Map; - if (tweaks["message"] != null) { + if (tweaks["message"] != null && !scanData.isSingleScan) { // re-subscribe to continue receiving messages - electrumClient.tweaksSubscribe(height: syncHeight); + electrumClient.tweaksSubscribe(height: syncHeight, count: count); return; } final blockHeight = tweaks.keys.first; + final tweakHeight = int.parse(blockHeight); try { final blockTweaks = tweaks[blockHeight] as Map; @@ -1901,13 +1904,13 @@ Future startRefresh(ScanData scanData) async { final txInfo = ElectrumTransactionInfo( WalletType.bitcoin, id: txid, - height: syncHeight, + height: tweakHeight, amount: 0, fee: 0, direction: TransactionDirection.incoming, isPending: false, date: DateTime.now(), - confirmations: scanData.chainTip - int.parse(blockHeight) + 1, + confirmations: scanData.chainTip - tweakHeight + 1, unspents: [], ); @@ -1961,7 +1964,7 @@ Future startRefresh(ScanData scanData) async { } } catch (_) {} - syncHeight = int.parse(blockHeight); + syncHeight = tweakHeight; scanData.sendPort.send( SyncResponse( syncHeight, @@ -1973,11 +1976,10 @@ Future startRefresh(ScanData scanData) async { ), ); - if (int.parse(blockHeight) >= scanData.chainTip) { + if (int.parse(blockHeight) >= scanData.chainTip || scanData.isSingleScan) { scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus())); + await tweaksSubscription!.close(); } - - return; }); } catch (e) { if (e is RequestFailedTimeoutException) { diff --git a/cw_core/lib/get_height_by_date.dart b/cw_core/lib/get_height_by_date.dart index 1dc624a7ff..11bc370c5b 100644 --- a/cw_core/lib/get_height_by_date.dart +++ b/cw_core/lib/get_height_by_date.dart @@ -264,10 +264,14 @@ const bitcoinDates = { }; int getBitcoinHeightByDate({required DateTime date}) { - String closestKey = - bitcoinDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => ''); + String dateKey = '${date.year}-${date.month.toString().padLeft(2, '0')}'; + int startBlock = bitcoinDates[dateKey] ?? bitcoinDates.values.last; + + DateTime startOfMonth = DateTime(date.year, date.month); + int daysDifference = date.difference(startOfMonth).inDays; - final oldestHeight = bitcoinDates.values.last; + // approximately 6 blocks per hour, 24 hours per day + int estimatedBlocksSinceStartOfMonth = (daysDifference * 24 * 6); - return bitcoinDates[closestKey] ?? oldestHeight; + return startBlock + estimatedBlocksSinceStartOfMonth; } diff --git a/lib/reactions/check_connection.dart b/lib/reactions/check_connection.dart index 9185ffe152..3252797ddd 100644 --- a/lib/reactions/check_connection.dart +++ b/lib/reactions/check_connection.dart @@ -3,15 +3,19 @@ import 'dart:async'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/store/settings_store.dart'; + Timer? _checkConnectionTimer; -void startCheckConnectionReaction( - WalletBase wallet, SettingsStore settingsStore, +void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore, {int timeInterval = 5}) { _checkConnectionTimer?.cancel(); - _checkConnectionTimer = - Timer.periodic(Duration(seconds: timeInterval), (_) async { + _checkConnectionTimer = Timer.periodic(Duration(seconds: timeInterval), (_) async { + if (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus) { + return; + } + try { final connectivityResult = await (Connectivity().checkConnectivity()); @@ -20,14 +24,11 @@ void startCheckConnectionReaction( return; } - if (wallet.syncStatus is LostConnectionSyncStatus || - wallet.syncStatus is FailedSyncStatus) { - final alive = - await settingsStore.getCurrentNode(wallet.type).requestNode(); + if (wallet.syncStatus is LostConnectionSyncStatus || wallet.syncStatus is FailedSyncStatus) { + final alive = await settingsStore.getCurrentNode(wallet.type).requestNode(); if (alive) { - await wallet.connectToNode( - node: settingsStore.getCurrentNode(wallet.type)); + await wallet.connectToNode(node: settingsStore.getCurrentNode(wallet.type)); } } } catch (e) { From 67256d30e6844e099b3be205c9f2a6e99994a59d Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Thu, 18 Apr 2024 10:38:18 -0300 Subject: [PATCH 043/242] refactor: common function for input tx selection --- cw_bitcoin/lib/electrum_wallet.dart | 353 +++++++++-------------- lib/view_model/send/send_view_model.dart | 4 + res/values/strings_ar.arb | 3 +- res/values/strings_bg.arb | 3 +- res/values/strings_cs.arb | 3 +- res/values/strings_de.arb | 3 +- res/values/strings_en.arb | 3 +- res/values/strings_es.arb | 3 +- res/values/strings_fr.arb | 3 +- res/values/strings_ha.arb | 3 +- res/values/strings_hi.arb | 3 +- res/values/strings_hr.arb | 3 +- res/values/strings_id.arb | 3 +- res/values/strings_it.arb | 3 +- res/values/strings_ja.arb | 3 +- res/values/strings_ko.arb | 3 +- res/values/strings_my.arb | 3 +- res/values/strings_nl.arb | 3 +- res/values/strings_pl.arb | 3 +- res/values/strings_pt.arb | 3 +- res/values/strings_ru.arb | 3 +- res/values/strings_th.arb | 3 +- res/values/strings_tl.arb | 3 +- res/values/strings_tr.arb | 3 +- res/values/strings_uk.arb | 3 +- res/values/strings_ur.arb | 3 +- res/values/strings_yo.arb | 3 +- res/values/strings_zh.arb | 3 +- 28 files changed, 200 insertions(+), 235 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 4fa640afe6..b171d08d64 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -352,6 +352,8 @@ abstract class ElectrumWalletBase updateAllUnspents().then(checkFinishedAllUpdates); updateBalance().then(checkFinishedAllUpdates); + + updateFeeRates(); } catch (e, stacktrace) { print(stacktrace); print(e.toString()); @@ -410,128 +412,87 @@ abstract class ElectrumWalletBase bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet; - void _createSilentPayments( - List outputs, - List inputPubKeys, - List vinOutpoints, - List inputPrivKeyInfos, - ) { - List silentPaymentDestinations = []; - - for (final out in outputs) { - final address = out.address; - final amount = out.value; - - if (address is SilentPaymentAddress) { - silentPaymentDestinations.add( - SilentPaymentDestination.fromAddress(address.toAddress(network), amount.toInt()), - ); - } - } - - if (silentPaymentDestinations.isNotEmpty) { - final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); - final sendingOutputs = spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations); - - final outputsAdded = []; - - for (var i = 0; i < outputs.length; i++) { - final out = outputs[i]; - - final silentOutputs = sendingOutputs[out.address.toAddress(network)]; - if (silentOutputs != null) { - final silentOutput = - silentOutputs.firstWhereOrNull((element) => !outputsAdded.contains(element)); - - if (silentOutput != null) { - outputs[i] = BitcoinOutput( - address: silentOutput.address, - value: BigInt.from(silentOutput.amount), - ); - - outputsAdded.add(silentOutput); - } - } - } - } - } - - Future estimateSendAllTx( - List outputs, - int feeRate, { - String? memo, - int credentialsAmount = 0, - bool hasSilentPayment = false, - }) async { - final utxos = []; - int allInputsAmount = 0; - + UtxoDetails _createUTXOS({ + required bool sendAll, + required int credentialsAmount, + required bool paysToSilentPayment, + int? inputsCount, + }) { + List utxos = []; List vinOutpoints = []; List inputPrivKeyInfos = []; - List inputPubKeys = []; + int allInputsAmount = 0; bool spendsSilentPayment = false; bool spendsCPFP = false; - for (int i = 0; i < unspentCoins.length; i++) { - final utx = unspentCoins[i]; + int leftAmount = credentialsAmount; + final availableInputs = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList(); - if (utx.isSending && !utx.isFrozen) { - if (hasSilentPayment) { - // Check inputs for shared secret derivation - if (utx.bitcoinAddressRecord.type == SegwitAddresType.p2wsh) { - throw BitcoinTransactionSilentPaymentsNotSupported(); - } - } + for (int i = 0; i < availableInputs.length; i++) { + final utx = availableInputs[i]; - if (!spendsCPFP) spendsCPFP = utx.confirmations == 0; + if (paysToSilentPayment) { + // Check inputs for shared secret derivation + if (utx.bitcoinAddressRecord.type == SegwitAddresType.p2wsh) { + throw BitcoinTransactionSilentPaymentsNotSupported(); + } + } - allInputsAmount += utx.value; + spendsCPFP = (utx.confirmations ?? 0) <= 0; - final address = addressTypeFromStr(utx.address, network); + allInputsAmount += utx.value; + leftAmount = leftAmount - utx.value; - ECPrivate? privkey; - bool? isSilentPayment = false; + final address = addressTypeFromStr(utx.address, network); + ECPrivate? privkey; + bool? isSilentPayment = false; - if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { - final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; - privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( - BigintUtils.fromBytes( - BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!), - ), - ); - spendsSilentPayment = true; - isSilentPayment = true; - } else { - privkey = generateECPrivate( - hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, - index: utx.bitcoinAddressRecord.index, - network: network, - ); - } + if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { + final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; + privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( + BigintUtils.fromBytes( + BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!), + ), + ); + spendsSilentPayment = true; + isSilentPayment = true; + } else { + privkey = generateECPrivate( + hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + index: utx.bitcoinAddressRecord.index, + network: network, + ); + } - inputPrivKeyInfos.add(ECPrivateInfo( - privkey, - address.type == SegwitAddresType.p2tr, - tweak: !isSilentPayment, - )); - inputPubKeys.add(privkey.getPublic()); - vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); + vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); + inputPrivKeyInfos.add(ECPrivateInfo( + privkey, + address.type == SegwitAddresType.p2tr, + tweak: !isSilentPayment, + )); - utxos.add( - UtxoWithAddress( - utxo: BitcoinUtxo( - txHash: utx.hash, - value: BigInt.from(utx.value), - vout: utx.vout, - scriptType: _getScriptType(address), - isSilentPayment: isSilentPayment, - ), - ownerDetails: UtxoAddressDetails( - publicKey: privkey.getPublic().toHex(), - address: address, - ), + utxos.add( + UtxoWithAddress( + utxo: BitcoinUtxo( + txHash: utx.hash, + value: BigInt.from(utx.value), + vout: utx.vout, + scriptType: _getScriptType(address), + isSilentPayment: isSilentPayment, ), - ); + ownerDetails: UtxoAddressDetails( + publicKey: privkey.getPublic().toHex(), + address: address, + ), + ), + ); + + // sendAll continues for all inputs + if (!sendAll) { + bool amountIsAcquired = leftAmount <= 0; + if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) { + break; + } } } @@ -539,24 +500,46 @@ abstract class ElectrumWalletBase throw BitcoinTransactionNoInputsException(); } - if (hasSilentPayment == true) { - _createSilentPayments(outputs, inputPubKeys, vinOutpoints, inputPrivKeyInfos); - } + return UtxoDetails( + availableInputs: availableInputs, + utxos: utxos, + vinOutpoints: vinOutpoints, + inputPrivKeyInfos: inputPrivKeyInfos, + allInputsAmount: allInputsAmount, + spendsSilentPayment: spendsSilentPayment, + spendsCPFP: spendsCPFP, + ); + } + + Future estimateSendAllTx( + List outputs, + int feeRate, { + String? memo, + int credentialsAmount = 0, + bool hasSilentPayment = false, + }) async { + final utxoDetails = _createUTXOS( + sendAll: true, + credentialsAmount: credentialsAmount, + paysToSilentPayment: hasSilentPayment, + ); int estimatedSize; if (network is BitcoinCashNetwork) { estimatedSize = ForkedTransactionBuilder.estimateTransactionSize( - utxos: utxos, + utxos: utxoDetails.utxos, outputs: outputs, network: network as BitcoinCashNetwork, memo: memo, ); } else { estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize( - utxos: utxos, + utxos: utxoDetails.utxos, outputs: outputs, network: network, memo: memo, + inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, + vinOutpoints: utxoDetails.vinOutpoints, ); } @@ -567,10 +550,10 @@ abstract class ElectrumWalletBase } // Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change - int amount = allInputsAmount - fee; + int amount = utxoDetails.allInputsAmount - fee; if (amount <= 0) { - throw BitcoinTransactionWrongBalanceException(); + throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee); } // Attempting to send less than the dust limit @@ -590,15 +573,15 @@ abstract class ElectrumWalletBase BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount)); return EstimatedTxResult( - utxos: utxos, - inputPrivKeyInfos: inputPrivKeyInfos, + utxos: utxoDetails.utxos, + inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, fee: fee, amount: amount, isSendAll: true, hasChange: false, memo: memo, - spendsSilentPayment: spendsSilentPayment, - spendsCPFP: spendsCPFP, + spendsSilentPayment: utxoDetails.spendsSilentPayment, + spendsCPFP: utxoDetails.spendsCPFP, ); } @@ -610,87 +593,17 @@ abstract class ElectrumWalletBase String? memo, bool hasSilentPayment = false, }) async { - final utxos = []; - List vinOutpoints = []; - List inputPrivKeyInfos = []; - List inputPubKeys = []; - int allInputsAmount = 0; - bool spendsSilentPayment = false; - bool spendsCPFP = false; - - int leftAmount = credentialsAmount; - final sendingCoins = unspentCoins.where((utx) => utx.isSending).toList(); - - for (int i = 0; i < sendingCoins.length; i++) { - final utx = sendingCoins[i]; - spendsCPFP = utx.confirmations == 0; - - allInputsAmount += utx.value; - leftAmount = leftAmount - utx.value; - - final address = addressTypeFromStr(utx.address, network); - ECPrivate? privkey; - bool? isSilentPayment = false; - - if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { - final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; - privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( - BigintUtils.fromBytes( - BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!), - ), - ); - spendsSilentPayment = true; - isSilentPayment = true; - } else { - privkey = generateECPrivate( - hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, - index: utx.bitcoinAddressRecord.index, - network: network, - ); - } - - inputPrivKeyInfos.add(ECPrivateInfo( - privkey, - address.type == SegwitAddresType.p2tr, - tweak: !isSilentPayment, - )); - inputPubKeys.add(privkey.getPublic()); - vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); - - utxos.add( - UtxoWithAddress( - utxo: BitcoinUtxo( - txHash: utx.hash, - value: BigInt.from(utx.value), - vout: utx.vout, - scriptType: _getScriptType(address), - isSilentPayment: isSilentPayment, - ), - ownerDetails: UtxoAddressDetails( - publicKey: privkey.getPublic().toHex(), - address: address, - ), - ), - ); - - bool amountIsAcquired = leftAmount <= 0; - if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) { - break; - } - } - - if (utxos.isEmpty) { - throw BitcoinTransactionNoInputsException(); - } - - if (hasSilentPayment == true) { - _createSilentPayments(outputs, inputPubKeys, vinOutpoints, inputPrivKeyInfos); - } + final utxoDetails = _createUTXOS( + sendAll: false, + credentialsAmount: credentialsAmount, + inputsCount: inputsCount, + paysToSilentPayment: hasSilentPayment, + ); - final spendingAllCoins = sendingCoins.length == utxos.length; + final spendingAllCoins = utxoDetails.availableInputs.length == utxoDetails.utxos.length; // How much is being spent - how much is being sent - int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount; + int amountLeftForChangeAndFee = utxoDetails.allInputsAmount - credentialsAmount; if (amountLeftForChangeAndFee <= 0) { if (!spendingAllCoins) { @@ -698,11 +611,12 @@ abstract class ElectrumWalletBase credentialsAmount, outputs, feeRate, - inputsCount: utxos.length + 1, + inputsCount: utxoDetails.utxos.length + 1, memo: memo, hasSilentPayment: hasSilentPayment, ); } + throw BitcoinTransactionWrongBalanceException(); } @@ -716,17 +630,19 @@ abstract class ElectrumWalletBase int estimatedSize; if (network is BitcoinCashNetwork) { estimatedSize = ForkedTransactionBuilder.estimateTransactionSize( - utxos: utxos, + utxos: utxoDetails.utxos, outputs: outputs, network: network as BitcoinCashNetwork, memo: memo, ); } else { estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize( - utxos: utxos, + utxos: utxoDetails.utxos, outputs: outputs, network: network, memo: memo, + inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, + vinOutpoints: utxoDetails.vinOutpoints, ); } @@ -755,7 +671,7 @@ abstract class ElectrumWalletBase credentialsAmount, outputs, feeRate, - inputsCount: utxos.length + 1, + inputsCount: utxoDetails.utxos.length + 1, memo: memo, ); } @@ -771,7 +687,7 @@ abstract class ElectrumWalletBase } // Estimate to user how much is needed to send to cover the fee - final maxAmountWithReturningChange = allInputsAmount - _dustAmount - fee - 1; + final maxAmountWithReturningChange = utxoDetails.allInputsAmount - _dustAmount - fee - 1; throw BitcoinTransactionNoDustOnChangeException( bitcoinAmountToString(amount: maxAmountWithReturningChange), bitcoinAmountToString(amount: estimatedSendAll.amount), @@ -789,7 +705,7 @@ abstract class ElectrumWalletBase throw BitcoinTransactionWrongBalanceException(); } - if (totalAmount > allInputsAmount) { + if (totalAmount > utxoDetails.allInputsAmount) { if (spendingAllCoins) { throw BitcoinTransactionWrongBalanceException(); } else { @@ -797,12 +713,11 @@ abstract class ElectrumWalletBase outputs.removeLast(); } - outputs.removeLast(); return estimateTxForAmount( credentialsAmount, outputs, feeRate, - inputsCount: utxos.length + 1, + inputsCount: utxoDetails.utxos.length + 1, memo: memo, hasSilentPayment: hasSilentPayment, ); @@ -810,15 +725,15 @@ abstract class ElectrumWalletBase } return EstimatedTxResult( - utxos: utxos, - inputPrivKeyInfos: inputPrivKeyInfos, + utxos: utxoDetails.utxos, + inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, fee: fee, amount: amount, hasChange: true, isSendAll: false, memo: memo, - spendsSilentPayment: spendsSilentPayment, - spendsCPFP: spendsCPFP, + spendsSilentPayment: utxoDetails.spendsSilentPayment, + spendsCPFP: utxoDetails.spendsCPFP, ); } @@ -2067,3 +1982,23 @@ BitcoinAddressType _getScriptType(BitcoinBaseAddress type) { return SegwitAddresType.p2wpkh; } } + +class UtxoDetails { + final List availableInputs; + final List utxos; + final List vinOutpoints; + final List inputPrivKeyInfos; + final int allInputsAmount; + final bool spendsSilentPayment; + final bool spendsCPFP; + + UtxoDetails({ + required this.availableInputs, + required this.utxos, + required this.vinOutpoints, + required this.inputPrivKeyInfos, + required this.allInputsAmount, + required this.spendsSilentPayment, + required this.spendsCPFP, + }); +} diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 0bea0c59bf..3cc379a282 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -541,6 +541,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor walletType == WalletType.litecoin || walletType == WalletType.bitcoinCash) { if (error is TransactionWrongBalanceException) { + if (error.amount != null) + return S.current + .tx_wrong_balance_with_amount_exception(currency.toString(), error.amount.toString()); + return S.current.tx_wrong_balance_exception(currency.toString()); } if (error is TransactionNoInputsException) { diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 7fd44c588f..eaf39c7faa 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى التحقق من رصيد العملات المعدنية المحددة تحت التحكم في العملة.", "tx_rejected_vout_negative": "لا يوجد ما يكفي من الرصيد لدفع رسوم هذه الصفقة. يرجى التحقق من رصيد العملات المعدنية تحت السيطرة على العملة.", "tx_wrong_balance_exception": "ليس لديك ما يكفي من ${currency} لإرسال هذا المبلغ.", + "tx_wrong_balance_with_amount_exception": "ليس لديك ما يكفي ${currency} لإرسال المبلغ الإجمالي ${amount}", "tx_zero_fee_exception": "لا يمكن إرسال معاملة مع 0 رسوم. حاول زيادة المعدل أو التحقق من اتصالك للحصول على أحدث التقديرات.", "unavailable_balance": "ﺮﻓﻮﺘﻣ ﺮﻴﻏ ﺪﻴﺻﺭ", "unavailable_balance_description": ".ﺎﻫﺪﻴﻤﺠﺗ ءﺎﻐﻟﺇ ﺭﺮﻘﺗ ﻰﺘﺣ ﺕﻼﻣﺎﻌﻤﻠﻟ ﻝﻮﺻﻮﻠﻟ ﺔﻠﺑﺎﻗ ﺮﻴﻏ ﺓﺪﻤﺠﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﻞﻈﺗ ﺎﻤﻨﻴﺑ ،ﺎﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻣﺎﻌﻤﻟﺍ ﻝﺎﻤﺘﻛﺍ ﺩﺮﺠﻤﺑ ﺔﺣﺎﺘﻣ ﺔﻠﻔﻘﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﺢﺒﺼﺘﺳ .ﻚﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻤﻌﻟﺍ ﻲﻓ ﻢﻜﺤﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻂﺸﻧ ﻞﻜﺸﺑ ﺎﻫﺪﻴﻤﺠﺘﺑ ﺖﻤﻗ", @@ -837,4 +838,4 @@ "you_will_get": "حول الى", "you_will_send": "تحويل من", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 6acbfd6646..17795c1ccb 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, проверете баланса на монетите, избрани под контрол на монети.", "tx_rejected_vout_negative": "Няма достатъчно баланс, за да платите за таксите на тази транзакция. Моля, проверете баланса на монетите под контрол на монетите.", "tx_wrong_balance_exception": "Нямате достатъчно ${currency}, за да изпратите тази сума.", + "tx_wrong_balance_with_amount_exception": "Нямате достатъчно ${currency} За да изпратите общата сума на ${amount}", "tx_zero_fee_exception": "Не може да изпраща транзакция с 0 такса. Опитайте да увеличите скоростта или да проверите връзката си за най -новите оценки.", "unavailable_balance": "Неналично салдо", "unavailable_balance_description": "Неналично салдо: Тази обща сума включва средства, които са заключени в чакащи транзакции и тези, които сте замразили активно в настройките за контрол на монетите. Заключените баланси ще станат достъпни, след като съответните им транзакции бъдат завършени, докато замразените баланси остават недостъпни за транзакции, докато не решите да ги размразите.", @@ -837,4 +838,4 @@ "you_will_get": "Обръщане в", "you_will_send": "Обръщане от", "yy": "гг" -} \ No newline at end of file +} diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 9bd20e6606..902f506930 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zkontrolujte prosím zůstatek mincí vybraných pod kontrolou mincí.", "tx_rejected_vout_negative": "Nedostatek zůstatek na zaplacení poplatků za tuto transakci. Zkontrolujte prosím zůstatek mincí pod kontrolou mincí.", "tx_wrong_balance_exception": "Nemáte dost ${currency} pro odeslání této částky.", + "tx_wrong_balance_with_amount_exception": "Nemáte dost ${currency} na odeslání celkové částky ${amount}", "tx_zero_fee_exception": "Nelze odeslat transakci s 0 poplatkem. Zkuste zvýšit sazbu nebo zkontrolovat připojení pro nejnovější odhady.", "unavailable_balance": "Nedostupný zůstatek", "unavailable_balance_description": "Nedostupný zůstatek: Tento součet zahrnuje prostředky, které jsou uzamčeny v nevyřízených transakcích a ty, které jste aktivně zmrazili v nastavení kontroly mincí. Uzamčené zůstatky budou k dispozici po dokončení příslušných transakcí, zatímco zmrazené zůstatky zůstanou pro transakce nepřístupné, dokud se nerozhodnete je uvolnit.", @@ -837,4 +838,4 @@ "you_will_get": "Směnit na", "you_will_send": "Směnit z", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 2ab9a4b62c..4c424264c6 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -757,6 +757,7 @@ "tx_rejected_dust_output_send_all": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte überprüfen Sie den Gleichgewicht der unter Münzkontrolle ausgewählten Münzen.", "tx_rejected_vout_negative": "Nicht genug Guthaben, um die Gebühren dieser Transaktion zu bezahlen. Bitte überprüfen Sie den Restbetrag der Münzen unter Münzkontrolle.", "tx_wrong_balance_exception": "Sie haben nicht genug ${currency}, um diesen Betrag zu senden.", + "tx_wrong_balance_with_amount_exception": "Sie haben nicht genug ${currency}, um die Gesamtmenge von ${amount} zu senden", "tx_zero_fee_exception": "Transaktion kann nicht mit 0 Gebühren gesendet werden. Versuchen Sie, die Rate zu erhöhen oder Ihre Verbindung auf die neuesten Schätzungen zu überprüfen.", "unavailable_balance": "Nicht verfügbares Guthaben", "unavailable_balance_description": "Nicht verfügbares Guthaben: Diese Summe umfasst Gelder, die in ausstehenden Transaktionen gesperrt sind, und solche, die Sie in Ihren Münzkontrolleinstellungen aktiv eingefroren haben. Gesperrte Guthaben werden verfügbar, sobald die entsprechenden Transaktionen abgeschlossen sind, während eingefrorene Guthaben für Transaktionen nicht zugänglich bleiben, bis Sie sich dazu entschließen, sie wieder freizugeben.", @@ -840,4 +841,4 @@ "you_will_get": "Konvertieren zu", "you_will_send": "Konvertieren von", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 53c892b0d4..491264ba2f 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "Transaction rejected by network rules, low output amount (dust). Please check the balance of coins selected under Coin Control.", "tx_rejected_vout_negative": "Not enough balance to pay for this transaction's fees. Please check the balance of coins under Coin Control.", "tx_wrong_balance_exception": "You do not have enough ${currency} to send this amount.", + "tx_wrong_balance_with_amount_exception": "You do not have enough ${currency} to send the total amount of ${amount}", "tx_zero_fee_exception": "Cannot send transaction with 0 fee. Try increasing the rate or checking your connection for latest estimates.", "unavailable_balance": "Unavailable balance", "unavailable_balance_description": "Unavailable Balance: This total includes funds that are locked in pending transactions and those you have actively frozen in your coin control settings. Locked balances will become available once their respective transactions are completed, while frozen balances remain inaccessible for transactions until you decide to unfreeze them.", @@ -837,4 +838,4 @@ "you_will_get": "Convert to", "you_will_send": "Convert from", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 1f6fe23977..0752b8ce1a 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -757,6 +757,7 @@ "tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Verifique el saldo de monedas seleccionadas bajo control de monedas.", "tx_rejected_vout_negative": "No es suficiente saldo para pagar las tarifas de esta transacción. Verifique el saldo de monedas bajo control de monedas.", "tx_wrong_balance_exception": "No tiene suficiente ${currency} para enviar esta cantidad.", + "tx_wrong_balance_with_amount_exception": "No tiene suficiente ${currency} para enviar la cantidad total de ${amount}", "tx_zero_fee_exception": "No se puede enviar transacciones con 0 tarifa. Intente aumentar la tasa o verificar su conexión para las últimas estimaciones.", "unavailable_balance": "Saldo no disponible", "unavailable_balance_description": "Saldo no disponible: este total incluye fondos que están bloqueados en transacciones pendientes y aquellos que usted ha congelado activamente en su configuración de control de monedas. Los saldos bloqueados estarán disponibles una vez que se completen sus respectivas transacciones, mientras que los saldos congelados permanecerán inaccesibles para las transacciones hasta que usted decida descongelarlos.", @@ -838,4 +839,4 @@ "you_will_get": "Convertir a", "you_will_send": "Convertir de", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 060bf33090..b65347ad1f 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez vérifier le solde des pièces sélectionnées sous le contrôle des pièces de monnaie.", "tx_rejected_vout_negative": "Pas assez de solde pour payer les frais de cette transaction. Veuillez vérifier le solde des pièces sous le contrôle des pièces.", "tx_wrong_balance_exception": "Vous n'avez pas assez ${currency} pour envoyer ce montant.", + "tx_wrong_balance_with_amount_exception": "Vous n'avez pas assez ${currency} pour envoyer le montant total de ${amount}", "tx_zero_fee_exception": "Impossible d'envoyer une transaction avec 0 frais. Essayez d'augmenter le taux ou de vérifier votre connexion pour les dernières estimations.", "unavailable_balance": "Solde indisponible", "unavailable_balance_description": "Solde indisponible : ce total comprend les fonds bloqués dans les transactions en attente et ceux que vous avez activement gelés dans vos paramètres de contrôle des pièces. Les soldes bloqués deviendront disponibles une fois leurs transactions respectives terminées, tandis que les soldes gelés resteront inaccessibles aux transactions jusqu'à ce que vous décidiez de les débloquer.", @@ -837,4 +838,4 @@ "you_will_get": "Convertir vers", "you_will_send": "Convertir depuis", "yy": "AA" -} \ No newline at end of file +} diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 28e4747abf..a663139505 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -758,6 +758,7 @@ "tx_rejected_dust_output_send_all": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.", "tx_rejected_vout_negative": "Bai isa daidai ba don biyan wannan kudin ma'amala. Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.", "tx_wrong_balance_exception": "Ba ku da isasshen ${currency} don aika wannan adadin.", + "tx_wrong_balance_with_amount_exception": "Ba ku da isasshen ${currency} don aika jimlar adadin ${amount}", "tx_zero_fee_exception": "Ba zai iya aika ma'amala da kuɗi 0 ba. Gwada ƙara ƙimar ko bincika haɗin ku don mahimmin ƙididdiga.", "unavailable_balance": "Ma'aunin da ba ya samuwa", "unavailable_balance_description": "Ma'auni Babu: Wannan jimlar ya haɗa da kuɗi waɗanda ke kulle a cikin ma'amaloli da ke jiran aiki da waɗanda kuka daskare sosai a cikin saitunan sarrafa kuɗin ku. Ma'auni da aka kulle za su kasance da zarar an kammala ma'amalolinsu, yayin da daskararrun ma'auni ba za su iya samun damar yin ciniki ba har sai kun yanke shawarar cire su.", @@ -839,4 +840,4 @@ "you_will_get": "Maida zuwa", "you_will_send": "Maida daga", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index b92eed8370..df9e42b277 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -758,6 +758,7 @@ "tx_rejected_dust_output_send_all": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया सिक्का नियंत्रण के तहत चुने गए सिक्कों के संतुलन की जाँच करें।", "tx_rejected_vout_negative": "इस लेनदेन की फीस के लिए भुगतान करने के लिए पर्याप्त शेष राशि नहीं है। कृपया सिक्के नियंत्रण के तहत सिक्कों के संतुलन की जाँच करें।", "tx_wrong_balance_exception": "इस राशि को भेजने के लिए आपके पास पर्याप्त ${currency} नहीं है।", + "tx_wrong_balance_with_amount_exception": "आपके पास पर्याप्त नहीं है${currency} ${amount} की कुल राशि भेजने के लिए", "tx_zero_fee_exception": "0 शुल्क के साथ लेनदेन नहीं भेज सकते। नवीनतम अनुमानों के लिए दर बढ़ाने या अपने कनेक्शन की जांच करने का प्रयास करें।", "unavailable_balance": "अनुपलब्ध शेष", "unavailable_balance_description": "अनुपलब्ध शेष राशि: इस कुल में वे धनराशि शामिल हैं जो लंबित लेनदेन में बंद हैं और जिन्हें आपने अपनी सिक्का नियंत्रण सेटिंग्स में सक्रिय रूप से जमा कर रखा है। लॉक किए गए शेष उनके संबंधित लेन-देन पूरे होने के बाद उपलब्ध हो जाएंगे, जबकि जमे हुए शेष लेन-देन के लिए अप्राप्य रहेंगे जब तक कि आप उन्हें अनफ्रीज करने का निर्णय नहीं लेते।", @@ -839,4 +840,4 @@ "you_will_get": "में बदलें", "you_will_send": "से रूपांतरित करें", "yy": "वाईवाई" -} \ No newline at end of file +} diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index b4200861d2..a6a1c976d1 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo provjerite ravnotežu kovanica odabranih pod kontrolom novčića.", "tx_rejected_vout_negative": "Nema dovoljno salda za plaćanje naknada ove transakcije. Molimo provjerite ravnotežu kovanica pod kontrolom novčića.", "tx_wrong_balance_exception": "Nemate dovoljno ${currency} da biste poslali ovaj iznos.", + "tx_wrong_balance_with_amount_exception": "Nemate dovoljno ${currency} za slanje ukupne količine ${amount}", "tx_zero_fee_exception": "Ne mogu poslati transakciju s 0 naknade. Pokušajte povećati stopu ili provjeriti vezu za najnovije procjene.", "unavailable_balance": "Nedostupno stanje", "unavailable_balance_description": "Nedostupno stanje: Ovaj ukupni iznos uključuje sredstva koja su zaključana u transakcijama na čekanju i ona koja ste aktivno zamrznuli u postavkama kontrole novčića. Zaključani saldi postat će dostupni kada se dovrše njihove transakcije, dok zamrznuti saldi ostaju nedostupni za transakcije sve dok ih ne odlučite odmrznuti.", @@ -837,4 +838,4 @@ "you_will_get": "Razmijeni u", "you_will_send": "Razmijeni iz", "yy": "GG" -} \ No newline at end of file +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 43ab36319a..3cd9cc7600 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -759,6 +759,7 @@ "tx_rejected_dust_output_send_all": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Silakan periksa saldo koin yang dipilih di bawah kontrol koin.", "tx_rejected_vout_negative": "Tidak cukup saldo untuk membayar biaya transaksi ini. Silakan periksa saldo koin di bawah kendali koin.", "tx_wrong_balance_exception": "Anda tidak memiliki cukup ${currency} untuk mengirim jumlah ini.", + "tx_wrong_balance_with_amount_exception": "Anda tidak memiliki cukup ${currency} untuk mengirim jumlah total ${amount}", "tx_zero_fee_exception": "Tidak dapat mengirim transaksi dengan biaya 0. Coba tingkatkan tarif atau periksa koneksi Anda untuk perkiraan terbaru.", "unavailable_balance": "Saldo tidak tersedia", "unavailable_balance_description": "Saldo Tidak Tersedia: Total ini termasuk dana yang terkunci dalam transaksi yang tertunda dan dana yang telah Anda bekukan secara aktif di pengaturan kontrol koin Anda. Saldo yang terkunci akan tersedia setelah transaksi masing-masing selesai, sedangkan saldo yang dibekukan tetap tidak dapat diakses untuk transaksi sampai Anda memutuskan untuk mencairkannya.", @@ -840,4 +841,4 @@ "you_will_get": "Konversi ke", "you_will_send": "Konversi dari", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 0dfd3ef47b..bd8764cae8 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -758,6 +758,7 @@ "tx_rejected_dust_output_send_all": "Transazione respinta dalle regole di rete, bassa quantità di output (polvere). Si prega di controllare il saldo delle monete selezionate sotto controllo delle monete.", "tx_rejected_vout_negative": "Non abbastanza saldo per pagare le commissioni di questa transazione. Si prega di controllare il saldo delle monete sotto controllo delle monete.", "tx_wrong_balance_exception": "Non hai abbastanza ${currency} per inviare questo importo.", + "tx_wrong_balance_with_amount_exception": "Non hai abbastanza ${currency} per inviare la quantità totale di ${amount}", "tx_zero_fee_exception": "Impossibile inviare transazioni con 0 tassa. Prova ad aumentare la tariffa o controlla la connessione per le ultime stime.", "unavailable_balance": "Saldo non disponibile", "unavailable_balance_description": "Saldo non disponibile: questo totale include i fondi bloccati nelle transazioni in sospeso e quelli che hai congelato attivamente nelle impostazioni di controllo delle monete. I saldi bloccati diventeranno disponibili una volta completate le rispettive transazioni, mentre i saldi congelati rimarranno inaccessibili per le transazioni finché non deciderai di sbloccarli.", @@ -840,4 +841,4 @@ "you_will_get": "Converti a", "you_will_send": "Conveti da", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 4902e56cb3..5c0c0b2322 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -757,6 +757,7 @@ "tx_rejected_dust_output_send_all": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。コイン管理下で選択されたコインのバランスを確認してください。", "tx_rejected_vout_negative": "この取引の料金に支払うのに十分な残高はありません。コイン制御下のコインのバランスを確認してください。", "tx_wrong_balance_exception": "この金額を送信するのに十分な${currency}はありません。", + "tx_wrong_balance_with_amount_exception": "あなたは十分なものを持っていませ${currency} ${amount}", "tx_zero_fee_exception": "0料金でトランザクションを送信できません。レートを上げて、最新の見積もりについて接続を確認してみてください。", "unavailable_balance": "利用できない残高", "unavailable_balance_description": "利用不可能な残高: この合計には、保留中のトランザクションにロックされている資金と、コイン管理設定でアクティブに凍結した資金が含まれます。ロックされた残高は、それぞれの取引が完了すると利用可能になりますが、凍結された残高は、凍結を解除するまで取引にアクセスできません。", @@ -838,4 +839,4 @@ "you_will_get": "に変換", "you_will_send": "から変換", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index cbf4b5e89c..d02639ebb4 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -757,6 +757,7 @@ "tx_rejected_dust_output_send_all": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 동전 제어에서 선택한 동전의 균형을 확인하십시오.", "tx_rejected_vout_negative": "이 거래 수수료를 지불하기에 잔액이 충분하지 않습니다. 동전 통제하에 동전의 균형을 확인하십시오.", "tx_wrong_balance_exception": "이 금액을 보내기에 충분한 ${currency}가 충분하지 않습니다.", + "tx_wrong_balance_with_amount_exception": "충분하지 않습니다 ${currency} 총 ${amount} 총 금액을 보내십시오.", "tx_zero_fee_exception": "0 수수료로 거래를 보낼 수 없습니다. 최신 견적에 대해서는 속도를 높이거나 연결을 확인하십시오.", "unavailable_balance": "사용할 수 없는 잔액", "unavailable_balance_description": "사용할 수 없는 잔액: 이 총계에는 보류 중인 거래에 잠겨 있는 자금과 코인 관리 설정에서 적극적으로 동결된 자금이 포함됩니다. 잠긴 잔액은 해당 거래가 완료되면 사용할 수 있게 되며, 동결된 잔액은 동결을 해제하기 전까지 거래에 액세스할 수 없습니다.", @@ -839,4 +840,4 @@ "you_will_send": "다음에서 변환", "YY": "YY", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index a3e570ead3..3b438e707b 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ရွေးချယ်ထားသောဒင်္ဂါးများ၏လက်ကျန်ငွေကိုစစ်ဆေးပါ။", "tx_rejected_vout_negative": "ဒီငွေပေးငွေယူရဲ့အခကြေးငွေအတွက်ပေးဆောင်ဖို့လုံလောက်တဲ့ဟန်ချက်မလုံလောက်။ ဒင်္ဂါးပြား၏လက်ကျန်ငွေလက်ကျန်ငွေကိုစစ်ဆေးပါ။", "tx_wrong_balance_exception": "ဤငွေပမာဏကိုပေးပို့ရန်သင့်တွင် ${currency} မရှိပါ။", +"tx_wrong_balance_with_amount_exception": "သင့်တွင်လုံလောက်မှုမရှိပါ${currency} ${amount}", "tx_zero_fee_exception": "0 ကြေးနှင့်အတူငွေပေးငွေယူပေးပို့လို့မရပါဘူး။ နှုန်းကိုတိုးမြှင့်ခြင်းသို့မဟုတ်နောက်ဆုံးခန့်မှန်းချက်များအတွက်သင်၏ connection ကိုစစ်ဆေးပါ။", "unavailable_balance": "လက်ကျန်ငွေ မရရှိနိုင်ပါ။", "unavailable_balance_description": "မရရှိနိုင်သော လက်ကျန်ငွေ- ဤစုစုပေါင်းတွင် ဆိုင်းငံ့ထားသော ငွေပေးငွေယူများတွင် သော့ခတ်ထားသော ငွေကြေးများနှင့် သင်၏ coin ထိန်းချုပ်မှုဆက်တင်များတွင် သင် တက်ကြွစွာ အေးခဲထားသော ငွေများ ပါဝင်သည်။ သော့ခတ်ထားသော လက်ကျန်ငွေများကို ၎င်းတို့၏ သက်ဆိုင်ရာ ငွေပေးငွေယူများ ပြီးမြောက်သည်နှင့် တပြိုင်နက် ရရှိနိုင်မည်ဖြစ်ပြီး၊ အေးခဲထားသော လက်ကျန်များကို ၎င်းတို့အား ပြန်ဖြုတ်ရန် သင်ဆုံးဖြတ်သည်အထိ ငွေပေးငွေယူများအတွက် ဆက်လက်၍မရနိုင်ပါ။", @@ -837,4 +838,4 @@ "you_will_get": "သို့ပြောင်းပါ။", "you_will_send": "မှပြောင်းပါ။", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index f017959642..6e8714554d 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Controleer het saldo van munten die zijn geselecteerd onder muntcontrole.", "tx_rejected_vout_negative": "Niet genoeg saldo om te betalen voor de kosten van deze transactie. Controleer het saldo van munten onder muntcontrole.", "tx_wrong_balance_exception": "Je hebt niet genoeg ${currency} om dit bedrag te verzenden.", + "tx_wrong_balance_with_amount_exception": "Je hebt niet genoeg ${currency} om de totale hoeveelheid ${amount} te verzenden", "tx_zero_fee_exception": "Kan geen transactie verzenden met 0 kosten. Probeer het tarief te verhogen of uw verbinding te controleren op de laatste schattingen.", "unavailable_balance": "Onbeschikbaar saldo", "unavailable_balance_description": "Niet-beschikbaar saldo: Dit totaal omvat het geld dat is vergrendeld in lopende transacties en het geld dat u actief hebt bevroren in uw muntcontrole-instellingen. Vergrendelde saldi komen beschikbaar zodra de betreffende transacties zijn voltooid, terwijl bevroren saldi ontoegankelijk blijven voor transacties totdat u besluit ze weer vrij te geven.", @@ -838,4 +839,4 @@ "you_will_get": "Converteren naar", "you_will_send": "Converteren van", "yy": "JJ" -} \ No newline at end of file +} diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 07c8fc7924..f3805bf5e9 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "Transakcja odrzucona według reguł sieciowych, niskiej ilości wyjściowej (pyłu). Sprawdź saldo monet wybranych pod kontrolą monet.", "tx_rejected_vout_negative": "Za mało salda, aby zapłacić za opłaty tej transakcji. Sprawdź saldo monet pod kontrolą monet.", "tx_wrong_balance_exception": "Nie masz wystarczającej ilości ${currency}, aby wysłać tę kwotę.", + "tx_wrong_balance_with_amount_exception": "Nie masz wystarczająco dużo ${currency} aby wysłać całkowitą ilość ${amount}", "tx_zero_fee_exception": "Nie można wysłać transakcji z 0 opłatą. Spróbuj zwiększyć stawkę lub sprawdzić połączenie w poszukiwaniu najnowszych szacunków.", "unavailable_balance": "Niedostępne saldo", "unavailable_balance_description": "Niedostępne saldo: Suma ta obejmuje środki zablokowane w transakcjach oczekujących oraz te, które aktywnie zamroziłeś w ustawieniach kontroli monet. Zablokowane salda staną się dostępne po zakończeniu odpowiednich transakcji, natomiast zamrożone salda pozostaną niedostępne dla transakcji, dopóki nie zdecydujesz się ich odblokować.", @@ -837,4 +838,4 @@ "you_will_get": "Konwertuj na", "you_will_send": "Konwertuj z", "yy": "RR" -} \ No newline at end of file +} diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index f010e6c2b9..d31dfd91b9 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -758,6 +758,7 @@ "tx_rejected_dust_output_send_all": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, verifique o saldo de moedas selecionadas sob controle de moedas.", "tx_rejected_vout_negative": "Não há saldo suficiente para pagar as taxas desta transação. Por favor, verifique o saldo de moedas sob controle de moedas.", "tx_wrong_balance_exception": "Você não tem o suficiente ${currency} para enviar esse valor.", + "tx_wrong_balance_with_amount_exception": "Você não tem o suficiente ${currency} para enviar o valor total de ${amount}", "tx_zero_fee_exception": "Não pode enviar transação com taxa 0. Tente aumentar a taxa ou verificar sua conexão para obter as estimativas mais recentes.", "unavailable_balance": "Saldo indisponível", "unavailable_balance_description": "Saldo Indisponível: Este total inclui fundos bloqueados em transações pendentes e aqueles que você congelou ativamente nas configurações de controle de moedas. Os saldos bloqueados ficarão disponíveis assim que suas respectivas transações forem concluídas, enquanto os saldos congelados permanecerão inacessíveis para transações até que você decida descongelá-los.", @@ -840,4 +841,4 @@ "you_will_get": "Converter para", "you_will_send": "Converter de", "yy": "aa" -} \ No newline at end of file +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 52d2cfe9e4..5c48d926f6 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -757,6 +757,7 @@ "tx_rejected_dust_output_send_all": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, проверьте баланс монет, выбранных под контролем монет.", "tx_rejected_vout_negative": "Недостаточно баланс, чтобы оплатить плату этой транзакции. Пожалуйста, проверьте баланс монет под контролем монет.", "tx_wrong_balance_exception": "У вас не хватает ${currency}, чтобы отправить эту сумму.", + "tx_wrong_balance_with_amount_exception": "У вас недостаточно ${currency}, чтобы отправить общее количество ${amount}", "tx_zero_fee_exception": "Не может отправить транзакцию с платой 0. Попробуйте увеличить ставку или проверить соединение на наличие последних оценок.", "unavailable_balance": "Недоступный баланс", "unavailable_balance_description": "Недоступный баланс: в эту сумму входят средства, заблокированные в ожидающих транзакциях, и средства, которые вы активно заморозили в настройках управления монетами. Заблокированные балансы станут доступны после завершения соответствующих транзакций, а замороженные балансы останутся недоступными для транзакций, пока вы не решите их разморозить.", @@ -838,4 +839,4 @@ "you_will_get": "Конвертировать в", "you_will_send": "Конвертировать из", "yy": "ГГ" -} \ No newline at end of file +} diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 25ca76830b..2e8cbe2b9f 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดตรวจสอบยอดคงเหลือของเหรียญที่เลือกภายใต้การควบคุมเหรียญ", "tx_rejected_vout_negative": "ยอดคงเหลือไม่เพียงพอที่จะจ่ายสำหรับค่าธรรมเนียมการทำธุรกรรมนี้ โปรดตรวจสอบยอดคงเหลือของเหรียญภายใต้การควบคุมเหรียญ", "tx_wrong_balance_exception": "คุณมีไม่เพียงพอ ${currency} ในการส่งจำนวนนี้", + "tx_wrong_balance_with_amount_exception": "คุณมีไม่เพียงพอ ${currency} เพื่อส่งจำนวนทั้งหมดของ ${amount}", "tx_zero_fee_exception": "ไม่สามารถส่งธุรกรรมด้วยค่าธรรมเนียม 0 ลองเพิ่มอัตราหรือตรวจสอบการเชื่อมต่อของคุณสำหรับการประมาณการล่าสุด", "unavailable_balance": "ยอดคงเหลือไม่พร้อมใช้งาน", "unavailable_balance_description": "ยอดคงเหลือที่ไม่พร้อมใช้งาน: ยอดรวมนี้รวมถึงเงินทุนที่ถูกล็อคในการทำธุรกรรมที่รอดำเนินการและที่คุณได้แช่แข็งไว้ในการตั้งค่าการควบคุมเหรียญของคุณ ยอดคงเหลือที่ถูกล็อคจะพร้อมใช้งานเมื่อธุรกรรมที่เกี่ยวข้องเสร็จสมบูรณ์ ในขณะที่ยอดคงเหลือที่แช่แข็งจะไม่สามารถเข้าถึงได้สำหรับธุรกรรมจนกว่าคุณจะตัดสินใจยกเลิกการแช่แข็ง", @@ -837,4 +838,4 @@ "you_will_get": "แปลงเป็น", "you_will_send": "แปลงจาก", "yy": "ปี" -} \ No newline at end of file +} diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 53eac9da83..25f450d0a1 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (alikabok). Mangyaring suriin ang balanse ng mga barya na napili sa ilalim ng kontrol ng barya.", "tx_rejected_vout_negative": "Hindi sapat na balanse upang magbayad para sa mga bayarin ng transaksyon na ito. Mangyaring suriin ang balanse ng mga barya sa ilalim ng kontrol ng barya.", "tx_wrong_balance_exception": "Wala kang sapat na ${currency} upang maipadala ang halagang ito.", + "tx_wrong_balance_with_amount_exception": "Wala kang sapat ${currency} Upang ipadala ang kabuuang halaga ng ${amount}", "tx_zero_fee_exception": "Hindi maaaring magpadala ng transaksyon na may 0 bayad. Subukan ang pagtaas ng rate o pagsuri sa iyong koneksyon para sa pinakabagong mga pagtatantya.", "unavailable_balance": "Hindi available na balanse", "unavailable_balance_description": "Hindi Available na Balanse: Kasama sa kabuuang ito ang mga pondong naka-lock sa mga nakabinbing transaksyon at ang mga aktibong na-freeze mo sa iyong mga setting ng kontrol ng coin. Magiging available ang mga naka-lock na balanse kapag nakumpleto na ang kani-kanilang mga transaksyon, habang ang mga nakapirming balanse ay nananatiling hindi naa-access para sa mga transaksyon hanggang sa magpasya kang i-unfreeze ang mga ito.", @@ -837,4 +838,4 @@ "you_will_get": "Mag -convert sa", "you_will_send": "I -convert mula sa", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index c1c36dc037..a024cbcd25 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen madeni para kontrolü altında seçilen madeni para dengesini kontrol edin.", "tx_rejected_vout_negative": "Bu işlem ücretleri için ödeme yapmak için yeterli bakiye yok. Lütfen madeni para kontrolü altındaki madeni para dengesini kontrol edin.", "tx_wrong_balance_exception": "Bu miktarı göndermek için yeterli ${currency} yok.", + "tx_wrong_balance_with_amount_exception": "Yeterli değilsiniz ${currency} toplam ${amount} miktarını göndermek için", "tx_zero_fee_exception": "0 ücret ile işlem gönderilemez. En son tahminler için oranı artırmayı veya bağlantınızı kontrol etmeyi deneyin.", "unavailable_balance": "Kullanılamayan bakiye", "unavailable_balance_description": "Kullanılamayan Bakiye: Bu toplam, bekleyen işlemlerde kilitlenen fonları ve jeton kontrol ayarlarınızda aktif olarak dondurduğunuz fonları içerir. Kilitli bakiyeler, ilgili işlemleri tamamlandıktan sonra kullanılabilir hale gelir; dondurulmuş bakiyeler ise siz onları dondurmaya karar verene kadar işlemler için erişilemez durumda kalır.", @@ -837,4 +838,4 @@ "you_will_get": "Biçimine dönüştür:", "you_will_send": "Biçiminden dönüştür:", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 8d05c27a38..669ba1e82a 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -757,6 +757,7 @@ "tx_rejected_dust_output_send_all": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, перевірте баланс монет, вибраних під контролем монет.", "tx_rejected_vout_negative": "Недостатньо балансу, щоб оплатити плату за цю транзакцію. Будь ласка, перевірте баланс монет під контролем монет.", "tx_wrong_balance_exception": "У вас недостатньо ${currency}, щоб надіслати цю суму.", + "tx_wrong_balance_with_amount_exception": "У вас недостатньо ${currency}, щоб надіслати загальну кількість ${amount}", "tx_zero_fee_exception": "Не вдається відправити транзакцію з 0 платежами. Спробуйте збільшити ставку або перевірити з'єднання на останні оцінки.", "unavailable_balance": "Недоступний баланс", "unavailable_balance_description": "Недоступний баланс: ця сума включає кошти, заблоковані в незавершених транзакціях, і ті, які ви активно заморозили в налаштуваннях контролю монет. Заблоковані баланси стануть доступними після завершення відповідних транзакцій, тоді як заморожені баланси залишаються недоступними для транзакцій, доки ви не вирішите їх розморозити.", @@ -838,4 +839,4 @@ "you_will_get": "Конвертувати в", "you_will_send": "Конвертувати з", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index c2672e224a..40e7e71dbd 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -758,6 +758,7 @@ "tx_rejected_dust_output_send_all": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم سکے کے کنٹرول میں منتخب کردہ سکے کا توازن چیک کریں۔", "tx_rejected_vout_negative": "اس لین دین کی فیسوں کی ادائیگی کے لئے کافی توازن نہیں ہے۔ براہ کرم سکے کے کنٹرول میں سکے کا توازن چیک کریں۔", "tx_wrong_balance_exception": "آپ کے پاس یہ رقم بھیجنے کے لئے کافی ${currency} نہیں ہے۔", + "tx_wrong_balance_with_amount_exception": "آپ کے پاس کافی نہیں ہے ${currency} ${amount}", "tx_zero_fee_exception": "0 فیس کے ساتھ لین دین نہیں بھیج سکتا۔ شرح کو بڑھانے یا تازہ ترین تخمینے کے ل your اپنے کنکشن کی جانچ پڑتال کرنے کی کوشش کریں۔", "unavailable_balance": "ﺲﻨﻠﯿﺑ ﺏﺎﯿﺘﺳﺩ ﺮﯿﻏ", "unavailable_balance_description": "۔ﮯﺗﺮﮐ ﮟﯿﮩﻧ ﮧﻠﺼﯿﻓ ﺎﮐ ﮯﻧﺮﮐ ﺪﻤﺠﻨﻣ ﻥﺍ ﮟﯿﮩﻧﺍ ﭖﺁ ﮧﮐ ﮏﺗ ﺐﺟ ﮟﯿﮨ ﮯﺘﮨﺭ ﯽﺋﺎﺳﺭ ﻞﺑﺎﻗﺎﻧ ﮏﺗ ﺖﻗﻭ ﺱﺍ ﮯﯿﻟ ﮯﮐ ﻦﯾﺩ ﻦﯿﻟ ﺲﻨﻠﯿﺑ ﺪﻤﺠﻨﻣ ﮧﮐ ﺐﺟ ،ﮯﮔ ﮟﯿﺋﺎﺟ ﻮﮨ ﺏﺎﯿﺘﺳﺩ ﺲﻨﻠﯿﺑ ﻞﻔﻘﻣ ﺪﻌﺑ ﮯﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﻦﯾﺩ ﻦﯿﻟ ﮧﻘﻠﻌﺘﻣ ﮯﮐ ﻥﺍ ۔ﮯﮨ ﺎﮭﮐﺭ ﺮ", @@ -839,4 +840,4 @@ "you_will_get": "میں تبدیل کریں۔", "you_will_send": "سے تبدیل کریں۔", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 39e65f82d3..4de13273d8 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -757,6 +757,7 @@ "tx_rejected_dust_output_send_all": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ ṣayẹwo dọgbadọgba ti awọn owo ti a yan labẹ iṣakoso owo.", "tx_rejected_vout_negative": "Iwontunws.funfun ti o to lati sanwo fun awọn idiyele iṣowo yii. Jọwọ ṣayẹwo iwọntunwọnsi ti awọn owo labẹ iṣakoso owo.", "tx_wrong_balance_exception": "O ko ni to ${currency} lati firanṣẹ iye yii.", + "tx_wrong_balance_with_amount_exception": "O ko ni to ${currency} Lati firanṣẹ lapapọ iye ${amount}", "tx_zero_fee_exception": "Ko le firanṣẹ idunadura pẹlu ọya 0. Gbiyanju jijẹ oṣuwọn tabi ṣayẹwo asopọ rẹ fun awọn iṣiro tuntun.", "unavailable_balance": "Iwontunwonsi ti ko si", "unavailable_balance_description": "Iwontunws.funfun ti ko si: Lapapọ yii pẹlu awọn owo ti o wa ni titiipa ni awọn iṣowo isunmọ ati awọn ti o ti didi ni itara ninu awọn eto iṣakoso owo rẹ. Awọn iwọntunwọnsi titiipa yoo wa ni kete ti awọn iṣowo oniwun wọn ba ti pari, lakoko ti awọn iwọntunwọnsi tio tutunini ko ni iraye si fun awọn iṣowo titi iwọ o fi pinnu lati mu wọn kuro.", @@ -838,4 +839,4 @@ "you_will_get": "Ṣe pàṣípààrọ̀ sí", "you_will_send": "Ṣe pàṣípààrọ̀ láti", "yy": "Ọd" -} \ No newline at end of file +} diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 400a2ed3cf..2f283532a7 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -756,6 +756,7 @@ "tx_rejected_dust_output_send_all": "交易被网络规则,低输出量(灰尘)拒绝。请检查在硬币控制下选择的硬币的余额。", "tx_rejected_vout_negative": "没有足够的余额来支付此交易费用。请检查硬币控制下的硬币余额。", "tx_wrong_balance_exception": "您没有足够的${currency}来发送此金额。", + "tx_wrong_balance_with_amount_exception": "您没有足够的${currency} ${amount}", "tx_zero_fee_exception": "无法以0费用发送交易。尝试提高速率或检查连接以获取最新估计。", "unavailable_balance": "不可用余额", "unavailable_balance_description": "不可用余额:此总额包括锁定在待处理交易中的资金以及您在硬币控制设置中主动冻结的资金。一旦各自的交易完成,锁定的余额将变得可用,而冻结的余额在您决定解冻之前仍然无法进行交易。", @@ -837,4 +838,4 @@ "you_will_get": "转换到", "you_will_send": "转换自", "yy": "YY" -} \ No newline at end of file +} From 5f787693389c1a229b7d5253a13633b6fb2814af Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Wed, 10 Apr 2024 10:52:58 +0200 Subject: [PATCH 044/242] various fixes for build issues --- android/app/src/debug/AndroidManifest.xml | 2 +- macos/Podfile.lock | 2 +- macos/Runner.xcodeproj/project.pbxproj | 27 ++++--- .../xcshareddata/xcschemes/Runner.xcscheme | 8 +-- scripts/android/build_openssl.sh | 3 +- scripts/android/build_unbound.sh | 8 +-- scripts/android/init_boost.sh | 4 +- scripts/android/inject_app_details.sh | 1 + scripts/android/install_ndk.sh | 6 +- scripts/docker/Dockerfile | 42 +++++++++-- scripts/docker/build_all.sh | 18 ++++- scripts/docker/build_boost.sh | 2 +- scripts/docker/build_haven.sh | 72 ++++++++++++++++++- scripts/docker/build_haven_all.sh | 10 ++- scripts/docker/build_iconv.sh | 1 + scripts/docker/build_monero.sh | 1 + scripts/docker/build_openssl.sh | 1 + scripts/docker/build_sodium.sh | 1 + scripts/docker/build_unbound.sh | 2 +- scripts/docker/build_zmq.sh | 1 + scripts/docker/config.sh | 1 + scripts/docker/copy_haven_deps.sh | 1 + scripts/docker/copy_monero_deps.sh | 1 + scripts/docker/docker-compose.yml | 0 scripts/docker/entrypoint.sh | 1 + scripts/docker/finish_boost.sh | 1 + scripts/docker/init_boost.sh | 6 +- scripts/docker/install_ndk.sh | 1 + 28 files changed, 181 insertions(+), 43 deletions(-) mode change 100644 => 100755 scripts/docker/Dockerfile mode change 100644 => 100755 scripts/docker/build_all.sh mode change 100644 => 100755 scripts/docker/build_boost.sh mode change 100644 => 100755 scripts/docker/build_haven.sh mode change 100644 => 100755 scripts/docker/build_haven_all.sh mode change 100644 => 100755 scripts/docker/build_iconv.sh mode change 100644 => 100755 scripts/docker/build_monero.sh mode change 100644 => 100755 scripts/docker/build_openssl.sh mode change 100644 => 100755 scripts/docker/build_sodium.sh mode change 100644 => 100755 scripts/docker/build_unbound.sh mode change 100644 => 100755 scripts/docker/build_zmq.sh mode change 100644 => 100755 scripts/docker/config.sh mode change 100644 => 100755 scripts/docker/copy_haven_deps.sh mode change 100644 => 100755 scripts/docker/copy_monero_deps.sh mode change 100644 => 100755 scripts/docker/docker-compose.yml mode change 100644 => 100755 scripts/docker/entrypoint.sh mode change 100644 => 100755 scripts/docker/finish_boost.sh mode change 100644 => 100755 scripts/docker/init_boost.sh mode change 100644 => 100755 scripts/docker/install_ndk.sh diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index dc767a55dc..dfd0d315a6 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.monero.app"> diff --git a/macos/Podfile.lock b/macos/Podfile.lock index b82513de23..f83e18c409 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -106,7 +106,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 - cw_monero: ec03de55a19c4a2b174ea687e0f4202edc716fa4 + cw_monero: f8b7f104508efba2591548e76b5c058d05cba3f0 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 911fa9fcc0..7bf0cd5e8f 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -59,7 +59,7 @@ 2A820A13B0719E9E0CD6686F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* Cake Wallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Cake Wallet.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* Monero.com.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Monero.com.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -116,7 +116,7 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* Cake Wallet.app */, + 33CC10ED2044A3C60003C045 /* Monero.com.app */, ); name = Products; sourceTree = ""; @@ -197,7 +197,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* Cake Wallet.app */; + productReference = 33CC10ED2044A3C60003C045 /* Monero.com.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -421,15 +421,14 @@ isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { - ARCHS = "$(ARCHS_STANDARD)"; + ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 10; - DEVELOPMENT_TEAM = 32J6BB6VUS; + DEVELOPMENT_TEAM = X7J2Z37ZUL; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Cake Wallet"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; @@ -555,15 +554,14 @@ isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { - ARCHS = "$(ARCHS_STANDARD)"; + ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 10; - DEVELOPMENT_TEAM = 32J6BB6VUS; + DEVELOPMENT_TEAM = X7J2Z37ZUL; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Cake Wallet"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; @@ -583,15 +581,14 @@ isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { - ARCHS = "$(ARCHS_STANDARD)"; + ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 10; - DEVELOPMENT_TEAM = 32J6BB6VUS; + DEVELOPMENT_TEAM = X7J2Z37ZUL; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Cake Wallet"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8536e9a814..1f89f68354 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -54,7 +54,7 @@ @@ -71,7 +71,7 @@ diff --git a/scripts/android/build_openssl.sh b/scripts/android/build_openssl.sh index aa668e6bf9..b67479464f 100755 --- a/scripts/android/build_openssl.sh +++ b/scripts/android/build_openssl.sh @@ -24,9 +24,8 @@ echo $OPENSSL_SHA256 $OPENSSL_FILE_PATH | sha256sum -c - || exit 1 for arch in "aarch" "aarch64" "i686" "x86_64" do PREFIX=$WORKDIR/prefix_${arch} -TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64 +TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/darwin-x86_64 PATH="${TOOLCHAIN}/bin:${ORIGINAL_PATH}" - case $arch in "aarch") X_ARCH="android-arm";; "aarch64") X_ARCH="android-arm64";; diff --git a/scripts/android/build_unbound.sh b/scripts/android/build_unbound.sh index 8786b0f2b7..fe3549f07c 100755 --- a/scripts/android/build_unbound.sh +++ b/scripts/android/build_unbound.sh @@ -9,12 +9,12 @@ EXPAT_SRC_DIR=$WORKDIR/libexpat for arch in "aarch" "aarch64" "i686" "x86_64" do PREFIX=$WORKDIR/prefix_${arch} -TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64 +TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/darwin-x86_64 PATH="${TOOLCHAIN_BASE_DIR}_${arch}/bin:${ORIGINAL_PATH}" cd $WORKDIR rm -rf $EXPAT_SRC_DIR -git clone https://github.com/libexpat/libexpat.git -b ${EXPAT_VERSION} ${EXPAT_SRC_DIR} +git clone https://github.com/libexpat/libexpat.git --depth=1 -b ${EXPAT_VERSION} ${EXPAT_SRC_DIR} cd $EXPAT_SRC_DIR test `git rev-parse HEAD` = ${EXPAT_HASH} || exit 1 cd $EXPAT_SRC_DIR/expat @@ -38,7 +38,7 @@ UNBOUND_SRC_DIR=$WORKDIR/unbound-1.16.2 for arch in "aarch" "aarch64" "i686" "x86_64" do PREFIX=$WORKDIR/prefix_${arch} -TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64 +TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/darwin-x86_64 case $arch in "aarch") TOOLCHAIN_BIN_PATH=${TOOLCHAIN_BASE_DIR}_${arch}/arm-linux-androideabi/bin;; @@ -49,7 +49,7 @@ PATH="${TOOLCHAIN_BIN_PATH}:${TOOLCHAIN_BASE_DIR}_${arch}/bin:${ORIGINAL_PATH}" echo $PATH cd $WORKDIR rm -rf $UNBOUND_SRC_DIR -git clone https://github.com/NLnetLabs/unbound.git -b ${UNBOUND_VERSION} ${UNBOUND_SRC_DIR} +git clone https://github.com/NLnetLabs/unbound.git --depth=1 -b ${UNBOUND_VERSION} ${UNBOUND_SRC_DIR} cd $UNBOUND_SRC_DIR test `git rev-parse HEAD` = ${UNBOUND_HASH} || exit 1 diff --git a/scripts/android/init_boost.sh b/scripts/android/init_boost.sh index 13120c910c..7579acdd7c 100755 --- a/scripts/android/init_boost.sh +++ b/scripts/android/init_boost.sh @@ -17,6 +17,6 @@ echo $BOOST_SHA256 $BOOST_FILE_PATH | sha256sum -c - || exit 1 cd $WORKDIR rm -rf $BOOST_SRC_DIR rm -rf $PREFIX/include/boost -tar -xvf $BOOST_FILE_PATH -C $WORKDIR +tar -xf $BOOST_FILE_PATH -C $WORKDIR cd $BOOST_SRC_DIR -./bootstrap.sh --prefix=${PREFIX} +./bootstrap.sh --prefix=${PREFIX} --with-toolset=gcc diff --git a/scripts/android/inject_app_details.sh b/scripts/android/inject_app_details.sh index 27b7efa391..3e6e2915d5 100755 --- a/scripts/android/inject_app_details.sh +++ b/scripts/android/inject_app_details.sh @@ -6,6 +6,7 @@ if [ -z "$APP_ANDROID_TYPE" ]; then fi cd ../.. +set -x sed -i "0,/version:/{s/version:.*/version: ${APP_ANDROID_VERSION}+${APP_ANDROID_BUILD_NUMBER}/}" ./pubspec.yaml sed -i "0,/version:/{s/__APP_PACKAGE__/${APP_ANDROID_PACKAGE}/}" ./android/app/src/main/AndroidManifest.xml sed -i "0,/__APP_SCHEME__/s/__APP_SCHEME__/${APP_ANDROID_SCHEME}/" ./android/app/src/main/AndroidManifest.xml diff --git a/scripts/android/install_ndk.sh b/scripts/android/install_ndk.sh index bee72abadd..4ec794371b 100755 --- a/scripts/android/install_ndk.sh +++ b/scripts/android/install_ndk.sh @@ -8,9 +8,9 @@ TOOLCHAIN_x86_DIR=${TOOLCHAIN_DIR}_i686 TOOLCHAIN_x86_64_DIR=${TOOLCHAIN_DIR}_x86_64 ANDROID_NDK_SHA256="3f541adbd0330a9205ba12697f6d04ec90752c53d6b622101a2a8a856e816589" -curl https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip -o ${ANDROID_NDK_ZIP} -echo $ANDROID_NDK_SHA256 $ANDROID_NDK_ZIP | sha256sum -c || exit 1 -unzip $ANDROID_NDK_ZIP -d $WORKDIR +# curl https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip -o ${ANDROID_NDK_ZIP} +# echo $ANDROID_NDK_SHA256 $ANDROID_NDK_ZIP | sha256sum -c || exit 1 +# unzip $ANDROID_NDK_ZIP -d $WORKDIR ${ANDROID_NDK_ROOT}/build/tools/make_standalone_toolchain.py --arch arm64 --api $API --install-dir ${TOOLCHAIN_A64_DIR} --stl=libc++ ${ANDROID_NDK_ROOT}/build/tools/make_standalone_toolchain.py --arch arm --api $API --install-dir ${TOOLCHAIN_A32_DIR} --stl=libc++ diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile old mode 100644 new mode 100755 index eef09a323c..1bcb0464ec --- a/scripts/docker/Dockerfile +++ b/scripts/docker/Dockerfile @@ -4,23 +4,57 @@ LABEL authors="konsti" ENV MONERO_BRANCH=release-v0.18.2.2-android RUN apt-get update && \ echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections && \ - apt-get install -y dialog apt-utils curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + apt-get install -y dialog apt-utils curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang bison RUN mkdir /opt/android/ -COPY . /opt/android/cakewallet/ - WORKDIR /opt/android/cakewallet/ - +# build_all.sh +# build_boost.sh +# build_haven.sh +# build_haven_all.sh +# build_iconv.sh +# build_monero.sh +# build_openssl.sh +# build_sodium.sh +# build_unbound.sh +# build_zmq.sh +# config.sh +# copy_haven_deps.sh +# copy_monero_deps.sh +# docker-compose.yml +# entrypoint.sh +# finish_boost.sh +# init_boost.sh +# install_ndk.sh + +COPY config.sh /opt/android/cakewallet/ +COPY install_ndk.sh /opt/android/cakewallet/ RUN ./install_ndk.sh +COPY build_iconv.sh /opt/android/cakewallet/ RUN ./build_iconv.sh + +COPY build_boost.sh /opt/android/cakewallet/ +COPY init_boost.sh /opt/android/cakewallet/ +COPY finish_boost.sh /opt/android/cakewallet/ RUN ./build_boost.sh + +COPY build_openssl.sh /opt/android/cakewallet/ RUN ./build_openssl.sh + +COPY build_sodium.sh /opt/android/cakewallet/ RUN ./build_sodium.sh + +COPY build_unbound.sh /opt/android/cakewallet/ RUN ./build_unbound.sh + +COPY build_zmq.sh /opt/android/cakewallet/ RUN ./build_zmq.sh +COPY entrypoint.sh /opt/android/cakewallet/ +COPY build_monero.sh /opt/android/cakewallet/ +COPY copy_monero_deps.sh /opt/android/cakewallet/ ENTRYPOINT ["./entrypoint.sh"] diff --git a/scripts/docker/build_all.sh b/scripts/docker/build_all.sh old mode 100644 new mode 100755 index 0acb7fcde7..a4163c3f43 --- a/scripts/docker/build_all.sh +++ b/scripts/docker/build_all.sh @@ -1 +1,17 @@ -#!/bin/sh if [ -z "$APP_ANDROID_TYPE" ]; then echo "Please set APP_ANDROID_TYPE" exit 1 fi DIR=$(dirname "$0") case $APP_ANDROID_TYPE in "monero.com") $DIR/build_monero_all.sh ;; "cakewallet") $DIR/build_monero_all.sh $DIR/build_haven.sh ;; "haven") $DIR/build_haven_all.sh ;; esac \ No newline at end of file +#!/bin/sh + +set -x -e + +if [ -z "$APP_ANDROID_TYPE" ]; then + echo "Please set APP_ANDROID_TYPE" + exit 1 +fi + +DIR=$(dirname "$0") + +case $APP_ANDROID_TYPE in + "monero.com") $DIR/build_monero_all.sh ;; + "cakewallet") $DIR/build_monero_all.sh + $DIR/build_haven.sh ;; + "haven") $DIR/build_haven_all.sh ;; +esac diff --git a/scripts/docker/build_boost.sh b/scripts/docker/build_boost.sh old mode 100644 new mode 100755 index 2c98afab53..97333bbee7 --- a/scripts/docker/build_boost.sh +++ b/scripts/docker/build_boost.sh @@ -1,5 +1,5 @@ #!/bin/bash - +set -x -e . ./config.sh BOOST_SRC_DIR=$WORKDIR/boost_1_72_0 BOOST_FILENAME=boost_1_72_0.tar.bz2 diff --git a/scripts/docker/build_haven.sh b/scripts/docker/build_haven.sh old mode 100644 new mode 100755 index 7927c51027..1dc4a6cfd2 --- a/scripts/docker/build_haven.sh +++ b/scripts/docker/build_haven.sh @@ -1 +1,71 @@ -#!/bin/sh . ./config.sh HAVEN_VERSION=tags/v3.0.7 HAVEN_SRC_DIR=${WORKDIR}/haven git clone https://github.com/haven-protocol-org/haven-main.git ${HAVEN_SRC_DIR} git checkout ${HAVEN_VERSION} cd $HAVEN_SRC_DIR git submodule init git submodule update for arch in "aarch" "aarch64" "i686" "x86_64" do FLAGS="" PREFIX=${WORKDIR}/prefix_${arch} DEST_LIB_DIR=${PREFIX}/lib/haven DEST_INCLUDE_DIR=${PREFIX}/include/haven export CMAKE_INCLUDE_PATH="${PREFIX}/include" export CMAKE_LIBRARY_PATH="${PREFIX}/lib" ANDROID_STANDALONE_TOOLCHAIN_PATH="${TOOLCHAIN_BASE_DIR}_${arch}" PATH="${ANDROID_STANDALONE_TOOLCHAIN_PATH}/bin:${ORIGINAL_PATH}" mkdir -p $DEST_LIB_DIR mkdir -p $DEST_INCLUDE_DIR case $arch in "aarch" ) CLANG=arm-linux-androideabi-clang CXXLANG=arm-linux-androideabi-clang++ BUILD_64=OFF TAG="android-armv7" ARCH="armv7-a" ARCH_ABI="armeabi-v7a" FLAGS="-D CMAKE_ANDROID_ARM_MODE=ON -D NO_AES=true";; "aarch64" ) CLANG=aarch64-linux-androideabi-clang CXXLANG=aarch64-linux-androideabi-clang++ BUILD_64=ON TAG="android-armv8" ARCH="armv8-a" ARCH_ABI="arm64-v8a";; "i686" ) CLANG=i686-linux-androideabi-clang CXXLANG=i686-linux-androideabi-clang++ BUILD_64=OFF TAG="android-x86" ARCH="i686" ARCH_ABI="x86";; "x86_64" ) CLANG=x86_64-linux-androideabi-clang CXXLANG=x86_64-linux-androideabi-clang++ BUILD_64=ON TAG="android-x86_64" ARCH="x86-64" ARCH_ABI="x86_64";; esac cd $HAVEN_SRC_DIR rm -rf ./build/release mkdir -p ./build/release cd ./build/release CC=${CLANG} CXX=${CXXLANG} cmake -D USE_DEVICE_TREZOR=OFF -D BUILD_GUI_DEPS=1 -D BUILD_TESTS=OFF -D ARCH=${ARCH} -D STATIC=ON -D BUILD_64=${BUILD_64} -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON -D BUILD_TAG=${TAG} -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI=${ARCH_ABI} $FLAGS ../.. make wallet_api -j$THREADS find . -path ./lib -prune -o -name '*.a' -exec cp '{}' lib \; cp -r ./lib/* $DEST_LIB_DIR cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR done \ No newline at end of file +#!/bin/sh +set -x -e + +. ./config.sh +HAVEN_VERSION=tags/v3.0.7 +HAVEN_SRC_DIR=${WORKDIR}/haven + +git clone https://github.com/haven-protocol-org/haven-main.git ${HAVEN_SRC_DIR} +git checkout ${HAVEN_VERSION} +cd $HAVEN_SRC_DIR +git submodule init +git submodule update + +for arch in "aarch" "aarch64" "i686" "x86_64" +do +FLAGS="" +PREFIX=${WORKDIR}/prefix_${arch} +DEST_LIB_DIR=${PREFIX}/lib/haven +DEST_INCLUDE_DIR=${PREFIX}/include/haven +export CMAKE_INCLUDE_PATH="${PREFIX}/include" +export CMAKE_LIBRARY_PATH="${PREFIX}/lib" +ANDROID_STANDALONE_TOOLCHAIN_PATH="${TOOLCHAIN_BASE_DIR}_${arch}" +PATH="${ANDROID_STANDALONE_TOOLCHAIN_PATH}/bin:${ORIGINAL_PATH}" + +mkdir -p $DEST_LIB_DIR +mkdir -p $DEST_INCLUDE_DIR + +case $arch in + "aarch" ) + CLANG=arm-linux-androideabi-clang + CXXLANG=arm-linux-androideabi-clang++ + BUILD_64=OFF + TAG="android-armv7" + ARCH="armv7-a" + ARCH_ABI="armeabi-v7a" + FLAGS="-D CMAKE_ANDROID_ARM_MODE=ON -D NO_AES=true";; + "aarch64" ) + CLANG=aarch64-linux-androideabi-clang + CXXLANG=aarch64-linux-androideabi-clang++ + BUILD_64=ON + TAG="android-armv8" + ARCH="armv8-a" + ARCH_ABI="arm64-v8a";; + "i686" ) + CLANG=i686-linux-androideabi-clang + CXXLANG=i686-linux-androideabi-clang++ + BUILD_64=OFF + TAG="android-x86" + ARCH="i686" + ARCH_ABI="x86";; + "x86_64" ) + CLANG=x86_64-linux-androideabi-clang + CXXLANG=x86_64-linux-androideabi-clang++ + BUILD_64=ON + TAG="android-x86_64" + ARCH="x86-64" + ARCH_ABI="x86_64";; +esac + +cd $HAVEN_SRC_DIR +rm -rf ./build/release +mkdir -p ./build/release +cd ./build/release +CC=${CLANG} CXX=${CXXLANG} cmake -D USE_DEVICE_TREZOR=OFF -D BUILD_GUI_DEPS=1 -D BUILD_TESTS=OFF -D ARCH=${ARCH} -D STATIC=ON -D BUILD_64=${BUILD_64} -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON -D BUILD_TAG=${TAG} -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI=${ARCH_ABI} $FLAGS ../.. + +make wallet_api -j$THREADS +find . -path ./lib -prune -o -name '*.a' -exec cp '{}' lib \; + +cp -r ./lib/* $DEST_LIB_DIR +cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR +done diff --git a/scripts/docker/build_haven_all.sh b/scripts/docker/build_haven_all.sh old mode 100644 new mode 100755 index 4b33ad0772..ce8eb3f0e1 --- a/scripts/docker/build_haven_all.sh +++ b/scripts/docker/build_haven_all.sh @@ -1 +1,9 @@ -#!/bin/bash ./build_iconv.sh ./build_boost.sh ./build_openssl.sh ./build_sodium.sh ./build_zmq.sh ./build_haven.sh \ No newline at end of file +#!/bin/bash +set -x -e + +./build_iconv.sh +./build_boost.sh +./build_openssl.sh +./build_sodium.sh +./build_zmq.sh +./build_haven.sh diff --git a/scripts/docker/build_iconv.sh b/scripts/docker/build_iconv.sh old mode 100644 new mode 100755 index 9edac26b39..e55686fecc --- a/scripts/docker/build_iconv.sh +++ b/scripts/docker/build_iconv.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e . ./config.sh export ICONV_FILENAME=libiconv-1.16.tar.gz diff --git a/scripts/docker/build_monero.sh b/scripts/docker/build_monero.sh old mode 100644 new mode 100755 index d663f5288d..04162f0f85 --- a/scripts/docker/build_monero.sh +++ b/scripts/docker/build_monero.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e . ./config.sh diff --git a/scripts/docker/build_openssl.sh b/scripts/docker/build_openssl.sh old mode 100644 new mode 100755 index 685d0a1be7..233e64a7c7 --- a/scripts/docker/build_openssl.sh +++ b/scripts/docker/build_openssl.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e set -e diff --git a/scripts/docker/build_sodium.sh b/scripts/docker/build_sodium.sh old mode 100644 new mode 100755 index a934d641b7..c911814d9f --- a/scripts/docker/build_sodium.sh +++ b/scripts/docker/build_sodium.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e . ./config.sh SODIUM_SRC_DIR=${WORKDIR}/libsodium diff --git a/scripts/docker/build_unbound.sh b/scripts/docker/build_unbound.sh old mode 100644 new mode 100755 index 8786b0f2b7..2d1efdea2c --- a/scripts/docker/build_unbound.sh +++ b/scripts/docker/build_unbound.sh @@ -1,7 +1,7 @@ #!/bin/bash +set -x -e . ./config.sh - EXPAT_VERSION=R_2_4_8 EXPAT_HASH="3bab6c09bbe8bf42d84b81563ddbcf4cca4be838" EXPAT_SRC_DIR=$WORKDIR/libexpat diff --git a/scripts/docker/build_zmq.sh b/scripts/docker/build_zmq.sh old mode 100644 new mode 100755 index bbff9e41bc..19bb99172b --- a/scripts/docker/build_zmq.sh +++ b/scripts/docker/build_zmq.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e . ./config.sh ZMQ_SRC_DIR=$WORKDIR/libzmq diff --git a/scripts/docker/config.sh b/scripts/docker/config.sh old mode 100644 new mode 100755 index c5067f2c3b..a9b691688a --- a/scripts/docker/config.sh +++ b/scripts/docker/config.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e export API=21 export WORKDIR=/opt/android diff --git a/scripts/docker/copy_haven_deps.sh b/scripts/docker/copy_haven_deps.sh old mode 100644 new mode 100755 index d59e9d7f01..5be7a1bb68 --- a/scripts/docker/copy_haven_deps.sh +++ b/scripts/docker/copy_haven_deps.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e WORKDIR=/opt/android CW_DIR=${WORKDIR}/cake_wallet diff --git a/scripts/docker/copy_monero_deps.sh b/scripts/docker/copy_monero_deps.sh old mode 100644 new mode 100755 index e4392186c8..611fedd01a --- a/scripts/docker/copy_monero_deps.sh +++ b/scripts/docker/copy_monero_deps.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e WORKDIR=/opt/android CW_EXRTERNAL_DIR=${WORKDIR}/output/android diff --git a/scripts/docker/docker-compose.yml b/scripts/docker/docker-compose.yml old mode 100644 new mode 100755 diff --git a/scripts/docker/entrypoint.sh b/scripts/docker/entrypoint.sh old mode 100644 new mode 100755 index e4bdc017cb..84f106145f --- a/scripts/docker/entrypoint.sh +++ b/scripts/docker/entrypoint.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e ./build_monero.sh ./copy_monero_deps.sh diff --git a/scripts/docker/finish_boost.sh b/scripts/docker/finish_boost.sh old mode 100644 new mode 100755 index e3f1952762..774c65d77d --- a/scripts/docker/finish_boost.sh +++ b/scripts/docker/finish_boost.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e ARCH=$1 PREFIX=$2 diff --git a/scripts/docker/init_boost.sh b/scripts/docker/init_boost.sh old mode 100644 new mode 100755 index ffb7a14167..068647e1f0 --- a/scripts/docker/init_boost.sh +++ b/scripts/docker/init_boost.sh @@ -1,4 +1,6 @@ #!/bin/bash +set -x -e + ARCH=$1 PREFIX=$2 @@ -17,6 +19,6 @@ echo $BOOST_SHA256 $BOOST_FILE_PATH | sha256sum -c - || exit 1 cd $WORKDIR rm -rf $BOOST_SRC_DIR rm -rf $PREFIX/include/boost -tar -xvf $BOOST_FILE_PATH -C $WORKDIR +tar -xf $BOOST_FILE_PATH -C $WORKDIR cd $BOOST_SRC_DIR -./bootstrap.sh --prefix=${PREFIX} +./bootstrap.sh --prefix=${PREFIX} --with-toolset=gcc diff --git a/scripts/docker/install_ndk.sh b/scripts/docker/install_ndk.sh old mode 100644 new mode 100755 index 5f97751e31..94373954c1 --- a/scripts/docker/install_ndk.sh +++ b/scripts/docker/install_ndk.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x -e . ./config.sh TOOLCHAIN_DIR=${WORKDIR}/toolchain From 7bc6967fc944fe9150b019a435afb063161f4ed6 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Wed, 10 Apr 2024 14:27:10 +0200 Subject: [PATCH 045/242] initial monero.dart implementation --- Makefile | 120 ++++++ android/app/src/main/AndroidManifestBase.xml | 3 +- .../app/src/main/jniLibs/arm64-v8a/.gitkeep | 0 .../app/src/main/jniLibs/armeabi-v7a/.gitkeep | 0 android/app/src/main/jniLibs/x86/.gitkeep | 0 android/app/src/main/jniLibs/x86_64/.gitkeep | 0 cw_haven/lib/api/account_list.dart | 6 +- cw_monero/example/pubspec.lock | 13 +- cw_monero/lib/api/account_list.dart | 59 +-- cw_monero/lib/api/coins_info.dart | 40 +- cw_monero/lib/api/convert_utf8_to_string.dart | 8 - cw_monero/lib/api/monero_api.dart | 6 - cw_monero/lib/api/signatures.dart | 153 -------- cw_monero/lib/api/subaddress_list.dart | 62 +-- cw_monero/lib/api/transaction_history.dart | 359 +++++++++--------- cw_monero/lib/api/wallet.dart | 292 +++----------- cw_monero/lib/api/wallet_manager.dart | 208 ++++------ cw_monero/lib/monero_account_list.dart | 11 +- cw_monero/lib/monero_subaddress_list.dart | 42 +- cw_monero/lib/monero_wallet.dart | 28 +- cw_monero/pubspec.lock | 13 +- cw_monero/pubspec.yaml | 4 + pubspec_base.yaml | 5 +- 23 files changed, 583 insertions(+), 849 deletions(-) create mode 100644 Makefile create mode 100644 android/app/src/main/jniLibs/arm64-v8a/.gitkeep create mode 100644 android/app/src/main/jniLibs/armeabi-v7a/.gitkeep create mode 100644 android/app/src/main/jniLibs/x86/.gitkeep create mode 100644 android/app/src/main/jniLibs/x86_64/.gitkeep delete mode 100644 cw_monero/lib/api/convert_utf8_to_string.dart delete mode 100644 cw_monero/lib/api/monero_api.dart delete mode 100644 cw_monero/lib/api/signatures.dart diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..f8205ab9d5 --- /dev/null +++ b/Makefile @@ -0,0 +1,120 @@ +# TODO(mrcyjanek): Cleanup, this is borrowed from unnamed_monero_wallet repo. + +MONERO_C_TAG=v0.18.3.3-RC21 +LIBCPP_SHARED_SO_TAG=latest-RC1 +LIBCPP_SHARED_SO_NDKVERSION=r17c + +.PHONY: android +android: + ./build_changelog.sh + flutter build apk --split-per-abi --flavor calc --dart-define=libstealth_calculator=true + flutter build apk --split-per-abi --flavor clean --dart-define=libstealth_calculator=false + +.PHONY: linux +linux: + ./build_changelog.sh + flutter build linux + echo https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/${TARGET_TRIPLET}_libwallet2_api_c.so.xz + wget https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/${TARGET_TRIPLET}_libwallet2_api_c.so.xz \ + -O build/linux/${FLUTTER_ARCH}/release/bundle/lib/libwallet2_api_c.so.xz + -rm build/linux/${FLUTTER_ARCH}/release/bundle/lib/libwallet2_api_c.so + unxz build/linux/${FLUTTER_ARCH}/release/bundle/lib/libwallet2_api_c.so.xz + -rm build/linux/${FLUTTER_ARCH}/release/xmruw-linux-${DEBIAN_ARCH}.tar* + (cd build/linux/${FLUTTER_ARCH}/release && cp -a bundle xmruw && tar -cvf xmruw-linux-${DEBIAN_ARCH}.tar xmruw && xz -e xmruw-linux-${DEBIAN_ARCH}.tar) + + +.PHONY: linux_debug_lib +linux_debug_lib: + wget https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/${shell gcc -dumpmachine}_libwallet2_api_c.so.xz \ + -O build/linux/${FLUTTER_ARCH}/debug/bundle/lib/libwallet2_api_c.so.xz + -rm build/linux/${FLUTTER_ARCH}/debug/bundle/lib/libwallet2_api_c.so + unxz build/linux/${FLUTTER_ARCH}/debug/bundle/lib/libwallet2_api_c.so.xz + +deb: + dart pub global activate --source git https://github.com/tomekit/flutter_to_debian.git + cat debian/debian.yaml.txt | sed 's/x64/${FLUTTER_ARCH}/g' | sed 's/amd64/${DEBIAN_ARCH}/g' > debian/debian.yaml + ${HOME}/.pub-cache/bin/flutter_to_debian + +.PHONY: dev +dev: libs + +dev: +lib/const/resource.g.dart: + dart pub global activate flutter_asset_generator + timeout 15 ${HOME}/.pub-cache/bin/fgen || true + mv lib/const/resource.dart lib/const/resource.g.dart +.PHONY: lib/const/resource.g.dart + +.PHONY: sailfishos +sailfishos: + ./build_changelog.sh + bash ./elinux/sailfish_build.sh + +.PHONY: version +version: + sed -i "s/^version: .*/version: 1.0.0+$(shell git rev-list --count HEAD)/" "pubspec.yaml" + sed -i "s/^ Version: .*/ Version: 1.0.0+$(shell git rev-list --count HEAD)/" "debian/debian.yaml.txt" + sed -i "s/^Version=.*/Version=1.0.0+$(shell git rev-list --count HEAD)/" "debian/gui/xmruw.desktop" + sed -i "s/^Version=.*/Version=1.0.0+$(shell git rev-list --count HEAD)/" "elinux/unnamed-monero-wallet.desktop" + sed -i "s/^Version: .*/Version: 1.0.0+$(shell git rev-list --count HEAD)/" "elinux/sailfishos.spec" + sed -i "s/^Release: .*/Release: $(shell git rev-list --count HEAD)/" "elinux/sailfishos.spec" + sed -i "s/^Version: .*/Version: 1.0.0+$(shell git rev-list --count HEAD)/" "elinux/sailfishos.spec" + sed -i "s/^const xmruwVersion = .*/const xmruwVersion = '$(shell git describe --tags)';/" "lib/helpers/licenses_extra.dart" + +.PHONY: lib/helpers/licenses.g.dart +lib/helpers/licenses.g.dart: + dart pub run flutter_oss_licenses:generate.dart -o lib/helpers/licenses.g.dart + +libs: android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so +.PHONY: android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so +android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so: + wget -q https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/monero/aarch64-linux-android_libwallet2_api_c.so.xz -O android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so.xz + unxz android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so.xz + +libs: android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so +.PHONY: android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so +android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so: + wget -q https://git.mrcyjanek.net/mrcyjanek/libcpp_shared.so/releases/download/${LIBCPP_SHARED_SO_TAG}/${LIBCPP_SHARED_SO_NDKVERSION}_arm64-v8a_libc++_shared.so -O android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so + +libs: android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so +.PHONY: android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so +android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so: + wget -q https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/monero/arm-linux-androideabi_libwallet2_api_c.so.xz -O android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so.xz + unxz android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so.xz + +libs: android/app/src/main/jniLibs/armeabi-v7a/libc++_shared.so +.PHONY: android/app/src/main/jniLibs/armeabi-v7a/libc++_shared.so +android/app/src/main/jniLibs/armeabi-v7a/libc++_shared.so: + wget -q https://git.mrcyjanek.net/mrcyjanek/libcpp_shared.so/releases/download/${LIBCPP_SHARED_SO_TAG}/${LIBCPP_SHARED_SO_NDKVERSION}_armeabi-v7a_libc++_shared.so -O android/app/src/main/jniLibs/armeabi-v7a/libc++_shared.so + +# libs: android/app/src/main/jniLibs/x86/libmonero_libwallet2_api_c.so +# .PHONY: android/app/src/main/jniLibs/x86/libmonero_libwallet2_api_c.so +# android/app/src/main/jniLibs/x86/libmonero_libwallet2_api_c.so: +# wget -q https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/monero/i686-linux-android_libwallet2_api_c.so.xz -O android/app/src/main/jniLibs/x86/libmonero_libwallet2_api_c.so.xz +# unxz android/app/src/main/jniLibs/x86/libmonero_libwallet2_api_c.so.xz + +libs: android/app/src/main/jniLibs/x86/libc++_shared.so +.PHONY: android/app/src/main/jniLibs/x86/libc++_shared.so +android/app/src/main/jniLibs/x86/libc++_shared.so: + wget -q https://git.mrcyjanek.net/mrcyjanek/libcpp_shared.so/releases/download/${LIBCPP_SHARED_SO_TAG}/${LIBCPP_SHARED_SO_NDKVERSION}_x86_libc++_shared.so -O android/app/src/main/jniLibs/x86/libc++_shared.so + +libs: android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so +.PHONY: android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so +android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so: + wget -q https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/monero/x86_64-linux-android_libwallet2_api_c.so.xz -O android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so.xz + unxz android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so.xz + +libs: android/app/src/main/jniLibs/x86_64/libc++_shared.so +.PHONY: android/app/src/main/jniLibs/x86_64/libc++_shared.so +android/app/src/main/jniLibs/x86_64/libc++_shared.so: + wget -q https://git.mrcyjanek.net/mrcyjanek/libcpp_shared.so/releases/download/${LIBCPP_SHARED_SO_TAG}/${LIBCPP_SHARED_SO_NDKVERSION}_x86_64_libc++_shared.so -O android/app/src/main/jniLibs/x86_64/libc++_shared.so + +clean_libs: + -rm android/app/src/main/jniLibs/x86_64/libc++_shared.so* + -rm android/app/src/main/jniLibs/x86_64/*_libwallet2_api_c.so* + -rm android/app/src/main/jniLibs/armeabi-v7a/libc++_shared.so* + -rm android/app/src/main/jniLibs/armeabi-v7a/*_libwallet2_api_c.so* + -rm android/app/src/main/jniLibs/x86/libc++_shared.so* + -rm android/app/src/main/jniLibs/x86/*_libwallet2_api_c.so* + -rm android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so* + -rm android/app/src/main/jniLibs/arm64-v8a/*_libwallet2_api_c.so* \ No newline at end of file diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index eea9b55214..ba7868f9ea 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -18,7 +18,8 @@ android:fullBackupContent="false" android:versionCode="__versionCode__" android:versionName="__versionName__" - android:requestLegacyExternalStorage="true"> + android:requestLegacyExternalStorage="true" + android:extractNativeLibs="true"> args) { } Future addAccount({required String label}) async { - await compute(_addAccount, label); + _addAccount(label); await store(); } Future setLabelForAccount({required int accountIndex, required String label}) async { - await compute( - _setLabelForAccount, {'accountIndex': accountIndex, 'label': label}); + _setLabelForAccount({'accountIndex': accountIndex, 'label': label}); await store(); } \ No newline at end of file diff --git a/cw_monero/example/pubspec.lock b/cw_monero/example/pubspec.lock index c9ca8d92b2..e2dd94d7a6 100644 --- a/cw_monero/example/pubspec.lock +++ b/cw_monero/example/pubspec.lock @@ -115,10 +115,10 @@ packages: dependency: transitive description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" file: dependency: transitive description: @@ -241,6 +241,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3+1" + monero: + dependency: transitive + description: + path: "." + ref: master + resolved-ref: "08c5a32cbcf1f04dbae5826c83abda8fb0dbdcce" + url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart" + source: git + version: "0.0.0" path: dependency: transitive description: diff --git a/cw_monero/lib/api/account_list.dart b/cw_monero/lib/api/account_list.dart index 451ba50333..0a18ca5717 100644 --- a/cw_monero/lib/api/account_list.dart +++ b/cw_monero/lib/api/account_list.dart @@ -1,38 +1,16 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; -import 'package:cw_monero/api/signatures.dart'; -import 'package:cw_monero/api/types.dart'; -import 'package:cw_monero/api/monero_api.dart'; -import 'package:cw_monero/api/structs/account_row.dart'; -import 'package:flutter/foundation.dart'; import 'package:cw_monero/api/wallet.dart'; +import 'package:monero/monero.dart' as monero; -final accountSizeNative = moneroApi - .lookup>('account_size') - .asFunction(); - -final accountRefreshNative = moneroApi - .lookup>('account_refresh') - .asFunction(); - -final accountGetAllNative = moneroApi - .lookup>('account_get_all') - .asFunction(); - -final accountAddNewNative = moneroApi - .lookup>('account_add_row') - .asFunction(); - -final accountSetLabelNative = moneroApi - .lookup>('account_set_label_row') - .asFunction(); +monero.wallet? wptr = null; +monero.SubaddressAccount? account; bool isUpdating = false; void refreshAccounts() { try { isUpdating = true; - accountRefreshNative(); + account = monero.Wallet_subaddressAccount(wptr!); + monero.SubaddressAccount_refresh(account!); isUpdating = false; } catch (e) { isUpdating = false; @@ -40,26 +18,22 @@ void refreshAccounts() { } } -List getAllAccount() { - final size = accountSizeNative(); - final accountAddressesPointer = accountGetAllNative(); - final accountAddresses = accountAddressesPointer.asTypedList(size); +List getAllAccount() { + // final size = monero.Wallet_numSubaddressAccounts(wptr!); + final size = monero.SubaddressAccount_getAll_size(wptr!); - return accountAddresses - .map((addr) => Pointer.fromAddress(addr).ref) - .toList(); + return List.generate(size, (index) { + return monero.SubaddressAccount_getAll_byIndex(wptr!, index: index); + }); } void addAccountSync({required String label}) { - final labelPointer = label.toNativeUtf8(); - accountAddNewNative(labelPointer); - calloc.free(labelPointer); + monero.Wallet_addSubaddressAccount(wptr!, label: label); } void setLabelForAccountSync({required int accountIndex, required String label}) { - final labelPointer = label.toNativeUtf8(); - accountSetLabelNative(accountIndex, labelPointer); - calloc.free(labelPointer); + // TODO(mrcyjanek): this may be wrong function? + monero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: 0, label: label); } void _addAccount(String label) => addAccountSync(label: label); @@ -72,12 +46,11 @@ void _setLabelForAccount(Map args) { } Future addAccount({required String label}) async { - await compute(_addAccount, label); + _addAccount(label); await store(); } Future setLabelForAccount({required int accountIndex, required String label}) async { - await compute( - _setLabelForAccount, {'accountIndex': accountIndex, 'label': label}); + _setLabelForAccount({'accountIndex': accountIndex, 'label': label}); await store(); } \ No newline at end of file diff --git a/cw_monero/lib/api/coins_info.dart b/cw_monero/lib/api/coins_info.dart index d7350a6e21..c1b634cc6b 100644 --- a/cw_monero/lib/api/coins_info.dart +++ b/cw_monero/lib/api/coins_info.dart @@ -1,35 +1,17 @@ -import 'dart:ffi'; -import 'package:cw_monero/api/signatures.dart'; -import 'package:cw_monero/api/structs/coins_info_row.dart'; -import 'package:cw_monero/api/types.dart'; -import 'package:cw_monero/api/monero_api.dart'; +import 'package:cw_monero/api/account_list.dart'; +import 'package:monero/monero.dart' as monero; -final refreshCoinsNative = moneroApi - .lookup>('refresh_coins') - .asFunction(); +monero.Coins? coins = null; -final coinsCountNative = moneroApi - .lookup>('coins_count') - .asFunction(); +void refreshCoins(int accountIndex) { + coins = monero.Wallet_coins(wptr!); + monero.Coins_refresh(coins!); +} -final coinNative = moneroApi - .lookup>('coin') - .asFunction(); +int countOfCoins() => monero.Coins_count(coins!); -final freezeCoinNative = moneroApi - .lookup>('freeze_coin') - .asFunction(); +monero.CoinsInfo getCoin(int index) => monero.Coins_coin(coins!, index); -final thawCoinNative = moneroApi - .lookup>('thaw_coin') - .asFunction(); +void freezeCoin(int index) => monero.Coins_setFrozen(coins!, index: index); -void refreshCoins(int accountIndex) => refreshCoinsNative(accountIndex); - -int countOfCoins() => coinsCountNative(); - -CoinsInfoRow getCoin(int index) => coinNative(index).ref; - -void freezeCoin(int index) => freezeCoinNative(index); - -void thawCoin(int index) => thawCoinNative(index); +void thawCoin(int index) => monero.Coins_thaw(coins!, index: index); diff --git a/cw_monero/lib/api/convert_utf8_to_string.dart b/cw_monero/lib/api/convert_utf8_to_string.dart deleted file mode 100644 index 41a6b648a5..0000000000 --- a/cw_monero/lib/api/convert_utf8_to_string.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -String convertUTF8ToString({required Pointer pointer}) { - final str = pointer.toDartString(); - calloc.free(pointer); - return str; -} \ No newline at end of file diff --git a/cw_monero/lib/api/monero_api.dart b/cw_monero/lib/api/monero_api.dart deleted file mode 100644 index 398d737d19..0000000000 --- a/cw_monero/lib/api/monero_api.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'dart:ffi'; -import 'dart:io'; - -final DynamicLibrary moneroApi = Platform.isAndroid - ? DynamicLibrary.open("libcw_monero.so") - : DynamicLibrary.open("cw_monero.framework/cw_monero"); \ No newline at end of file diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart deleted file mode 100644 index bc4fc9d381..0000000000 --- a/cw_monero/lib/api/signatures.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'dart:ffi'; -import 'package:cw_monero/api/structs/coins_info_row.dart'; -import 'package:cw_monero/api/structs/pending_transaction.dart'; -import 'package:cw_monero/api/structs/transaction_info_row.dart'; -import 'package:cw_monero/api/structs/ut8_box.dart'; -import 'package:ffi/ffi.dart'; - -typedef create_wallet = Int8 Function( - Pointer, Pointer, Pointer, Int32, Pointer); - -typedef restore_wallet_from_seed = Int8 Function( - Pointer, Pointer, Pointer, Int32, Int64, Pointer); - -typedef restore_wallet_from_keys = Int8 Function(Pointer, Pointer, Pointer, - Pointer, Pointer, Pointer, Int32, Int64, Pointer); - -typedef restore_wallet_from_spend_key = Int8 Function(Pointer, Pointer, Pointer, - Pointer, Pointer, Int32, Int64, Pointer); - -typedef is_wallet_exist = Int8 Function(Pointer); - -typedef load_wallet = Int8 Function(Pointer, Pointer, Int8); - -typedef error_string = Pointer Function(); - -typedef get_filename = Pointer Function(); - -typedef get_seed = Pointer Function(); - -typedef get_address = Pointer Function(Int32, Int32); - -typedef get_full_balanace = Int64 Function(Int32); - -typedef get_unlocked_balanace = Int64 Function(Int32); - -typedef get_current_height = Int64 Function(); - -typedef get_node_height = Int64 Function(); - -typedef is_connected = Int8 Function(); - -typedef setup_node = Int8 Function( - Pointer, Pointer?, Pointer?, Int8, Int8, Pointer?, Pointer); - -typedef start_refresh = Void Function(); - -typedef connect_to_node = Int8 Function(); - -typedef set_refresh_from_block_height = Void Function(Int64); - -typedef set_recovering_from_seed = Void Function(Int8); - -typedef store_c = Void Function(Pointer); - -typedef set_password = Int8 Function(Pointer password, Pointer error); - -typedef set_listener = Void Function(); - -typedef get_syncing_height = Int64 Function(); - -typedef is_needed_to_refresh = Int8 Function(); - -typedef is_new_transaction_exist = Int8 Function(); - -typedef subaddrress_size = Int32 Function(); - -typedef subaddrress_refresh = Void Function(Int32); - -typedef subaddress_get_all = Pointer Function(); - -typedef subaddress_add_new = Void Function(Int32 accountIndex, Pointer label); - -typedef subaddress_set_label = Void Function( - Int32 accountIndex, Int32 addressIndex, Pointer label); - -typedef account_size = Int32 Function(); - -typedef account_refresh = Void Function(); - -typedef account_get_all = Pointer Function(); - -typedef account_add_new = Void Function(Pointer label); - -typedef account_set_label = Void Function(Int32 accountIndex, Pointer label); - -typedef transactions_refresh = Void Function(); - -typedef get_transaction = Pointer Function(Pointer txId); - -typedef get_tx_key = Pointer? Function(Pointer txId); - -typedef transactions_count = Int64 Function(); - -typedef transactions_get_all = Pointer Function(); - -typedef transaction_create = Int8 Function( - Pointer address, - Pointer paymentId, - Pointer amount, - Int8 priorityRaw, - Int32 subaddrAccount, - Pointer> preferredInputs, - Int32 preferredInputsSize, - Pointer error, - Pointer pendingTransaction); - -typedef transaction_create_mult_dest = Int8 Function( - Pointer> addresses, - Pointer paymentId, - Pointer> amounts, - Int32 size, - Int8 priorityRaw, - Int32 subaddrAccount, - Pointer> preferredInputs, - Int32 preferredInputsSize, - Pointer error, - Pointer pendingTransaction); - -typedef transaction_commit = Int8 Function(Pointer, Pointer); - -typedef secret_view_key = Pointer Function(); - -typedef public_view_key = Pointer Function(); - -typedef secret_spend_key = Pointer Function(); - -typedef public_spend_key = Pointer Function(); - -typedef close_current_wallet = Void Function(); - -typedef on_startup = Void Function(); - -typedef rescan_blockchain = Void Function(); - -typedef get_subaddress_label = Pointer Function(Int32 accountIndex, Int32 addressIndex); - -typedef set_trusted_daemon = Void Function(Int8 trusted); - -typedef trusted_daemon = Int8 Function(); - -typedef refresh_coins = Void Function(Int32 accountIndex); - -typedef coins_count = Int64 Function(); - -// typedef coins_from_txid = Pointer Function(Pointer txid); - -typedef coin = Pointer Function(Int32 index); - -typedef freeze_coin = Void Function(Int32 index); - -typedef thaw_coin = Void Function(Int32 index); - -typedef sign_message = Pointer Function(Pointer message, Pointer address); diff --git a/cw_monero/lib/api/subaddress_list.dart b/cw_monero/lib/api/subaddress_list.dart index 1c1f1253f5..7ac44b1b50 100644 --- a/cw_monero/lib/api/subaddress_list.dart +++ b/cw_monero/lib/api/subaddress_list.dart @@ -1,38 +1,14 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; -import 'package:flutter/foundation.dart'; -import 'package:cw_monero/api/signatures.dart'; -import 'package:cw_monero/api/types.dart'; -import 'package:cw_monero/api/monero_api.dart'; -import 'package:cw_monero/api/structs/subaddress_row.dart'; +import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/wallet.dart'; - -final subaddressSizeNative = moneroApi - .lookup>('subaddrress_size') - .asFunction(); - -final subaddressRefreshNative = moneroApi - .lookup>('subaddress_refresh') - .asFunction(); - -final subaddrressGetAllNative = moneroApi - .lookup>('subaddrress_get_all') - .asFunction(); - -final subaddrressAddNewNative = moneroApi - .lookup>('subaddress_add_row') - .asFunction(); - -final subaddrressSetLabelNative = moneroApi - .lookup>('subaddress_set_label') - .asFunction(); +import 'package:monero/monero.dart' as monero; bool isUpdating = false; - +monero.AddressBook? addressbook = null; void refreshSubaddresses({required int accountIndex}) { try { isUpdating = true; - subaddressRefreshNative(accountIndex); + addressbook = monero.Wallet_subaddressAccount(wptr!); + monero.AddressBook_refresh(addressbook!); isUpdating = false; } catch (e) { isUpdating = false; @@ -40,28 +16,21 @@ void refreshSubaddresses({required int accountIndex}) { } } -List getAllSubaddresses() { - final size = subaddressSizeNative(); - final subaddressAddressesPointer = subaddrressGetAllNative(); - final subaddressAddresses = subaddressAddressesPointer.asTypedList(size); +List getAllSubaddresses() { + final size = monero.AddressBook_getAll_size(addressbook!); - return subaddressAddresses - .map((addr) => Pointer.fromAddress(addr).ref) - .toList(); + return List.generate(size, (index) { + return monero.Subaddress_getAll_byIndex(wptr!, index: index); + }); } void addSubaddressSync({required int accountIndex, required String label}) { - final labelPointer = label.toNativeUtf8(); - subaddrressAddNewNative(accountIndex, labelPointer); - calloc.free(labelPointer); + monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex, label: label); } void setLabelForSubaddressSync( {required int accountIndex, required int addressIndex, required String label}) { - final labelPointer = label.toNativeUtf8(); - - subaddrressSetLabelNative(accountIndex, addressIndex, labelPointer); - calloc.free(labelPointer); + monero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex, label: label); } void _addSubaddress(Map args) { @@ -81,14 +50,13 @@ void _setLabelForSubaddress(Map args) { } Future addSubaddress({required int accountIndex, required String label}) async { - await compute, void>( - _addSubaddress, {'accountIndex': accountIndex, 'label': label}); - await store(); + _addSubaddress({'accountIndex': accountIndex, 'label': label}); + await store(); } Future setLabelForSubaddress( {required int accountIndex, required int addressIndex, required String label}) async { - await compute, void>(_setLabelForSubaddress, { + _setLabelForSubaddress({ 'accountIndex': accountIndex, 'addressIndex': addressIndex, 'label': label diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 73c8de8018..4dff7dfe3a 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -1,78 +1,35 @@ import 'dart:ffi'; -import 'package:cw_monero/api/convert_utf8_to_string.dart'; +import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart'; -import 'package:cw_monero/api/monero_api.dart'; import 'package:cw_monero/api/monero_output.dart'; -import 'package:cw_monero/api/signatures.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; -import 'package:cw_monero/api/structs/transaction_info_row.dart'; -import 'package:cw_monero/api/structs/ut8_box.dart'; -import 'package:cw_monero/api/types.dart'; import 'package:ffi/ffi.dart'; -import 'package:flutter/foundation.dart'; +import 'package:monero/monero.dart' as monero; -final transactionsRefreshNative = moneroApi - .lookup>('transactions_refresh') - .asFunction(); - -final transactionsCountNative = moneroApi - .lookup>('transactions_count') - .asFunction(); - -final transactionsGetAllNative = moneroApi - .lookup>('transactions_get_all') - .asFunction(); - -final transactionCreateNative = moneroApi - .lookup>('transaction_create') - .asFunction(); - -final transactionCreateMultDestNative = moneroApi - .lookup>('transaction_create_mult_dest') - .asFunction(); - -final transactionCommitNative = moneroApi - .lookup>('transaction_commit') - .asFunction(); - -final getTxKeyNative = - moneroApi.lookup>('get_tx_key').asFunction(); - -final getTransactionNative = moneroApi - .lookup>('get_transaction') - .asFunction(); String getTxKey(String txId) { - final txIdPointer = txId.toNativeUtf8(); - final keyPointer = getTxKeyNative(txIdPointer); - - calloc.free(txIdPointer); + return monero.Wallet_getTxKey(wptr!, txid: txId); +} - if (keyPointer != null) { - return convertUTF8ToString(pointer: keyPointer); - } +monero.TransactionHistory? txhistory; - return ''; +void refreshTransactions() { + txhistory = monero.Wallet_history(wptr!); + monero.TransactionHistory_refresh(txhistory!); } -void refreshTransactions() => transactionsRefreshNative(); - -int countOfTransactions() => transactionsCountNative(); +int countOfTransactions() => monero.TransactionHistory_count(txhistory!); -List getAllTransactions() { - final size = transactionsCountNative(); - final transactionsPointer = transactionsGetAllNative(); - final transactionsAddresses = transactionsPointer.asTypedList(size); +List getAllTransactions() { + final size = countOfTransactions(); - return transactionsAddresses - .map((addr) => Pointer.fromAddress(addr).ref) - .toList(); + return List.generate(size, (index) => Transaction(txInfo: monero.TransactionHistory_transaction(txhistory!, index: index))); } -TransactionInfoRow getTransaction(String txId) { - final txIdPointer = txId.toNativeUtf8(); - return getTransactionNative(txIdPointer).ref; +// TODO(mrcyjanek): ... +Transaction getTransaction(String txId) { + return Transaction(txInfo: monero.TransactionHistory_transactionById(txhistory!, txid: txId)); } PendingTransactionDescription createTransactionSync( @@ -82,8 +39,6 @@ PendingTransactionDescription createTransactionSync( String? amount, int accountIndex = 0, List preferredInputs = const []}) { - final addressPointer = address.toNativeUtf8(); - final paymentIdPointer = paymentId.toNativeUtf8(); final amountPointer = amount != null ? amount.toNativeUtf8() : nullptr; final int preferredInputsSize = preferredInputs.length; @@ -95,44 +50,38 @@ PendingTransactionDescription createTransactionSync( preferredInputsPointerPointer[i] = preferredInputsPointers[i]; } - final errorMessagePointer = calloc(); - final pendingTransactionRawPointer = calloc(); - final created = transactionCreateNative( - addressPointer, - paymentIdPointer, - amountPointer, - priorityRaw, - accountIndex, - preferredInputsPointerPointer, - preferredInputsSize, - errorMessagePointer, - pendingTransactionRawPointer) != - 0; - - calloc.free(preferredInputsPointerPointer); - - preferredInputsPointers.forEach((element) => calloc.free(element)); - - calloc.free(addressPointer); - calloc.free(paymentIdPointer); - - if (amountPointer != nullptr) { - calloc.free(amountPointer); - } - - if (!created) { - final message = errorMessagePointer.ref.getValue(); - calloc.free(errorMessagePointer); + final amt = amount == null ? 0 : monero.Wallet_amountFromString(amount); + final pendingTx = monero.Wallet_createTransaction( + wptr!, + dst_addr: address, + payment_id: paymentId, + amount: amt, + mixin_count: 1, + pendingTransactionPriority: priorityRaw, + subaddr_account: accountIndex, + preferredInputs: preferredInputs, + ); + final String? error = (() { + final status = monero.Wallet_status(wptr!); + if (status == 0) { + return null; + } + return monero.Wallet_errorString(wptr!); + })(); + + if (error != null) { + final message = error; throw CreationTransactionException(message: message); } return PendingTransactionDescription( - amount: pendingTransactionRawPointer.ref.amount, - fee: pendingTransactionRawPointer.ref.fee, - hash: pendingTransactionRawPointer.ref.getHash(), - hex: pendingTransactionRawPointer.ref.getHex(), - txKey: pendingTransactionRawPointer.ref.getKey(), - pointerAddress: pendingTransactionRawPointer.address); + amount: monero.PendingTransaction_amount(wptr!), + fee: monero.PendingTransaction_fee(wptr!), + hash: monero.PendingTransaction_txid(wptr!, ''), + hex: '', + txKey: monero.PendingTransaction_txid(wptr!, ''), + pointerAddress: pendingTx.address, + ); } PendingTransactionDescription createTransactionMultDestSync( @@ -141,80 +90,88 @@ PendingTransactionDescription createTransactionMultDestSync( required int priorityRaw, int accountIndex = 0, List preferredInputs = const []}) { - final int size = outputs.length; - final List> addressesPointers = - outputs.map((output) => output.address.toNativeUtf8()).toList(); - final Pointer> addressesPointerPointer = calloc(size); - final List> amountsPointers = - outputs.map((output) => output.amount.toNativeUtf8()).toList(); - final Pointer> amountsPointerPointer = calloc(size); - - for (int i = 0; i < size; i++) { - addressesPointerPointer[i] = addressesPointers[i]; - amountsPointerPointer[i] = amountsPointers[i]; - } - - final int preferredInputsSize = preferredInputs.length; - final List> preferredInputsPointers = - preferredInputs.map((output) => output.toNativeUtf8()).toList(); - final Pointer> preferredInputsPointerPointer = calloc(preferredInputsSize); - - for (int i = 0; i < preferredInputsSize; i++) { - preferredInputsPointerPointer[i] = preferredInputsPointers[i]; - } - - final paymentIdPointer = paymentId.toNativeUtf8(); - final errorMessagePointer = calloc(); - final pendingTransactionRawPointer = calloc(); - final created = transactionCreateMultDestNative( - addressesPointerPointer, - paymentIdPointer, - amountsPointerPointer, - size, - priorityRaw, - accountIndex, - preferredInputsPointerPointer, - preferredInputsSize, - errorMessagePointer, - pendingTransactionRawPointer) != - 0; - - calloc.free(addressesPointerPointer); - calloc.free(amountsPointerPointer); - calloc.free(preferredInputsPointerPointer); - - addressesPointers.forEach((element) => calloc.free(element)); - amountsPointers.forEach((element) => calloc.free(element)); - preferredInputsPointers.forEach((element) => calloc.free(element)); - - calloc.free(paymentIdPointer); - - if (!created) { - final message = errorMessagePointer.ref.getValue(); - calloc.free(errorMessagePointer); - throw CreationTransactionException(message: message); - } - - return PendingTransactionDescription( - amount: pendingTransactionRawPointer.ref.amount, - fee: pendingTransactionRawPointer.ref.fee, - hash: pendingTransactionRawPointer.ref.getHash(), - hex: pendingTransactionRawPointer.ref.getHex(), - txKey: pendingTransactionRawPointer.ref.getKey(), - pointerAddress: pendingTransactionRawPointer.address); + // final int size = outputs.length; + // final List> addressesPointers = + // outputs.map((output) => output.address.toNativeUtf8()).toList(); + // final Pointer> addressesPointerPointer = calloc(size); + // final List> amountsPointers = + // outputs.map((output) => output.amount.toNativeUtf8()).toList(); + // final Pointer> amountsPointerPointer = calloc(size); + + // for (int i = 0; i < size; i++) { + // addressesPointerPointer[i] = addressesPointers[i]; + // amountsPointerPointer[i] = amountsPointers[i]; + // } + + // final int preferredInputsSize = preferredInputs.length; + // final List> preferredInputsPointers = + // preferredInputs.map((output) => output.toNativeUtf8()).toList(); + // final Pointer> preferredInputsPointerPointer = calloc(preferredInputsSize); + + // for (int i = 0; i < preferredInputsSize; i++) { + // preferredInputsPointerPointer[i] = preferredInputsPointers[i]; + // } + + // final paymentIdPointer = paymentId.toNativeUtf8(); + // final errorMessagePointer = calloc(); + // final pendingTransactionRawPointer = calloc(); + // final created = transactionCreateMultDestNative( + // addressesPointerPointer, + // paymentIdPointer, + // amountsPointerPointer, + // size, + // priorityRaw, + // accountIndex, + // preferredInputsPointerPointer, + // preferredInputsSize, + // errorMessagePointer, + // pendingTransactionRawPointer) != + // 0; + + // calloc.free(addressesPointerPointer); + // calloc.free(amountsPointerPointer); + // calloc.free(preferredInputsPointerPointer); + + // addressesPointers.forEach((element) => calloc.free(element)); + // amountsPointers.forEach((element) => calloc.free(element)); + // preferredInputsPointers.forEach((element) => calloc.free(element)); + + // calloc.free(paymentIdPointer); + + // if (!created) { + // final message = errorMessagePointer.ref.getValue(); + // calloc.free(errorMessagePointer); + // throw CreationTransactionException(message: message); + // } + + // return PendingTransactionDescription( + // amount: pendingTransactionRawPointer.ref.amount, + // fee: pendingTransactionRawPointer.ref.fee, + // hash: pendingTransactionRawPointer.ref.getHash(), + // hex: pendingTransactionRawPointer.ref.getHex(), + // txKey: pendingTransactionRawPointer.ref.getKey(), + // pointerAddress: pendingTransactionRawPointer.address); + throw CreationTransactionException(message: "Unimplemented in monero_c"); } void commitTransactionFromPointerAddress({required int address}) => - commitTransaction(transactionPointer: Pointer.fromAddress(address)); - -void commitTransaction({required Pointer transactionPointer}) { - final errorMessagePointer = calloc(); - final isCommited = transactionCommitNative(transactionPointer, errorMessagePointer) != 0; - - if (!isCommited) { - final message = errorMessagePointer.ref.getValue(); - calloc.free(errorMessagePointer); - throw CreationTransactionException(message: message); + commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address)); + +void commitTransaction({required monero.PendingTransaction transactionPointer}) { + + final txCommit = monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false); + final status = monero.PendingTransaction_status(transactionPointer.cast()); + + final String? error = (() { + final status = monero.Wallet_status(wptr!); + if (status == 0) { + return null; + } + return monero.Wallet_errorString(wptr!); + })(); + + if (error != null) { + throw CreationTransactionException(message: error); } } @@ -256,8 +213,8 @@ Future createTransaction( String? amount, String paymentId = '', int accountIndex = 0, - List preferredInputs = const []}) => - compute(_createTransactionSync, { + List preferredInputs = const []}) async => + _createTransactionSync({ 'address': address, 'paymentId': paymentId, 'amount': amount, @@ -271,11 +228,75 @@ Future createTransactionMultDest( required int priorityRaw, String paymentId = '', int accountIndex = 0, - List preferredInputs = const []}) => - compute(_createTransactionMultDestSync, { + List preferredInputs = const []}) async => + _createTransactionMultDestSync({ 'outputs': outputs, 'paymentId': paymentId, 'priorityRaw': priorityRaw, 'accountIndex': accountIndex, 'preferredInputs': preferredInputs }); + + +class Transaction { + final String displayLabel; + String subaddressLabel = monero.Wallet_getSubaddressLabel(wptr!, accountIndex: 0, addressIndex: 0); + late final String address = monero.Wallet_address( + wptr!, + accountIndex: 0, + addressIndex: 0, + ); + final String description; + final int fee; + final int confirmations; + late final bool isPending = confirmations < 10; + final int blockheight; + final int accountIndex; + final String paymentId; + final int amount; + final bool isSpend; + late DateTime timeStamp; + late final bool isConfirmed = !isPending; + final String hash; + + Map toJson() { + return { + "displayLabel": displayLabel, + "subaddressLabel": subaddressLabel, + "address": address, + "description": description, + "fee": fee, + "confirmations": confirmations, + "isPending": isPending, + "blockheight": blockheight, + "accountIndex": accountIndex, + "paymentId": paymentId, + "amount": amount, + "isSpend": isSpend, + "timeStamp": timeStamp.toIso8601String(), + "isConfirmed": isConfirmed, + "hash": hash, + }; + } + + // S finalubAddress? subAddress; + // List transfers = []; + // final int txIndex; + final monero.TransactionInfo txInfo; + Transaction({ + required this.txInfo, + }) : displayLabel = monero.TransactionInfo_label(txInfo), + hash = monero.TransactionInfo_hash(txInfo), + timeStamp = DateTime.fromMillisecondsSinceEpoch( + monero.TransactionInfo_timestamp(txInfo) * 1000, + ), + isSpend = monero.TransactionInfo_direction(txInfo) == + monero.TransactionInfo_Direction.Out, + amount = monero.TransactionInfo_amount(txInfo), + paymentId = monero.TransactionInfo_paymentId(txInfo), + accountIndex = monero.TransactionInfo_subaddrAccount(txInfo), + blockheight = monero.TransactionInfo_blockHeight(txInfo), + confirmations = monero.TransactionInfo_confirmations(txInfo), + fee = monero.TransactionInfo_fee(txInfo), + description = monero.TransactionInfo_description(txInfo); +} \ No newline at end of file diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index ffa5fe13ba..e543a131a3 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -1,160 +1,31 @@ import 'dart:async'; -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; -import 'package:cw_monero/api/structs/ut8_box.dart'; -import 'package:cw_monero/api/convert_utf8_to_string.dart'; -import 'package:cw_monero/api/signatures.dart'; -import 'package:cw_monero/api/types.dart'; -import 'package:cw_monero/api/monero_api.dart'; +import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; -import 'package:flutter/foundation.dart'; +import 'package:monero/monero.dart' as monero; int _boolToInt(bool value) => value ? 1 : 0; -final getFileNameNative = moneroApi - .lookup>('get_filename') - .asFunction(); +int getSyncingHeight() => monero.Wallet_blockChainHeight(wptr!); -final getSeedNative = - moneroApi.lookup>('seed').asFunction(); +bool isNeededToRefresh() => false; // TODO(mrcyjanek): ? -final getAddressNative = moneroApi - .lookup>('get_address') - .asFunction(); +bool isNewTransactionExist() => false; -final getFullBalanceNative = moneroApi - .lookup>('get_full_balance') - .asFunction(); +String getFilename() => monero.Wallet_filename(wptr!); -final getUnlockedBalanceNative = moneroApi - .lookup>('get_unlocked_balance') - .asFunction(); +String getSeed() => monero.Wallet_seed(wptr!, seedOffset: ''); -final getCurrentHeightNative = moneroApi - .lookup>('get_current_height') - .asFunction(); +String getAddress({int accountIndex = 0, int addressIndex = 0}) => monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); -final getNodeHeightNative = moneroApi - .lookup>('get_node_height') - .asFunction(); +int getFullBalance({int accountIndex = 0}) => monero.Wallet_balance(wptr!, accountIndex: accountIndex); -final isConnectedNative = moneroApi - .lookup>('is_connected') - .asFunction(); +int getUnlockedBalance({int accountIndex = 0}) => monero.Wallet_unlockedBalance(wptr!, accountIndex: accountIndex); -final setupNodeNative = moneroApi - .lookup>('setup_node') - .asFunction(); +int getCurrentHeight() => monero.Wallet_blockChainHeight(wptr!); -final startRefreshNative = moneroApi - .lookup>('start_refresh') - .asFunction(); +int getNodeHeightSync() => monero.Wallet_daemonBlockChainHeight(wptr!); -final connecToNodeNative = moneroApi - .lookup>('connect_to_node') - .asFunction(); - -final setRefreshFromBlockHeightNative = moneroApi - .lookup>( - 'set_refresh_from_block_height') - .asFunction(); - -final setRecoveringFromSeedNative = moneroApi - .lookup>( - 'set_recovering_from_seed') - .asFunction(); - -final storeNative = - moneroApi.lookup>('store').asFunction(); - -final setPasswordNative = - moneroApi.lookup>('set_password').asFunction(); - -final setListenerNative = moneroApi - .lookup>('set_listener') - .asFunction(); - -final getSyncingHeightNative = moneroApi - .lookup>('get_syncing_height') - .asFunction(); - -final isNeededToRefreshNative = moneroApi - .lookup>('is_needed_to_refresh') - .asFunction(); - -final isNewTransactionExistNative = moneroApi - .lookup>( - 'is_new_transaction_exist') - .asFunction(); - -final getSecretViewKeyNative = moneroApi - .lookup>('secret_view_key') - .asFunction(); - -final getPublicViewKeyNative = moneroApi - .lookup>('public_view_key') - .asFunction(); - -final getSecretSpendKeyNative = moneroApi - .lookup>('secret_spend_key') - .asFunction(); - -final getPublicSpendKeyNative = moneroApi - .lookup>('public_spend_key') - .asFunction(); - -final closeCurrentWalletNative = moneroApi - .lookup>('close_current_wallet') - .asFunction(); - -final onStartupNative = moneroApi - .lookup>('on_startup') - .asFunction(); - -final rescanBlockchainAsyncNative = moneroApi - .lookup>('rescan_blockchain') - .asFunction(); - -final getSubaddressLabelNative = moneroApi - .lookup>('get_subaddress_label') - .asFunction(); - -final setTrustedDaemonNative = moneroApi - .lookup>('set_trusted_daemon') - .asFunction(); - -final trustedDaemonNative = moneroApi - .lookup>('trusted_daemon') - .asFunction(); - -final signMessageNative = moneroApi - .lookup>('sign_message') - .asFunction(); - -int getSyncingHeight() => getSyncingHeightNative(); - -bool isNeededToRefresh() => isNeededToRefreshNative() != 0; - -bool isNewTransactionExist() => isNewTransactionExistNative() != 0; - -String getFilename() => convertUTF8ToString(pointer: getFileNameNative()); - -String getSeed() => convertUTF8ToString(pointer: getSeedNative()); - -String getAddress({int accountIndex = 0, int addressIndex = 0}) => - convertUTF8ToString(pointer: getAddressNative(accountIndex, addressIndex)); - -int getFullBalance({int accountIndex = 0}) => - getFullBalanceNative(accountIndex); - -int getUnlockedBalance({int accountIndex = 0}) => - getUnlockedBalanceNative(accountIndex); - -int getCurrentHeight() => getCurrentHeightNative(); - -int getNodeHeightSync() => getNodeHeightNative(); - -bool isConnectedSync() => isConnectedNative() != 0; +bool isConnectedSync() => monero.Wallet_connected(wptr!) != 0; bool setupNodeSync( {required String address, @@ -163,96 +34,64 @@ bool setupNodeSync( bool useSSL = false, bool isLightWallet = false, String? socksProxyAddress}) { - final addressPointer = address.toNativeUtf8(); - Pointer? loginPointer; - Pointer? socksProxyAddressPointer; - Pointer? passwordPointer; - - if (login != null) { - loginPointer = login.toNativeUtf8(); - } - - if (password != null) { - passwordPointer = password.toNativeUtf8(); - } - - if (socksProxyAddress != null) { - socksProxyAddressPointer = socksProxyAddress.toNativeUtf8(); - } - final errorMessagePointer = ''.toNativeUtf8(); - final isSetupNode = setupNodeNative( - addressPointer, - loginPointer, - passwordPointer, - _boolToInt(useSSL), - _boolToInt(isLightWallet), - socksProxyAddressPointer, - errorMessagePointer) != - 0; - - calloc.free(addressPointer); - - if (loginPointer != null) { - calloc.free(loginPointer); - } - - if (passwordPointer != null) { - calloc.free(passwordPointer); - } + monero.Wallet_init( + wptr!, + daemonAddress: address, + useSsl: useSSL, + proxyAddress: socksProxyAddress ?? '', + daemonUsername: login ?? '', + daemonPassword: password ?? '' + ); + // monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true); + monero.Wallet_startRefresh(wptr!); + monero.Wallet_refreshAsync(wptr!); + + final status = monero.Wallet_status(wptr!); - if (!isSetupNode) { - throw SetupWalletException( - message: convertUTF8ToString(pointer: errorMessagePointer)); + if (status == 0) { + throw SetupWalletException(message: monero.Wallet_errorString(wptr!)); } - return isSetupNode; + return status == 0; } -void startRefreshSync() => startRefreshNative(); +void startRefreshSync() {} -Future connectToNode() async => connecToNodeNative() != 0; +Future connectToNode() async { + return true; +} -void setRefreshFromBlockHeight({required int height}) => - setRefreshFromBlockHeightNative(height); +void setRefreshFromBlockHeight({required int height}) => monero.Wallet_setRefreshFromBlockHeight(wptr!, refresh_from_block_height: height); -void setRecoveringFromSeed({required bool isRecovery}) => - setRecoveringFromSeedNative(_boolToInt(isRecovery)); +void setRecoveringFromSeed({required bool isRecovery}) => monero.Wallet_setRecoveringFromSeed(wptr!, recoveringFromSeed: isRecovery); void storeSync() { - final pathPointer = ''.toNativeUtf8(); - storeNative(pathPointer); - calloc.free(pathPointer); + monero.Wallet_store(wptr!); } void setPasswordSync(String password) { - final passwordPointer = password.toNativeUtf8(); - final errorMessagePointer = calloc(); - final changed = setPasswordNative(passwordPointer, errorMessagePointer) != 0; - calloc.free(passwordPointer); - - if (!changed) { - final message = errorMessagePointer.ref.getValue(); - calloc.free(errorMessagePointer); - throw Exception(message); - } + monero.Wallet_setPassword(wptr!, password: password); + - calloc.free(errorMessagePointer); + final status = monero.Wallet_status(wptr!); + if (status == 0) { + throw Exception(monero.Wallet_errorString(wptr!)); + } } -void closeCurrentWallet() => closeCurrentWalletNative(); +void closeCurrentWallet() { + monero.Wallet_store(wptr!); + monero.Wallet_stop(wptr!); +} -String getSecretViewKey() => - convertUTF8ToString(pointer: getSecretViewKeyNative()); +String getSecretViewKey() => monero.Wallet_secretViewKey(wptr!); -String getPublicViewKey() => - convertUTF8ToString(pointer: getPublicViewKeyNative()); +String getPublicViewKey() => monero.Wallet_publicViewKey(wptr!); -String getSecretSpendKey() => - convertUTF8ToString(pointer: getSecretSpendKeyNative()); +String getSecretSpendKey() => monero.Wallet_secretSpendKey(wptr!); -String getPublicSpendKey() => - convertUTF8ToString(pointer: getPublicSpendKeyNative()); +String getPublicSpendKey() => monero.Wallet_publicSpendKey(wptr!); class SyncListener { SyncListener(this.onNewBlock, this.onNewTransaction) @@ -324,11 +163,11 @@ class SyncListener { SyncListener setListeners(void Function(int, int, double) onNewBlock, void Function() onNewTransaction) { final listener = SyncListener(onNewBlock, onNewTransaction); - setListenerNative(); + // setListenerNative(); return listener; } -void onStartup() => onStartupNative(); +void onStartup() {} void _storeSync(Object _) => storeSync(); @@ -361,8 +200,8 @@ Future setupNode( String? password, bool useSSL = false, String? socksProxyAddress, - bool isLightWallet = false}) => - compute, void>(_setupNodeSync, { + bool isLightWallet = false}) async => + _setupNodeSync({ 'address': address, 'login': login , 'password': password, @@ -371,29 +210,22 @@ Future setupNode( 'socksProxyAddress': socksProxyAddress }); -Future store() => compute(_storeSync, 0); +Future store() async => _storeSync(0); -Future isConnected() => compute(_isConnected, 0); +Future isConnected() async => _isConnected(0); -Future getNodeHeight() => compute(_getNodeHeight, 0); +Future getNodeHeight() async => _getNodeHeight(0); -void rescanBlockchainAsync() => rescanBlockchainAsyncNative(); +void rescanBlockchainAsync() => monero.Wallet_rescanBlockchainAsync(wptr!); String getSubaddressLabel(int accountIndex, int addressIndex) { - return convertUTF8ToString(pointer: getSubaddressLabelNative(accountIndex, addressIndex)); + return monero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); } -Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted)); +Future setTrustedDaemon(bool trusted) async => monero.Wallet_setTrustedDaemon(wptr!, arg: trusted); -Future trustedDaemon() async => trustedDaemonNative() != 0; +Future trustedDaemon() async => monero.Wallet_trustedDaemon(wptr!); String signMessage(String message, {String address = ""}) { - final messagePointer = message.toNativeUtf8(); - final addressPointer = address.toNativeUtf8(); - - final signature = convertUTF8ToString(pointer: signMessageNative(messagePointer, addressPointer)); - calloc.free(messagePointer); - calloc.free(addressPointer); - - return signature; + return monero.Wallet_signMessage(wptr!, message: message, address: address); } diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 0aa694e9ae..5913b25650 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -1,80 +1,44 @@ -import 'dart:ffi'; -import 'package:cw_monero/api/convert_utf8_to_string.dart'; +import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart'; -import 'package:cw_monero/api/monero_api.dart'; -import 'package:cw_monero/api/signatures.dart'; -import 'package:cw_monero/api/types.dart'; import 'package:cw_monero/api/wallet.dart'; import 'package:ffi/ffi.dart'; -import 'package:flutter/foundation.dart'; - -final createWalletNative = moneroApi - .lookup>('create_wallet') - .asFunction(); - -final restoreWalletFromSeedNative = moneroApi - .lookup>( - 'restore_wallet_from_seed') - .asFunction(); - -final restoreWalletFromKeysNative = moneroApi - .lookup>( - 'restore_wallet_from_keys') - .asFunction(); - -final restoreWalletFromSpendKeyNative = moneroApi - .lookup>( - 'restore_wallet_from_spend_key') - .asFunction(); - -final isWalletExistNative = moneroApi - .lookup>('is_wallet_exist') - .asFunction(); - -final loadWalletNative = moneroApi - .lookup>('load_wallet') - .asFunction(); +import 'package:monero/monero.dart' as monero; +import 'dart:ffi'; -final errorStringNative = moneroApi - .lookup>('error_string') - .asFunction(); +monero.WalletManager? _wmPtr; +final monero.WalletManager wmPtr = Pointer.fromAddress((() { + try { + monero.printStarts = true; + _wmPtr ??= monero.WalletManagerFactory_getWalletManager(); + print("ptr: $_wmPtr"); + } catch (e) { + print(e); + } + return _wmPtr!.address; +})()); void createWalletSync( {required String path, required String password, required String language, int nettype = 0}) { - final pathPointer = path.toNativeUtf8(); - final passwordPointer = password.toNativeUtf8(); - final languagePointer = language.toNativeUtf8(); - final errorMessagePointer = ''.toNativeUtf8(); - final isWalletCreated = createWalletNative(pathPointer, passwordPointer, - languagePointer, nettype, errorMessagePointer) != - 0; + wptr = monero.WalletManager_createWallet(wmPtr, path: path, password: password, language: language); - calloc.free(pathPointer); - calloc.free(passwordPointer); - calloc.free(languagePointer); - - if (!isWalletCreated) { - throw WalletCreationException( - message: convertUTF8ToString(pointer: errorMessagePointer)); + final status = monero.Wallet_status(wptr!); + if (status != 0) { + throw WalletCreationException(message: monero.Wallet_errorString(wptr!)); } + // is the line below needed? // setupNodeSync(address: "node.moneroworld.com:18089"); } bool isWalletExistSync({required String path}) { - final pathPointer = path.toNativeUtf8(); - final isExist = isWalletExistNative(pathPointer) != 0; - - calloc.free(pathPointer); - - return isExist; + return monero.WalletManager_walletExists(wmPtr, path); } void restoreWalletFromSeedSync( @@ -87,22 +51,20 @@ void restoreWalletFromSeedSync( final passwordPointer = password.toNativeUtf8(); final seedPointer = seed.toNativeUtf8(); final errorMessagePointer = ''.toNativeUtf8(); - final isWalletRestored = restoreWalletFromSeedNative( - pathPointer, - passwordPointer, - seedPointer, - nettype, - restoreHeight, - errorMessagePointer) != - 0; - - calloc.free(pathPointer); - calloc.free(passwordPointer); - calloc.free(seedPointer); - - if (!isWalletRestored) { - throw WalletRestoreFromSeedException( - message: convertUTF8ToString(pointer: errorMessagePointer)); + + wptr = monero.WalletManager_recoveryWallet( + wmPtr, + path: path, + password: password, + mnemonic: seed, + restoreHeight: restoreHeight, + seedOffset: '', + ); + + final status = monero.Wallet_status(wptr!); + + if (status != 0) { + throw WalletRestoreFromSeedException(message: monero.Wallet_errorString(wptr!)); } } @@ -122,28 +84,19 @@ void restoreWalletFromKeysSync( final viewKeyPointer = viewKey.toNativeUtf8(); final spendKeyPointer = spendKey.toNativeUtf8(); final errorMessagePointer = ''.toNativeUtf8(); - final isWalletRestored = restoreWalletFromKeysNative( - pathPointer, - passwordPointer, - languagePointer, - addressPointer, - viewKeyPointer, - spendKeyPointer, - nettype, - restoreHeight, - errorMessagePointer) != - 0; - - calloc.free(pathPointer); - calloc.free(passwordPointer); - calloc.free(languagePointer); - calloc.free(addressPointer); - calloc.free(viewKeyPointer); - calloc.free(spendKeyPointer); - - if (!isWalletRestored) { - throw WalletRestoreFromKeysException( - message: convertUTF8ToString(pointer: errorMessagePointer)); + wptr = monero.WalletManager_createWalletFromKeys( + wmPtr, + path: path, + password: password, + restoreHeight: restoreHeight, + addressString: address, + viewKeyString: viewKey, + spendKeyString: spendKey, + ); + + final status = monero.Wallet_status(wptr!); + if (status != 0) { + throw WalletRestoreFromKeysException(message: monero.Wallet_errorString(wptr!)); } } @@ -161,43 +114,40 @@ void restoreWalletFromSpendKeySync( final languagePointer = language.toNativeUtf8(); final spendKeyPointer = spendKey.toNativeUtf8(); final errorMessagePointer = ''.toNativeUtf8(); - final isWalletRestored = restoreWalletFromSpendKeyNative( - pathPointer, - passwordPointer, - seedPointer, - languagePointer, - spendKeyPointer, - nettype, - restoreHeight, - errorMessagePointer) != - 0; - - calloc.free(pathPointer); - calloc.free(passwordPointer); - calloc.free(languagePointer); - calloc.free(spendKeyPointer); - storeSync(); + wptr = monero.WalletManager_createWalletFromKeys( + wmPtr, + path: path, + password: password, + restoreHeight: restoreHeight, + addressString: '', + spendKeyString: spendKey, + viewKeyString: '', + ); + + final status = monero.Wallet_status(wptr!); - if (!isWalletRestored) { - throw WalletRestoreFromKeysException( - message: convertUTF8ToString(pointer: errorMessagePointer)); + if (status == 0) { + throw WalletRestoreFromKeysException(message: monero.Wallet_errorString(wptr!)); } + + storeSync(); } void loadWallet({ required String path, required String password, int nettype = 0}) { - final pathPointer = path.toNativeUtf8(); - final passwordPointer = password.toNativeUtf8(); - final loaded = loadWalletNative(pathPointer, passwordPointer, nettype) != 0; - calloc.free(pathPointer); - calloc.free(passwordPointer); - - if (!loaded) { - throw WalletOpeningException( - message: convertUTF8ToString(pointer: errorStringNative())); + try { + wptr ??= monero.WalletManager_openWallet(wmPtr, path: path, password: password); + } catch (e) { + print(e); + } + final status = monero.Wallet_status(wptr!); + if (status != 0) { + final err = monero.Wallet_errorString(wptr!); + print(err); + throw WalletOpeningException(message: err); } } @@ -258,20 +208,20 @@ void _restoreFromSpendKey(Map args) { Future _openWallet(Map args) async => loadWallet(path: args['path'] as String, password: args['password'] as String); -bool _isWalletExist(String path) => isWalletExistSync(path: path); +Future _isWalletExist(String path) async => isWalletExistSync(path: path); void openWallet({required String path, required String password, int nettype = 0}) async => loadWallet(path: path, password: password, nettype: nettype); Future openWalletAsync(Map args) async => - compute(_openWallet, args); + _openWallet(args); Future createWallet( {required String path, required String password, required String language, int nettype = 0}) async => - compute(_createWallet, { + _createWallet({ 'path': path, 'password': password, 'language': language, @@ -284,7 +234,7 @@ Future restoreFromSeed( required String seed, int nettype = 0, int restoreHeight = 0}) async => - compute, void>(_restoreFromSeed, { + _restoreFromSeed({ 'path': path, 'password': password, 'seed': seed, @@ -301,7 +251,7 @@ Future restoreFromKeys( required String spendKey, int nettype = 0, int restoreHeight = 0}) async => - compute, void>(_restoreFromKeys, { + _restoreFromKeys({ 'path': path, 'password': password, 'language': language, @@ -320,7 +270,7 @@ Future restoreFromSpendKey( required String spendKey, int nettype = 0, int restoreHeight = 0}) async => - compute, void>(_restoreFromSpendKey, { + _restoreFromSpendKey({ 'path': path, 'password': password, 'seed': seed, @@ -330,4 +280,4 @@ Future restoreFromSpendKey( 'restoreHeight': restoreHeight }); -Future isWalletExist({required String path}) => compute(_isWalletExist, path); +Future isWalletExist({required String path}) => _isWalletExist(path); diff --git a/cw_monero/lib/monero_account_list.dart b/cw_monero/lib/monero_account_list.dart index 2fd11b3ba6..29d096efd5 100644 --- a/cw_monero/lib/monero_account_list.dart +++ b/cw_monero/lib/monero_account_list.dart @@ -2,7 +2,7 @@ import 'package:cw_core/monero_amount_format.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/account.dart'; import 'package:cw_monero/api/account_list.dart' as account_list; -import 'package:cw_monero/api/wallet.dart' as monero_wallet; +import 'package:monero/monero.dart' as monero; part 'monero_account_list.g.dart'; @@ -44,13 +44,12 @@ abstract class MoneroAccountListBase with Store { } List getAll() => account_list.getAllAccount().map((accountRow) { - final accountIndex = accountRow.getId(); - final balance = monero_wallet.getFullBalance(accountIndex: accountIndex); + final balance = monero.SubaddressAccountRow_getUnlockedBalance(accountRow); return Account( - id: accountRow.getId(), - label: accountRow.getLabel(), - balance: moneroAmountToString(amount: balance), + id: monero.SubaddressAccountRow_getRowId(accountRow), + label: monero.SubaddressAccountRow_getLabel(accountRow), + balance: moneroAmountToString(amount: monero.Wallet_amountFromString(balance)), ); }).toList(); diff --git a/cw_monero/lib/monero_subaddress_list.dart b/cw_monero/lib/monero_subaddress_list.dart index dbd1a89aec..2d3ecdb7ea 100644 --- a/cw_monero/lib/monero_subaddress_list.dart +++ b/cw_monero/lib/monero_subaddress_list.dart @@ -3,6 +3,7 @@ import 'package:mobx/mobx.dart'; import 'package:cw_monero/api/coins_info.dart'; import 'package:cw_monero/api/subaddress_list.dart' as subaddress_list; import 'package:cw_core/subaddress.dart'; +import 'package:monero/monero.dart' as monero; part 'monero_subaddress_list.g.dart'; @@ -51,18 +52,21 @@ abstract class MoneroSubaddressListBase with Store { } return subaddresses.map((subaddressRow) { + final label = monero.SubaddressRow_getLabel(subaddressRow); + final id = monero.SubaddressRow_getRowId(subaddressRow); + final address = monero.SubaddressRow_getAddress(subaddressRow); final hasDefaultAddressName = - subaddressRow.getLabel().toLowerCase() == 'Primary account'.toLowerCase() || - subaddressRow.getLabel().toLowerCase() == 'Untitled account'.toLowerCase(); - final isPrimaryAddress = subaddressRow.getId() == 0 && hasDefaultAddressName; + label.toLowerCase() == 'Primary account'.toLowerCase() || + label.toLowerCase() == 'Untitled account'.toLowerCase(); + final isPrimaryAddress = id == 0 && hasDefaultAddressName; return Subaddress( - id: subaddressRow.getId(), - address: subaddressRow.getAddress(), + id: id, + address: address, label: isPrimaryAddress ? 'Primary address' : hasDefaultAddressName ? '' - : subaddressRow.getLabel()); + : label); }).toList(); } @@ -121,8 +125,8 @@ abstract class MoneroSubaddressListBase with Store { Future> _getAllUnusedAddresses( {required int accountIndex, required String label}) async { final allAddresses = subaddress_list.getAllSubaddresses(); - - if (allAddresses.isEmpty || _usedAddresses.contains(allAddresses.last.getAddress())) { + final lastAddress = monero.SubaddressRow_getAddress(allAddresses.last); + if (allAddresses.isEmpty || _usedAddresses.contains(lastAddress)) { final isAddressUnused = await _newSubaddress(accountIndex: accountIndex, label: label); if (!isAddressUnused) { return await _getAllUnusedAddresses(accountIndex: accountIndex, label: label); @@ -130,13 +134,18 @@ abstract class MoneroSubaddressListBase with Store { } return allAddresses - .map((subaddressRow) => Subaddress( - id: subaddressRow.getId(), - address: subaddressRow.getAddress(), - label: subaddressRow.getId() == 0 && - subaddressRow.getLabel().toLowerCase() == 'Primary account'.toLowerCase() + .map((subaddressRow) { + final id = monero.SubaddressRow_getRowId(subaddressRow); + final address = monero.SubaddressRow_getAddress(subaddressRow); + final label = monero.SubaddressRow_getLabel(subaddressRow); + return Subaddress( + id: id, + address: address, + label: id == 0 && + label.toLowerCase() == 'Primary account'.toLowerCase() ? 'Primary address' - : subaddressRow.getLabel())) + : label); + }) .toList(); } @@ -145,7 +154,10 @@ abstract class MoneroSubaddressListBase with Store { return subaddress_list .getAllSubaddresses() - .where((subaddressRow) => !_usedAddresses.contains(subaddressRow.getAddress())) + .where((subaddressRow) { + final address = monero.SubaddressRow_getAddress(subaddressRow); + return !_usedAddresses.contains(address); + }) .isNotEmpty; } } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index d00a54c8ff..e2e7196ba4 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -12,6 +12,7 @@ import 'package:cw_core/node.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; @@ -32,6 +33,7 @@ import 'package:cw_monero/pending_monero_transaction.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; +import 'package:monero/monero.dart' as monero; part 'monero_wallet.g.dart'; @@ -417,10 +419,18 @@ abstract class MoneroWalletBase final coinCount = countOfCoins(); for (var i = 0; i < coinCount; i++) { final coin = getCoin(i); - if (coin.spent == 0) { - final unspent = MoneroUnspent.fromCoinsInfoRow(coin); + final coinSpent = monero.CoinsInfo_spent(coin); + if (coinSpent == 0) { + final unspent = MoneroUnspent( + monero.CoinsInfo_address(coin), + monero.CoinsInfo_hash(coin), + monero.CoinsInfo_keyImage(coin), + monero.CoinsInfo_amount(coin), + monero.CoinsInfo_frozen(coin), + monero.CoinsInfo_unlocked(coin), + ); if (unspent.hash.isNotEmpty) { - unspent.isChange = transaction_history.getTransaction(unspent.hash).direction == 1; + unspent.isChange = transaction_history.getTransaction(unspent.hash) == 1; } unspentCoins.add(unspent); } @@ -541,7 +551,17 @@ abstract class MoneroWalletBase List _getAllTransactionsOfAccount(int? accountIndex) => transaction_history .getAllTransactions() - .map((row) => MoneroTransactionInfo.fromRow(row)) + .map((row) => MoneroTransactionInfo( + row.hash, + row.blockheight, + row.isSpend ? TransactionDirection.outgoing : TransactionDirection.incoming, + row.timeStamp, + row.isPending, + row.amount, + row.accountIndex, + 0, + row.fee, + row.confirmations)) .where((element) => element.accountIndex == (accountIndex ?? 0)) .toList(); diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 0f8f2c90e6..0faf7dab8a 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -204,10 +204,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" file: dependency: transitive description: @@ -410,6 +410,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + monero: + dependency: "direct main" + description: + path: "." + ref: master + resolved-ref: "08c5a32cbcf1f04dbae5826c83abda8fb0dbdcce" + url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart" + source: git + version: "0.0.0" package_config: dependency: transitive description: diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index a6fe7f9679..e94b650674 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -22,6 +22,10 @@ dependencies: polyseed: ^0.0.2 cw_core: path: ../cw_core + monero: + git: + url: https://git.mrcyjanek.net/mrcyjanek/monero.dart + ref: master dev_dependencies: flutter_test: diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 3ec3e79785..0f395007d4 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -111,7 +111,10 @@ dependencies: git: url: https://github.com/cake-tech/bitcoin_base.git ref: cake-update-v2 - + monero: + git: + url: https://git.mrcyjanek.net/mrcyjanek/monero.dart + ref: master dev_dependencies: flutter_test: sdk: flutter From 176150cc550d3ad05ca67591bffd071c5b595fca Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Fri, 12 Apr 2024 14:54:24 +0200 Subject: [PATCH 046/242] ... --- cw_monero/example/ios/Podfile | 44 ++++++++++++++++++++++++++ cw_monero/lib/api/account_list.dart | 17 ++++++---- cw_monero/lib/api/subaddress_list.dart | 16 +++++++--- cw_monero/lib/api/wallet.dart | 22 ++++++++++--- cw_monero/lib/api/wallet_manager.dart | 13 ++++---- 5 files changed, 90 insertions(+), 22 deletions(-) create mode 100644 cw_monero/example/ios/Podfile diff --git a/cw_monero/example/ios/Podfile b/cw_monero/example/ios/Podfile new file mode 100644 index 0000000000..fdcc671eb3 --- /dev/null +++ b/cw_monero/example/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/cw_monero/lib/api/account_list.dart b/cw_monero/lib/api/account_list.dart index 0a18ca5717..ae94d16ea5 100644 --- a/cw_monero/lib/api/account_list.dart +++ b/cw_monero/lib/api/account_list.dart @@ -2,15 +2,15 @@ import 'package:cw_monero/api/wallet.dart'; import 'package:monero/monero.dart' as monero; monero.wallet? wptr = null; -monero.SubaddressAccount? account; +monero.SubaddressAccount? subaddressAccount; bool isUpdating = false; void refreshAccounts() { try { isUpdating = true; - account = monero.Wallet_subaddressAccount(wptr!); - monero.SubaddressAccount_refresh(account!); + subaddressAccount = monero.Wallet_subaddressAccount(wptr!); + monero.SubaddressAccount_refresh(subaddressAccount!); isUpdating = false; } catch (e) { isUpdating = false; @@ -20,10 +20,15 @@ void refreshAccounts() { List getAllAccount() { // final size = monero.Wallet_numSubaddressAccounts(wptr!); - final size = monero.SubaddressAccount_getAll_size(wptr!); - + refreshAccounts(); + int size = monero.SubaddressAccount_getAll_size(subaddressAccount!); + print("size: $size"); + if (size == 0) { + monero.Wallet_addSubaddressAccount(wptr!); + return getAllAccount(); + } return List.generate(size, (index) { - return monero.SubaddressAccount_getAll_byIndex(wptr!, index: index); + return monero.SubaddressAccount_getAll_byIndex(subaddressAccount!, index: index); }); } diff --git a/cw_monero/lib/api/subaddress_list.dart b/cw_monero/lib/api/subaddress_list.dart index 7ac44b1b50..a172b93c44 100644 --- a/cw_monero/lib/api/subaddress_list.dart +++ b/cw_monero/lib/api/subaddress_list.dart @@ -3,12 +3,12 @@ import 'package:cw_monero/api/wallet.dart'; import 'package:monero/monero.dart' as monero; bool isUpdating = false; -monero.AddressBook? addressbook = null; +monero.Subaddress? subaddressPtr = null; void refreshSubaddresses({required int accountIndex}) { try { isUpdating = true; - addressbook = monero.Wallet_subaddressAccount(wptr!); - monero.AddressBook_refresh(addressbook!); + subaddressPtr = monero.Wallet_subaddress(wptr!); + monero.Subaddress_refresh(subaddressPtr!,accountIndex: accountIndex, label: ''); isUpdating = false; } catch (e) { isUpdating = false; @@ -17,15 +17,21 @@ void refreshSubaddresses({required int accountIndex}) { } List getAllSubaddresses() { - final size = monero.AddressBook_getAll_size(addressbook!); + monero.Subaddress_refresh(subaddressPtr!, + accountIndex: 0, + label: '' // BUG: by me (mrcyjanek), it isn't used, will remove. + ); + final size = monero.Subaddress_getAll_size(subaddressPtr!); + return List.generate(size, (index) { - return monero.Subaddress_getAll_byIndex(wptr!, index: index); + return monero.Subaddress_getAll_byIndex(subaddressAccount!, index: index); }); } void addSubaddressSync({required int accountIndex, required String label}) { monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex, label: label); + refreshSubaddresses(accountIndex: accountIndex); } void setLabelForSubaddressSync( diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index e543a131a3..79b0677684 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; import 'package:monero/monero.dart' as monero; @@ -13,9 +14,10 @@ bool isNewTransactionExist() => false; String getFilename() => monero.Wallet_filename(wptr!); +// TODO(mrcyjanek): Cake polyseed support String getSeed() => monero.Wallet_seed(wptr!, seedOffset: ''); -String getAddress({int accountIndex = 0, int addressIndex = 0}) => monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); +String getAddress({int accountIndex = 0, int addressIndex = 1}) => monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); int getFullBalance({int accountIndex = 0}) => monero.Wallet_balance(wptr!, accountIndex: accountIndex); @@ -34,7 +36,16 @@ bool setupNodeSync( bool useSSL = false, bool isLightWallet = false, String? socksProxyAddress}) { - + print(''' +{ + wptr!, + daemonAddress: $address, + useSsl: $useSSL, + proxyAddress: $socksProxyAddress ?? '', + daemonUsername: $login ?? '', + daemonPassword: $password ?? '' +} +'''); monero.Wallet_init( wptr!, daemonAddress: address, @@ -49,8 +60,10 @@ bool setupNodeSync( final status = monero.Wallet_status(wptr!); - if (status == 0) { - throw SetupWalletException(message: monero.Wallet_errorString(wptr!)); + if (status != 0) { + final error = monero.Wallet_errorString(wptr!); + print("error: $error"); + throw SetupWalletException(message: error); } return status == 0; @@ -81,7 +94,6 @@ void setPasswordSync(String password) { } void closeCurrentWallet() { - monero.Wallet_store(wptr!); monero.Wallet_stop(wptr!); } diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 5913b25650..84860e642f 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -26,12 +26,13 @@ void createWalletSync( required String password, required String language, int nettype = 0}) { - wptr = monero.WalletManager_createWallet(wmPtr, path: path, password: password, language: language); + wptr = monero.WalletManager_createWallet(wmPtr, path: path, password: password, language: language, networkType: 0); final status = monero.Wallet_status(wptr!); if (status != 0) { throw WalletCreationException(message: monero.Wallet_errorString(wptr!)); } + monero.Wallet_store(wptr!, path: path); // is the line below needed? // setupNodeSync(address: "node.moneroworld.com:18089"); @@ -47,10 +48,6 @@ void restoreWalletFromSeedSync( required String seed, int nettype = 0, int restoreHeight = 0}) { - final pathPointer = path.toNativeUtf8(); - final passwordPointer = password.toNativeUtf8(); - final seedPointer = seed.toNativeUtf8(); - final errorMessagePointer = ''.toNativeUtf8(); wptr = monero.WalletManager_recoveryWallet( wmPtr, @@ -59,12 +56,14 @@ void restoreWalletFromSeedSync( mnemonic: seed, restoreHeight: restoreHeight, seedOffset: '', + networkType: 0, ); final status = monero.Wallet_status(wptr!); if (status != 0) { - throw WalletRestoreFromSeedException(message: monero.Wallet_errorString(wptr!)); + final error = monero.Wallet_errorString(wptr!); + throw WalletRestoreFromSeedException(message: error); } } @@ -92,6 +91,7 @@ void restoreWalletFromKeysSync( addressString: address, viewKeyString: viewKey, spendKeyString: spendKey, + nettype: 0, ); final status = monero.Wallet_status(wptr!); @@ -123,6 +123,7 @@ void restoreWalletFromSpendKeySync( addressString: '', spendKeyString: spendKey, viewKeyString: '', + nettype: 0, ); final status = monero.Wallet_status(wptr!); From dcf61404444da8fb704e8866a2641a35bb87bd5d Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Tue, 16 Apr 2024 17:15:20 +0200 Subject: [PATCH 047/242] multiple wallets new lib minor fixes --- Makefile | 2 +- cw_monero/example/pubspec.lock | 4 +- cw_monero/lib/api/account_list.dart | 12 ++ .../lib/api/structs/pending_transaction.dart | 22 --- cw_monero/lib/api/subaddress_list.dart | 45 ++++-- cw_monero/lib/api/transaction_history.dart | 25 ++- cw_monero/lib/api/types.dart | 153 ------------------ cw_monero/lib/api/wallet.dart | 15 +- cw_monero/lib/api/wallet_manager.dart | 13 +- cw_monero/lib/monero_subaddress_list.dart | 29 ++-- cw_monero/pubspec.lock | 4 +- cw_monero/pubspec.yaml | 2 +- 12 files changed, 96 insertions(+), 230 deletions(-) delete mode 100644 cw_monero/lib/api/types.dart diff --git a/Makefile b/Makefile index f8205ab9d5..3eaef2370f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # TODO(mrcyjanek): Cleanup, this is borrowed from unnamed_monero_wallet repo. -MONERO_C_TAG=v0.18.3.3-RC21 +MONERO_C_TAG=v0.18.3.3-RC27 LIBCPP_SHARED_SO_TAG=latest-RC1 LIBCPP_SHARED_SO_NDKVERSION=r17c diff --git a/cw_monero/example/pubspec.lock b/cw_monero/example/pubspec.lock index e2dd94d7a6..f7aa1e1049 100644 --- a/cw_monero/example/pubspec.lock +++ b/cw_monero/example/pubspec.lock @@ -245,8 +245,8 @@ packages: dependency: transitive description: path: "." - ref: master - resolved-ref: "08c5a32cbcf1f04dbae5826c83abda8fb0dbdcce" + ref: ada81417cb53d8b0e34022df1dd3d9dc1fe62ab5 + resolved-ref: ada81417cb53d8b0e34022df1dd3d9dc1fe62ab5 url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart" source: git version: "0.0.0" diff --git a/cw_monero/lib/api/account_list.dart b/cw_monero/lib/api/account_list.dart index ae94d16ea5..1998966315 100644 --- a/cw_monero/lib/api/account_list.dart +++ b/cw_monero/lib/api/account_list.dart @@ -2,6 +2,18 @@ import 'package:cw_monero/api/wallet.dart'; import 'package:monero/monero.dart' as monero; monero.wallet? wptr = null; + +int _wlptrForW = 0; +monero.WalletListener? _wlptr = null; + +monero.WalletListener getWlptr() { + if (wptr!.address == _wlptrForW) return _wlptr!; + _wlptrForW = wptr!.address; + _wlptr = monero.MONERO_cw_getWalletListener(wptr!); + return _wlptr!; +} + + monero.SubaddressAccount? subaddressAccount; bool isUpdating = false; diff --git a/cw_monero/lib/api/structs/pending_transaction.dart b/cw_monero/lib/api/structs/pending_transaction.dart index 656ed333f1..dc5fbddd04 100644 --- a/cw_monero/lib/api/structs/pending_transaction.dart +++ b/cw_monero/lib/api/structs/pending_transaction.dart @@ -1,25 +1,3 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class PendingTransactionRaw extends Struct { - @Int64() - external int amount; - - @Int64() - external int fee; - - external Pointer hash; - - external Pointer hex; - - external Pointer txKey; - - String getHash() => hash.toDartString(); - - String getHex() => hex.toDartString(); - - String getKey() => txKey.toDartString(); -} class PendingTransactionDescription { PendingTransactionDescription({ diff --git a/cw_monero/lib/api/subaddress_list.dart b/cw_monero/lib/api/subaddress_list.dart index a172b93c44..57edea76ed 100644 --- a/cw_monero/lib/api/subaddress_list.dart +++ b/cw_monero/lib/api/subaddress_list.dart @@ -1,14 +1,23 @@ + import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/wallet.dart'; import 'package:monero/monero.dart' as monero; bool isUpdating = false; -monero.Subaddress? subaddressPtr = null; + +class SubaddressInfoMetadata { + SubaddressInfoMetadata({ + required this.accountIndex, + }); + int accountIndex; +} + +SubaddressInfoMetadata? subaddress = null; + void refreshSubaddresses({required int accountIndex}) { try { isUpdating = true; - subaddressPtr = monero.Wallet_subaddress(wptr!); - monero.Subaddress_refresh(subaddressPtr!,accountIndex: accountIndex, label: ''); + subaddress = SubaddressInfoMetadata(accountIndex: accountIndex); isUpdating = false; } catch (e) { isUpdating = false; @@ -16,17 +25,29 @@ void refreshSubaddresses({required int accountIndex}) { } } -List getAllSubaddresses() { - monero.Subaddress_refresh(subaddressPtr!, - accountIndex: 0, - label: '' // BUG: by me (mrcyjanek), it isn't used, will remove. - ); - final size = monero.Subaddress_getAll_size(subaddressPtr!); - +class Subaddress { + Subaddress({ + required this.addressIndex, + required this.accountIndex, + }); + String get address => monero.Wallet_address( + wptr!, + accountIndex: accountIndex, + addressIndex: addressIndex, + ); + final int addressIndex; + final int accountIndex; + String get label => monero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); +} +List getAllSubaddresses() { + final size = monero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex); return List.generate(size, (index) { - return monero.Subaddress_getAll_byIndex(subaddressAccount!, index: index); - }); + return Subaddress( + accountIndex: subaddress!.accountIndex, + addressIndex: index, + ); + }).reversed.toList(); } void addSubaddressSync({required int accountIndex, required String label}) { diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 4dff7dfe3a..020b199814 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -1,10 +1,8 @@ -import 'dart:ffi'; import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart'; import 'package:cw_monero/api/monero_output.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; -import 'package:ffi/ffi.dart'; import 'package:monero/monero.dart' as monero; @@ -39,16 +37,6 @@ PendingTransactionDescription createTransactionSync( String? amount, int accountIndex = 0, List preferredInputs = const []}) { - final amountPointer = amount != null ? amount.toNativeUtf8() : nullptr; - - final int preferredInputsSize = preferredInputs.length; - final List> preferredInputsPointers = - preferredInputs.map((output) => output.toNativeUtf8()).toList(); - final Pointer> preferredInputsPointerPointer = calloc(preferredInputsSize); - - for (int i = 0; i < preferredInputsSize; i++) { - preferredInputsPointerPointer[i] = preferredInputsPointers[i]; - } final amt = amount == null ? 0 : monero.Wallet_amountFromString(amount); final pendingTx = monero.Wallet_createTransaction( @@ -74,12 +62,17 @@ PendingTransactionDescription createTransactionSync( throw CreationTransactionException(message: message); } + final rAmt = monero.PendingTransaction_amount(pendingTx); + final rFee = monero.PendingTransaction_fee(pendingTx); + final rHash = monero.PendingTransaction_txid(pendingTx, ''); + final rTxKey = rHash; + return PendingTransactionDescription( - amount: monero.PendingTransaction_amount(wptr!), - fee: monero.PendingTransaction_fee(wptr!), - hash: monero.PendingTransaction_txid(wptr!, ''), + amount: rAmt, + fee: rFee, + hash: rHash, hex: '', - txKey: monero.PendingTransaction_txid(wptr!, ''), + txKey: rTxKey, pointerAddress: pendingTx.address, ); } diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart deleted file mode 100644 index 40a1e03213..0000000000 --- a/cw_monero/lib/api/types.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'dart:ffi'; -import 'package:cw_monero/api/structs/coins_info_row.dart'; -import 'package:cw_monero/api/structs/pending_transaction.dart'; -import 'package:cw_monero/api/structs/transaction_info_row.dart'; -import 'package:cw_monero/api/structs/ut8_box.dart'; -import 'package:ffi/ffi.dart'; - -typedef CreateWallet = int Function( - Pointer, Pointer, Pointer, int, Pointer); - -typedef RestoreWalletFromSeed = int Function( - Pointer, Pointer, Pointer, int, int, Pointer); - -typedef RestoreWalletFromKeys = int Function(Pointer, Pointer, - Pointer, Pointer, Pointer, Pointer, int, int, Pointer); - -typedef RestoreWalletFromSpendKey = int Function(Pointer, Pointer, Pointer, - Pointer, Pointer, int, int, Pointer); - -typedef IsWalletExist = int Function(Pointer); - -typedef LoadWallet = int Function(Pointer, Pointer, int); - -typedef ErrorString = Pointer Function(); - -typedef GetFilename = Pointer Function(); - -typedef GetSeed = Pointer Function(); - -typedef GetAddress = Pointer Function(int, int); - -typedef GetFullBalance = int Function(int); - -typedef GetUnlockedBalance = int Function(int); - -typedef GetCurrentHeight = int Function(); - -typedef GetNodeHeight = int Function(); - -typedef IsConnected = int Function(); - -typedef SetupNode = int Function( - Pointer, Pointer?, Pointer?, int, int, Pointer?, Pointer); - -typedef StartRefresh = void Function(); - -typedef ConnectToNode = int Function(); - -typedef SetRefreshFromBlockHeight = void Function(int); - -typedef SetRecoveringFromSeed = void Function(int); - -typedef Store = void Function(Pointer); - -typedef SetPassword = int Function(Pointer password, Pointer error); - -typedef SetListener = void Function(); - -typedef GetSyncingHeight = int Function(); - -typedef IsNeededToRefresh = int Function(); - -typedef IsNewTransactionExist = int Function(); - -typedef SubaddressSize = int Function(); - -typedef SubaddressRefresh = void Function(int); - -typedef SubaddressGetAll = Pointer Function(); - -typedef SubaddressAddNew = void Function(int accountIndex, Pointer label); - -typedef SubaddressSetLabel = void Function( - int accountIndex, int addressIndex, Pointer label); - -typedef AccountSize = int Function(); - -typedef AccountRefresh = void Function(); - -typedef AccountGetAll = Pointer Function(); - -typedef AccountAddNew = void Function(Pointer label); - -typedef AccountSetLabel = void Function(int accountIndex, Pointer label); - -typedef TransactionsRefresh = void Function(); - -typedef GetTransaction = Pointer Function(Pointer txId); - -typedef GetTxKey = Pointer? Function(Pointer txId); - -typedef TransactionsCount = int Function(); - -typedef TransactionsGetAll = Pointer Function(); - -typedef TransactionCreate = int Function( - Pointer address, - Pointer paymentId, - Pointer amount, - int priorityRaw, - int subaddrAccount, - Pointer> preferredInputs, - int preferredInputsSize, - Pointer error, - Pointer pendingTransaction); - -typedef TransactionCreateMultDest = int Function( - Pointer> addresses, - Pointer paymentId, - Pointer> amounts, - int size, - int priorityRaw, - int subaddrAccount, - Pointer> preferredInputs, - int preferredInputsSize, - Pointer error, - Pointer pendingTransaction); - -typedef TransactionCommit = int Function(Pointer, Pointer); - -typedef SecretViewKey = Pointer Function(); - -typedef PublicViewKey = Pointer Function(); - -typedef SecretSpendKey = Pointer Function(); - -typedef PublicSpendKey = Pointer Function(); - -typedef CloseCurrentWallet = void Function(); - -typedef OnStartup = void Function(); - -typedef RescanBlockchainAsync = void Function(); - -typedef GetSubaddressLabel = Pointer Function( - int accountIndex, - int addressIndex); - -typedef SetTrustedDaemon = void Function(int); - -typedef TrustedDaemon = int Function(); - -typedef RefreshCoins = void Function(int); - -typedef CoinsCount = int Function(); - -typedef GetCoin = Pointer Function(int); - -typedef FreezeCoin = void Function(int); - -typedef ThawCoin = void Function(int); - -typedef SignMessage = Pointer Function(Pointer, Pointer); diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 79b0677684..0f354bea9b 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -6,12 +6,19 @@ import 'package:monero/monero.dart' as monero; int _boolToInt(bool value) => value ? 1 : 0; -int getSyncingHeight() => monero.Wallet_blockChainHeight(wptr!); +int getSyncingHeight() => monero.MONERO_cw_WalletListener_height(getWlptr()); -bool isNeededToRefresh() => false; // TODO(mrcyjanek): ? - -bool isNewTransactionExist() => false; +bool isNeededToRefresh() { + final ret = monero.MONERO_cw_WalletListener_isNeedToRefresh(getWlptr()); + monero.MONERO_cw_WalletListener_resetNeedToRefresh(getWlptr()); + return ret; +} +bool isNewTransactionExist() { + final ret = monero.MONERO_cw_WalletListener_isNewTransactionExist(getWlptr()); + monero.MONERO_cw_WalletListener_resetIsNewTransactionExist(getWlptr()); + return ret; +} String getFilename() => monero.Wallet_filename(wptr!); // TODO(mrcyjanek): Cake polyseed support diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 84860e642f..aeacf27691 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -1,4 +1,6 @@ +import 'dart:ffi'; + import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; @@ -7,7 +9,6 @@ import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart import 'package:cw_monero/api/wallet.dart'; import 'package:ffi/ffi.dart'; import 'package:monero/monero.dart' as monero; -import 'dart:ffi'; monero.WalletManager? _wmPtr; final monero.WalletManager wmPtr = Pointer.fromAddress((() { @@ -135,12 +136,20 @@ void restoreWalletFromSpendKeySync( storeSync(); } +String _lastOpenedWallet = ""; + void loadWallet({ required String path, required String password, int nettype = 0}) { try { - wptr ??= monero.WalletManager_openWallet(wmPtr, path: path, password: password); + if (wptr == null || path != _lastOpenedWallet) { + if (wptr != null) { + monero.Wallet_store(wptr!); + } + wptr = monero.WalletManager_openWallet(wmPtr, path: path, password: password); + _lastOpenedWallet = path; + } } catch (e) { print(e); } diff --git a/cw_monero/lib/monero_subaddress_list.dart b/cw_monero/lib/monero_subaddress_list.dart index 2d3ecdb7ea..676a9536cb 100644 --- a/cw_monero/lib/monero_subaddress_list.dart +++ b/cw_monero/lib/monero_subaddress_list.dart @@ -1,9 +1,8 @@ -import 'package:flutter/services.dart'; -import 'package:mobx/mobx.dart'; +import 'package:cw_core/subaddress.dart'; import 'package:cw_monero/api/coins_info.dart'; import 'package:cw_monero/api/subaddress_list.dart' as subaddress_list; -import 'package:cw_core/subaddress.dart'; -import 'package:monero/monero.dart' as monero; +import 'package:flutter/services.dart'; +import 'package:mobx/mobx.dart'; part 'monero_subaddress_list.g.dart'; @@ -51,10 +50,10 @@ abstract class MoneroSubaddressListBase with Store { subaddresses = [primary] + rest.toList(); } - return subaddresses.map((subaddressRow) { - final label = monero.SubaddressRow_getLabel(subaddressRow); - final id = monero.SubaddressRow_getRowId(subaddressRow); - final address = monero.SubaddressRow_getAddress(subaddressRow); + return subaddresses.map((s) { + final address = s.address; + final label = s.label; + final id = s.addressIndex; final hasDefaultAddressName = label.toLowerCase() == 'Primary account'.toLowerCase() || label.toLowerCase() == 'Untitled account'.toLowerCase(); @@ -125,7 +124,7 @@ abstract class MoneroSubaddressListBase with Store { Future> _getAllUnusedAddresses( {required int accountIndex, required String label}) async { final allAddresses = subaddress_list.getAllSubaddresses(); - final lastAddress = monero.SubaddressRow_getAddress(allAddresses.last); + final lastAddress = allAddresses.last.address; if (allAddresses.isEmpty || _usedAddresses.contains(lastAddress)) { final isAddressUnused = await _newSubaddress(accountIndex: accountIndex, label: label); if (!isAddressUnused) { @@ -134,10 +133,10 @@ abstract class MoneroSubaddressListBase with Store { } return allAddresses - .map((subaddressRow) { - final id = monero.SubaddressRow_getRowId(subaddressRow); - final address = monero.SubaddressRow_getAddress(subaddressRow); - final label = monero.SubaddressRow_getLabel(subaddressRow); + .map((s) { + final id = s.addressIndex; + final address = s.address; + final label = s.label; return Subaddress( id: id, address: address, @@ -154,8 +153,8 @@ abstract class MoneroSubaddressListBase with Store { return subaddress_list .getAllSubaddresses() - .where((subaddressRow) { - final address = monero.SubaddressRow_getAddress(subaddressRow); + .where((s) { + final address = s.address; return !_usedAddresses.contains(address); }) .isNotEmpty; diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 0faf7dab8a..4ce0f77761 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -414,8 +414,8 @@ packages: dependency: "direct main" description: path: "." - ref: master - resolved-ref: "08c5a32cbcf1f04dbae5826c83abda8fb0dbdcce" + ref: ada81417cb53d8b0e34022df1dd3d9dc1fe62ab5 + resolved-ref: ada81417cb53d8b0e34022df1dd3d9dc1fe62ab5 url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart" source: git version: "0.0.0" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index e94b650674..6527dbee5c 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: monero: git: url: https://git.mrcyjanek.net/mrcyjanek/monero.dart - ref: master + ref: ada81417cb53d8b0e34022df1dd3d9dc1fe62ab5 dev_dependencies: flutter_test: From 5b42130e858ef7397aac3c1f2ceedbfa200ed936 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Sat, 20 Apr 2024 19:19:13 +0200 Subject: [PATCH 048/242] other fixes from monero.dart and monero_c --- Makefile | 2 +- cw_monero/example/pubspec.lock | 4 +- cw_monero/lib/api/transaction_history.dart | 141 +++++++++------------ cw_monero/lib/api/wallet.dart | 26 ++-- cw_monero/lib/api/wallet_manager.dart | 42 +++--- cw_monero/lib/monero_wallet.dart | 27 ++-- cw_monero/pubspec.lock | 4 +- cw_monero/pubspec.yaml | 4 +- lib/main.dart | 53 ++++---- 9 files changed, 153 insertions(+), 150 deletions(-) diff --git a/Makefile b/Makefile index 3eaef2370f..db18b09490 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # TODO(mrcyjanek): Cleanup, this is borrowed from unnamed_monero_wallet repo. -MONERO_C_TAG=v0.18.3.3-RC27 +MONERO_C_TAG=v0.18.3.3-RC31 LIBCPP_SHARED_SO_TAG=latest-RC1 LIBCPP_SHARED_SO_NDKVERSION=r17c diff --git a/cw_monero/example/pubspec.lock b/cw_monero/example/pubspec.lock index f7aa1e1049..188b1c52e6 100644 --- a/cw_monero/example/pubspec.lock +++ b/cw_monero/example/pubspec.lock @@ -245,8 +245,8 @@ packages: dependency: transitive description: path: "." - ref: ada81417cb53d8b0e34022df1dd3d9dc1fe62ab5 - resolved-ref: ada81417cb53d8b0e34022df1dd3d9dc1fe62ab5 + ref: e2149153ecbaa6cc4d7d3970d8fa8ce1099b63af + resolved-ref: e2149153ecbaa6cc4d7d3970d8fa8ce1099b63af url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart" source: git version: "0.0.0" diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 020b199814..8e69efca01 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -1,9 +1,14 @@ +import 'dart:ffi'; +import 'dart:isolate'; + import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart'; import 'package:cw_monero/api/monero_output.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; +import 'package:ffi/ffi.dart'; import 'package:monero/monero.dart' as monero; +import 'package:monero/src/generated_bindings_monero.g.dart' as monero_gen; String getTxKey(String txId) { @@ -30,31 +35,48 @@ Transaction getTransaction(String txId) { return Transaction(txInfo: monero.TransactionHistory_transactionById(txhistory!, txid: txId)); } -PendingTransactionDescription createTransactionSync( +Future createTransactionSync( {required String address, required String paymentId, required int priorityRaw, String? amount, int accountIndex = 0, - List preferredInputs = const []}) { + List preferredInputs = const []}) async { final amt = amount == null ? 0 : monero.Wallet_amountFromString(amount); - final pendingTx = monero.Wallet_createTransaction( - wptr!, - dst_addr: address, - payment_id: paymentId, - amount: amt, - mixin_count: 1, - pendingTransactionPriority: priorityRaw, - subaddr_account: accountIndex, - preferredInputs: preferredInputs, - ); + + final address_ = address.toNativeUtf8(); + final paymentId_ = paymentId.toNativeUtf8(); + final preferredInputs_ = preferredInputs.join(monero.defaultSeparatorStr).toNativeUtf8(); + + final waddr = wptr!.address; + final addraddr = address_.address; + final paymentIdAddr = paymentId_.address; + final preferredInputsAddr = preferredInputs_.address; + final spaddr = monero.defaultSeparator.address; + final pendingTx = Pointer.fromAddress(await Isolate.run(() { + final tx = monero_gen.MoneroC(DynamicLibrary.open(monero.libPath)).MONERO_Wallet_createTransaction( + Pointer.fromAddress(waddr), + Pointer.fromAddress(addraddr).cast(), + Pointer.fromAddress(paymentIdAddr).cast(), + amt, + 1, + priorityRaw, + accountIndex, + Pointer.fromAddress(preferredInputsAddr).cast(), + Pointer.fromAddress(spaddr), + ); + return tx.address; + })); + calloc.free(address_); + calloc.free(paymentId_); + calloc.free(preferredInputs_); final String? error = (() { - final status = monero.Wallet_status(wptr!); + final status = monero.PendingTransaction_status(pendingTx); if (status == 0) { return null; } - return monero.Wallet_errorString(wptr!); + return monero.PendingTransaction_errorString(pendingTx); })(); if (error != null) { @@ -83,68 +105,27 @@ PendingTransactionDescription createTransactionMultDestSync( required int priorityRaw, int accountIndex = 0, List preferredInputs = const []}) { - // final int size = outputs.length; - // final List> addressesPointers = - // outputs.map((output) => output.address.toNativeUtf8()).toList(); - // final Pointer> addressesPointerPointer = calloc(size); - // final List> amountsPointers = - // outputs.map((output) => output.amount.toNativeUtf8()).toList(); - // final Pointer> amountsPointerPointer = calloc(size); - - // for (int i = 0; i < size; i++) { - // addressesPointerPointer[i] = addressesPointers[i]; - // amountsPointerPointer[i] = amountsPointers[i]; - // } - - // final int preferredInputsSize = preferredInputs.length; - // final List> preferredInputsPointers = - // preferredInputs.map((output) => output.toNativeUtf8()).toList(); - // final Pointer> preferredInputsPointerPointer = calloc(preferredInputsSize); - - // for (int i = 0; i < preferredInputsSize; i++) { - // preferredInputsPointerPointer[i] = preferredInputsPointers[i]; - // } - - // final paymentIdPointer = paymentId.toNativeUtf8(); - // final errorMessagePointer = calloc(); - // final pendingTransactionRawPointer = calloc(); - // final created = transactionCreateMultDestNative( - // addressesPointerPointer, - // paymentIdPointer, - // amountsPointerPointer, - // size, - // priorityRaw, - // accountIndex, - // preferredInputsPointerPointer, - // preferredInputsSize, - // errorMessagePointer, - // pendingTransactionRawPointer) != - // 0; - - // calloc.free(addressesPointerPointer); - // calloc.free(amountsPointerPointer); - // calloc.free(preferredInputsPointerPointer); - - // addressesPointers.forEach((element) => calloc.free(element)); - // amountsPointers.forEach((element) => calloc.free(element)); - // preferredInputsPointers.forEach((element) => calloc.free(element)); - - // calloc.free(paymentIdPointer); - - // if (!created) { - // final message = errorMessagePointer.ref.getValue(); - // calloc.free(errorMessagePointer); - // throw CreationTransactionException(message: message); - // } - - // return PendingTransactionDescription( - // amount: pendingTransactionRawPointer.ref.amount, - // fee: pendingTransactionRawPointer.ref.fee, - // hash: pendingTransactionRawPointer.ref.getHash(), - // hex: pendingTransactionRawPointer.ref.getHex(), - // txKey: pendingTransactionRawPointer.ref.getKey(), - // pointerAddress: pendingTransactionRawPointer.address); - throw CreationTransactionException(message: "Unimplemented in monero_c"); + + final txptr = monero.Wallet_createTransactionMultDest( + wptr!, + dstAddr: outputs.map((e) => e.address).toList(), + isSweepAll: false, + amounts: outputs.map((e) => monero.Wallet_amountFromString(e.amount)).toList(), + mixinCount: 0, + pendingTransactionPriority: priorityRaw, + subaddr_account: accountIndex, + ); + if (monero.PendingTransaction_status(txptr) != 0) { + throw CreationTransactionException(message: monero.PendingTransaction_errorString(txptr)); + } + return PendingTransactionDescription( + amount: monero.PendingTransaction_amount(txptr), + fee: monero.PendingTransaction_fee(txptr), + hash: monero.PendingTransaction_txid(txptr, ''), + hex: monero.PendingTransaction_txid(txptr, ''), + txKey: monero.PendingTransaction_txid(txptr, ''), + pointerAddress: txptr.address, + ); } void commitTransactionFromPointerAddress({required int address}) => @@ -168,7 +149,7 @@ void commitTransaction({required monero.PendingTransaction transactionPointer}) } } -PendingTransactionDescription _createTransactionSync(Map args) { +Future _createTransactionSync(Map args) async { final address = args['address'] as String; final paymentId = args['paymentId'] as String; final amount = args['amount'] as String?; @@ -244,6 +225,7 @@ class Transaction { final int confirmations; late final bool isPending = confirmations < 10; final int blockheight; + final int addressIndex = 0; final int accountIndex; final String paymentId; final int amount; @@ -251,6 +233,7 @@ class Transaction { late DateTime timeStamp; late final bool isConfirmed = !isPending; final String hash; + final String key; Map toJson() { return { @@ -263,6 +246,7 @@ class Transaction { "isPending": isPending, "blockheight": blockheight, "accountIndex": accountIndex, + "addressIndex": addressIndex, "paymentId": paymentId, "amount": amount, "isSpend": isSpend, @@ -291,5 +275,6 @@ class Transaction { blockheight = monero.TransactionInfo_blockHeight(txInfo), confirmations = monero.TransactionInfo_confirmations(txInfo), fee = monero.TransactionInfo_fee(txInfo), - description = monero.TransactionInfo_description(txInfo); + description = monero.TransactionInfo_description(txInfo), + key = monero.Wallet_getTxKey(wptr!, txid: monero.TransactionInfo_hash(txInfo)); } \ No newline at end of file diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 0f354bea9b..a40b987ba7 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -4,10 +4,12 @@ import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; import 'package:monero/monero.dart' as monero; -int _boolToInt(bool value) => value ? 1 : 0; - -int getSyncingHeight() => monero.MONERO_cw_WalletListener_height(getWlptr()); - +int getSyncingHeight() { + // final height = monero.MONERO_cw_WalletListener_height(getWlptr()); + final h2 = monero.Wallet_blockChainHeight(wptr!); + // print("height: $height / $h2"); + return h2; +} bool isNeededToRefresh() { final ret = monero.MONERO_cw_WalletListener_isNeedToRefresh(getWlptr()); monero.MONERO_cw_WalletListener_resetNeedToRefresh(getWlptr()); @@ -22,7 +24,14 @@ bool isNewTransactionExist() { String getFilename() => monero.Wallet_filename(wptr!); // TODO(mrcyjanek): Cake polyseed support -String getSeed() => monero.Wallet_seed(wptr!, seedOffset: ''); +String getSeed() { + final legacy = monero.Wallet_seed(wptr!, seedOffset: ''); + final polyseed = monero.Wallet_getPolyseed(wptr!, passphrase: ''); + if (polyseed == "") { + return legacy; + } + return polyseed; +} String getAddress({int accountIndex = 0, int addressIndex = 1}) => monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); @@ -62,8 +71,6 @@ bool setupNodeSync( daemonPassword: password ?? '' ); // monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true); - monero.Wallet_startRefresh(wptr!); - monero.Wallet_refreshAsync(wptr!); final status = monero.Wallet_status(wptr!); @@ -76,7 +83,10 @@ bool setupNodeSync( return status == 0; } -void startRefreshSync() {} +void startRefreshSync() { + monero.Wallet_refreshAsync(wptr!); + monero.Wallet_startRefresh(wptr!); +} Future connectToNode() async { return true; diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index aeacf27691..6e70427932 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -7,7 +7,6 @@ import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart'; import 'package:cw_monero/api/wallet.dart'; -import 'package:ffi/ffi.dart'; import 'package:monero/monero.dart' as monero; monero.WalletManager? _wmPtr; @@ -77,13 +76,7 @@ void restoreWalletFromKeysSync( required String spendKey, int nettype = 0, int restoreHeight = 0}) { - final pathPointer = path.toNativeUtf8(); - final passwordPointer = password.toNativeUtf8(); - final languagePointer = language.toNativeUtf8(); - final addressPointer = address.toNativeUtf8(); - final viewKeyPointer = viewKey.toNativeUtf8(); - final spendKeyPointer = spendKey.toNativeUtf8(); - final errorMessagePointer = ''.toNativeUtf8(); + wptr = monero.WalletManager_createWalletFromKeys( wmPtr, path: path, @@ -109,28 +102,35 @@ void restoreWalletFromSpendKeySync( required String spendKey, int nettype = 0, int restoreHeight = 0}) { - final pathPointer = path.toNativeUtf8(); - final passwordPointer = password.toNativeUtf8(); - final seedPointer = seed.toNativeUtf8(); - final languagePointer = language.toNativeUtf8(); - final spendKeyPointer = spendKey.toNativeUtf8(); - final errorMessagePointer = ''.toNativeUtf8(); - wptr = monero.WalletManager_createWalletFromKeys( + // wptr = monero.WalletManager_createWalletFromKeys( + // wmPtr, + // path: path, + // password: password, + // restoreHeight: restoreHeight, + // addressString: '', + // spendKeyString: spendKey, + // viewKeyString: '', + // nettype: 0, + // ); + + wptr = monero.WalletManager_createWalletFromPolyseed( wmPtr, path: path, password: password, + mnemonic: seed, + seedOffset: '', + newWallet: false, restoreHeight: restoreHeight, - addressString: '', - spendKeyString: spendKey, - viewKeyString: '', - nettype: 0, + kdfRounds: 1, ); final status = monero.Wallet_status(wptr!); - if (status == 0) { - throw WalletRestoreFromKeysException(message: monero.Wallet_errorString(wptr!)); + if (status != 0) { + final err = monero.Wallet_errorString(wptr!); + print("err: $err"); + throw WalletRestoreFromKeysException(message: err); } storeSync(); diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index e2e7196ba4..86c18d4709 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -552,16 +552,23 @@ abstract class MoneroWalletBase List _getAllTransactionsOfAccount(int? accountIndex) => transaction_history .getAllTransactions() .map((row) => MoneroTransactionInfo( - row.hash, - row.blockheight, - row.isSpend ? TransactionDirection.outgoing : TransactionDirection.incoming, - row.timeStamp, - row.isPending, - row.amount, - row.accountIndex, - 0, - row.fee, - row.confirmations)) + row.hash, + row.blockheight, + row.isSpend ? TransactionDirection.outgoing : TransactionDirection.incoming, + row.timeStamp, + row.isPending, + row.amount, + row.accountIndex, + 0, + row.fee, + row.confirmations, + + )..additionalInfo = { + 'key': row.key, + 'accountIndex': row.accountIndex, + 'addressIndex': row.addressIndex + }, + ) .where((element) => element.accountIndex == (accountIndex ?? 0)) .toList(); diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 4ce0f77761..86b1ef5bbf 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -414,8 +414,8 @@ packages: dependency: "direct main" description: path: "." - ref: ada81417cb53d8b0e34022df1dd3d9dc1fe62ab5 - resolved-ref: ada81417cb53d8b0e34022df1dd3d9dc1fe62ab5 + ref: e2149153ecbaa6cc4d7d3970d8fa8ce1099b63af + resolved-ref: e2149153ecbaa6cc4d7d3970d8fa8ce1099b63af url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart" source: git version: "0.0.0" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 6527dbee5c..26ae7cc501 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -6,7 +6,7 @@ author: Cake Wallet homepage: https://cakewallet.com environment: - sdk: ">=2.17.5 <3.0.0" + sdk: ">=2.19.0 <3.0.0" flutter: ">=1.20.0" dependencies: @@ -25,7 +25,7 @@ dependencies: monero: git: url: https://git.mrcyjanek.net/mrcyjanek/monero.dart - ref: ada81417cb53d8b0e34022df1dd3d9dc1fe62ab5 + ref: e2149153ecbaa6cc4d7d3970d8fa8ce1099b63af dev_dependencies: flutter_test: diff --git a/lib/main.dart b/lib/main.dart index b80c9eb857..3a2340024c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,46 +1,47 @@ import 'dart:async'; + import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; +import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/entities/contact.dart'; +import 'package:cake_wallet/entities/default_settings_migration.dart'; +import 'package:cake_wallet/entities/get_encryption_key.dart'; import 'package:cake_wallet/entities/language_service.dart'; -import 'package:cake_wallet/buy/order.dart'; +import 'package:cake_wallet/entities/template.dart'; +import 'package:cake_wallet/entities/transaction_description.dart'; +import 'package:cake_wallet/exchange/exchange_template.dart'; +import 'package:cake_wallet/exchange/trade.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/locales/locale.dart'; +import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/reactions/bootstrap.dart'; +import 'package:cake_wallet/router.dart' as Router; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/root/root.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; -import 'package:cw_core/address_info.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cw_core/address_info.dart'; +import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/hive_type_ids.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; -import 'package:cake_wallet/di.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:cake_wallet/themes/theme_base.dart'; -import 'package:cake_wallet/router.dart' as Router; -import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/reactions/bootstrap.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/store/authentication_store.dart'; -import 'package:cake_wallet/entities/transaction_description.dart'; -import 'package:cake_wallet/entities/get_encryption_key.dart'; -import 'package:cake_wallet/entities/contact.dart'; -import 'package:cw_core/node.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cake_wallet/entities/default_settings_migration.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/entities/template.dart'; -import 'package:cake_wallet/exchange/trade.dart'; -import 'package:cake_wallet/exchange/exchange_template.dart'; -import 'package:cake_wallet/src/screens/root/root.dart'; import 'package:uni_links/uni_links.dart'; -import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cake_wallet/monero/monero.dart'; -import 'package:cw_core/cake_hive.dart'; final navigatorKey = GlobalKey(); final rootKey = GlobalKey(); From 39845d6904511b3dab587f1066685b0ae01fe862 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 22 Apr 2024 17:03:16 -0300 Subject: [PATCH 049/242] fix: nodes & build --- cw_bitcoin/lib/electrum_wallet.dart | 2 +- cw_bitcoin/pubspec.lock | 36 ++++++++++---------- lib/entities/default_settings_migration.dart | 6 ++++ 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 38d6c5fec6..21ee56307b 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -430,7 +430,7 @@ abstract class ElectrumWalletBase } if (silentPaymentDestinations.isNotEmpty) { - final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); + final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, vinOutpoints: vinOutpoints); final sendingOutputs = spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations); final outputsAdded = []; diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index bd3dd41894..86a2084452 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" asn1lib: dependency: transitive description: @@ -80,7 +80,7 @@ packages: description: path: "." ref: cake-update-v3 - resolved-ref: "3ddad3d1a9b78f49c9ef542962758400315d64a7" + resolved-ref: "2a18ab92a9f7136b76fcd1bf8480eaaa90e0a6b2" url: "https://github.com/cake-tech/bitcoin_base" source: git version: "4.0.0" @@ -154,10 +154,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.9" build_runner_core: dependency: transitive description: @@ -178,10 +178,10 @@ packages: dependency: transitive description: name: built_value - sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.9.1" + version: "8.9.2" characters: dependency: transitive description: @@ -326,10 +326,10 @@ packages: dependency: "direct main" description: name: flutter_mobx - sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4" + sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e" url: "https://pub.dev" source: hosted - version: "2.2.0+2" + version: "2.2.1+1" flutter_test: dependency: "direct dev" description: flutter @@ -339,10 +339,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -483,10 +483,10 @@ packages: dependency: "direct main" description: name: mobx - sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78" + sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e" url: "https://pub.dev" source: hosted - version: "2.3.0+1" + version: "2.3.3+2" mobx_codegen: dependency: "direct dev" description: @@ -587,10 +587,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" url: "https://pub.dev" source: hosted - version: "3.7.4" + version: "3.8.0" pool: dependency: transitive description: @@ -603,10 +603,10 @@ packages: dependency: transitive description: name: provider - sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "6.1.2" pub_semver: dependency: transitive description: @@ -697,7 +697,7 @@ packages: description: path: "." ref: master - resolved-ref: de90b20f4250647d0f55f6bd5e7203710d0d5678 + resolved-ref: "0ac9108db2f475c5b685af9eb9df393dcc978820" url: "https://github.com/rafael-xmr/sp_scanner" source: git version: "0.0.1" diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 882cced1b6..856ac48631 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -822,6 +822,9 @@ Future checkCurrentNodes( final cakeWalletElectrum = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false); await nodeSource.add(cakeWalletElectrum); + final cakeWalletElectrumTestnet = + Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false); + await nodeSource.add(cakeWalletElectrumTestnet); await sharedPreferences.setInt( PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int); } @@ -894,6 +897,9 @@ Future resetBitcoinElectrumServer( if (cakeWalletNode == null) { cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false); + final cakeWalletElectrumTestnet = + Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false); + await nodeSource.add(cakeWalletElectrumTestnet); await nodeSource.add(cakeWalletNode); } From 78f801ed9d9009e17c54038f9930497e339ce9f2 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Tue, 23 Apr 2024 14:31:00 +0200 Subject: [PATCH 050/242] update build scripts fix polyseed --- Makefile | 63 +-------- android/app/src/debug/AndroidManifest.xml | 2 +- cw_monero/example/pubspec.lock | 4 +- cw_monero/lib/api/transaction_history.dart | 3 +- cw_monero/lib/api/wallet.dart | 14 +- cw_monero/lib/api/wallet_manager.dart | 11 +- cw_monero/pubspec.lock | 4 +- cw_monero/pubspec.yaml | 2 +- pubspec_base.yaml | 5 +- run-android.sh | 4 +- scripts/docker/.gitignore | 1 + scripts/docker/Dockerfile | 4 +- scripts/docker/build_haven.sh | 142 ++++++++++----------- scripts/docker/copy_haven_deps.sh | 2 +- scripts/docker/copy_monero_deps.sh | 2 +- scripts/docker/docker-compose.yml | 2 + scripts/docker/entrypoint.sh | 6 + 17 files changed, 111 insertions(+), 160 deletions(-) diff --git a/Makefile b/Makefile index db18b09490..642d4fac66 100644 --- a/Makefile +++ b/Makefile @@ -1,70 +1,9 @@ # TODO(mrcyjanek): Cleanup, this is borrowed from unnamed_monero_wallet repo. -MONERO_C_TAG=v0.18.3.3-RC31 +MONERO_C_TAG=v0.18.3.3-RC35 LIBCPP_SHARED_SO_TAG=latest-RC1 LIBCPP_SHARED_SO_NDKVERSION=r17c -.PHONY: android -android: - ./build_changelog.sh - flutter build apk --split-per-abi --flavor calc --dart-define=libstealth_calculator=true - flutter build apk --split-per-abi --flavor clean --dart-define=libstealth_calculator=false - -.PHONY: linux -linux: - ./build_changelog.sh - flutter build linux - echo https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/${TARGET_TRIPLET}_libwallet2_api_c.so.xz - wget https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/${TARGET_TRIPLET}_libwallet2_api_c.so.xz \ - -O build/linux/${FLUTTER_ARCH}/release/bundle/lib/libwallet2_api_c.so.xz - -rm build/linux/${FLUTTER_ARCH}/release/bundle/lib/libwallet2_api_c.so - unxz build/linux/${FLUTTER_ARCH}/release/bundle/lib/libwallet2_api_c.so.xz - -rm build/linux/${FLUTTER_ARCH}/release/xmruw-linux-${DEBIAN_ARCH}.tar* - (cd build/linux/${FLUTTER_ARCH}/release && cp -a bundle xmruw && tar -cvf xmruw-linux-${DEBIAN_ARCH}.tar xmruw && xz -e xmruw-linux-${DEBIAN_ARCH}.tar) - - -.PHONY: linux_debug_lib -linux_debug_lib: - wget https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/${shell gcc -dumpmachine}_libwallet2_api_c.so.xz \ - -O build/linux/${FLUTTER_ARCH}/debug/bundle/lib/libwallet2_api_c.so.xz - -rm build/linux/${FLUTTER_ARCH}/debug/bundle/lib/libwallet2_api_c.so - unxz build/linux/${FLUTTER_ARCH}/debug/bundle/lib/libwallet2_api_c.so.xz - -deb: - dart pub global activate --source git https://github.com/tomekit/flutter_to_debian.git - cat debian/debian.yaml.txt | sed 's/x64/${FLUTTER_ARCH}/g' | sed 's/amd64/${DEBIAN_ARCH}/g' > debian/debian.yaml - ${HOME}/.pub-cache/bin/flutter_to_debian - -.PHONY: dev -dev: libs - -dev: -lib/const/resource.g.dart: - dart pub global activate flutter_asset_generator - timeout 15 ${HOME}/.pub-cache/bin/fgen || true - mv lib/const/resource.dart lib/const/resource.g.dart -.PHONY: lib/const/resource.g.dart - -.PHONY: sailfishos -sailfishos: - ./build_changelog.sh - bash ./elinux/sailfish_build.sh - -.PHONY: version -version: - sed -i "s/^version: .*/version: 1.0.0+$(shell git rev-list --count HEAD)/" "pubspec.yaml" - sed -i "s/^ Version: .*/ Version: 1.0.0+$(shell git rev-list --count HEAD)/" "debian/debian.yaml.txt" - sed -i "s/^Version=.*/Version=1.0.0+$(shell git rev-list --count HEAD)/" "debian/gui/xmruw.desktop" - sed -i "s/^Version=.*/Version=1.0.0+$(shell git rev-list --count HEAD)/" "elinux/unnamed-monero-wallet.desktop" - sed -i "s/^Version: .*/Version: 1.0.0+$(shell git rev-list --count HEAD)/" "elinux/sailfishos.spec" - sed -i "s/^Release: .*/Release: $(shell git rev-list --count HEAD)/" "elinux/sailfishos.spec" - sed -i "s/^Version: .*/Version: 1.0.0+$(shell git rev-list --count HEAD)/" "elinux/sailfishos.spec" - sed -i "s/^const xmruwVersion = .*/const xmruwVersion = '$(shell git describe --tags)';/" "lib/helpers/licenses_extra.dart" - -.PHONY: lib/helpers/licenses.g.dart -lib/helpers/licenses.g.dart: - dart pub run flutter_oss_licenses:generate.dart -o lib/helpers/licenses.g.dart - libs: android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so .PHONY: android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so: diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index dfd0d315a6..dc767a55dc 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.cakewallet.cake_wallet"> diff --git a/cw_monero/example/pubspec.lock b/cw_monero/example/pubspec.lock index 188b1c52e6..333c0299b7 100644 --- a/cw_monero/example/pubspec.lock +++ b/cw_monero/example/pubspec.lock @@ -245,8 +245,8 @@ packages: dependency: transitive description: path: "." - ref: e2149153ecbaa6cc4d7d3970d8fa8ce1099b63af - resolved-ref: e2149153ecbaa6cc4d7d3970d8fa8ce1099b63af + ref: "57e075ee67d16aa0f3f75fba67d79529fbc73a6c" + resolved-ref: "57e075ee67d16aa0f3f75fba67d79529fbc73a6c" url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart" source: git version: "0.0.0" diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 8e69efca01..c349dd312b 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -134,10 +134,9 @@ void commitTransactionFromPointerAddress({required int address}) => void commitTransaction({required monero.PendingTransaction transactionPointer}) { final txCommit = monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false); - final status = monero.PendingTransaction_status(transactionPointer.cast()); final String? error = (() { - final status = monero.Wallet_status(wptr!); + final status = monero.PendingTransaction_status(transactionPointer.cast()); if (status == 0) { return null; } diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index a40b987ba7..9e37268adc 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -23,14 +23,18 @@ bool isNewTransactionExist() { } String getFilename() => monero.Wallet_filename(wptr!); -// TODO(mrcyjanek): Cake polyseed support String getSeed() { - final legacy = monero.Wallet_seed(wptr!, seedOffset: ''); + // monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); + final cakepolyseed = monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed"); + if (cakepolyseed != "") { + return cakepolyseed; + } final polyseed = monero.Wallet_getPolyseed(wptr!, passphrase: ''); - if (polyseed == "") { - return legacy; + if (polyseed != "") { + return polyseed; } - return polyseed; + final legacy = monero.Wallet_seed(wptr!, seedOffset: ''); + return legacy; } String getAddress({int accountIndex = 0, int addressIndex = 1}) => monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 6e70427932..e441d74060 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -114,15 +114,14 @@ void restoreWalletFromSpendKeySync( // nettype: 0, // ); - wptr = monero.WalletManager_createWalletFromPolyseed( + wptr = monero.WalletManager_createDeterministicWalletFromSpendKey( wmPtr, path: path, password: password, - mnemonic: seed, - seedOffset: '', - newWallet: false, + language: language, + spendKeyString: spendKey, + newWallet: true, // TODO(mrcyjanek): safe to remove restoreHeight: restoreHeight, - kdfRounds: 1, ); final status = monero.Wallet_status(wptr!); @@ -133,6 +132,8 @@ void restoreWalletFromSpendKeySync( throw WalletRestoreFromKeysException(message: err); } + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); + storeSync(); } diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 86b1ef5bbf..7503cd96da 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -414,8 +414,8 @@ packages: dependency: "direct main" description: path: "." - ref: e2149153ecbaa6cc4d7d3970d8fa8ce1099b63af - resolved-ref: e2149153ecbaa6cc4d7d3970d8fa8ce1099b63af + ref: "57e075ee67d16aa0f3f75fba67d79529fbc73a6c" + resolved-ref: "57e075ee67d16aa0f3f75fba67d79529fbc73a6c" url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart" source: git version: "0.0.0" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 26ae7cc501..7d3b464953 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: monero: git: url: https://git.mrcyjanek.net/mrcyjanek/monero.dart - ref: e2149153ecbaa6cc4d7d3970d8fa8ce1099b63af + ref: 57e075ee67d16aa0f3f75fba67d79529fbc73a6c dev_dependencies: flutter_test: diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 0f395007d4..3ec3e79785 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -111,10 +111,7 @@ dependencies: git: url: https://github.com/cake-tech/bitcoin_base.git ref: cake-update-v2 - monero: - git: - url: https://git.mrcyjanek.net/mrcyjanek/monero.dart - ref: master + dev_dependencies: flutter_test: sdk: flutter diff --git a/run-android.sh b/run-android.sh index dd694267a1..880d86b6f8 100755 --- a/run-android.sh +++ b/run-android.sh @@ -4,7 +4,7 @@ get_current_branch() { if git rev-parse --git-dir > /dev/null 2>&1; then branch=$(git rev-parse --abbrev-ref HEAD) - echo "$branch" + echo "$branch" | tr '-' '_' else echo "Error: Not a git repository." return 1 @@ -27,4 +27,4 @@ if [[ $? -eq 0 ]]; then fi # run the app -flutter run \ No newline at end of file +flutter run diff --git a/scripts/docker/.gitignore b/scripts/docker/.gitignore index ea1472ec1f..c39e9d9f7c 100644 --- a/scripts/docker/.gitignore +++ b/scripts/docker/.gitignore @@ -1 +1,2 @@ output/ +cache/ \ No newline at end of file diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile index 1bcb0464ec..a352cdc71f 100755 --- a/scripts/docker/Dockerfile +++ b/scripts/docker/Dockerfile @@ -4,7 +4,7 @@ LABEL authors="konsti" ENV MONERO_BRANCH=release-v0.18.2.2-android RUN apt-get update && \ echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections && \ - apt-get install -y dialog apt-utils curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang bison + apt-get install -y dialog apt-utils curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang bison ccache RUN mkdir /opt/android/ @@ -56,5 +56,7 @@ RUN ./build_zmq.sh COPY entrypoint.sh /opt/android/cakewallet/ COPY build_monero.sh /opt/android/cakewallet/ COPY copy_monero_deps.sh /opt/android/cakewallet/ +COPY build_haven.sh /opt/android/cakewallet/ +COPY copy_haven_deps.sh /opt/android/cakewallet/ ENTRYPOINT ["./entrypoint.sh"] diff --git a/scripts/docker/build_haven.sh b/scripts/docker/build_haven.sh index 1dc4a6cfd2..1cfb162655 100755 --- a/scripts/docker/build_haven.sh +++ b/scripts/docker/build_haven.sh @@ -1,71 +1,71 @@ -#!/bin/sh -set -x -e - -. ./config.sh -HAVEN_VERSION=tags/v3.0.7 -HAVEN_SRC_DIR=${WORKDIR}/haven - -git clone https://github.com/haven-protocol-org/haven-main.git ${HAVEN_SRC_DIR} -git checkout ${HAVEN_VERSION} -cd $HAVEN_SRC_DIR -git submodule init -git submodule update - -for arch in "aarch" "aarch64" "i686" "x86_64" -do -FLAGS="" -PREFIX=${WORKDIR}/prefix_${arch} -DEST_LIB_DIR=${PREFIX}/lib/haven -DEST_INCLUDE_DIR=${PREFIX}/include/haven -export CMAKE_INCLUDE_PATH="${PREFIX}/include" -export CMAKE_LIBRARY_PATH="${PREFIX}/lib" -ANDROID_STANDALONE_TOOLCHAIN_PATH="${TOOLCHAIN_BASE_DIR}_${arch}" -PATH="${ANDROID_STANDALONE_TOOLCHAIN_PATH}/bin:${ORIGINAL_PATH}" - -mkdir -p $DEST_LIB_DIR -mkdir -p $DEST_INCLUDE_DIR - -case $arch in - "aarch" ) - CLANG=arm-linux-androideabi-clang - CXXLANG=arm-linux-androideabi-clang++ - BUILD_64=OFF - TAG="android-armv7" - ARCH="armv7-a" - ARCH_ABI="armeabi-v7a" - FLAGS="-D CMAKE_ANDROID_ARM_MODE=ON -D NO_AES=true";; - "aarch64" ) - CLANG=aarch64-linux-androideabi-clang - CXXLANG=aarch64-linux-androideabi-clang++ - BUILD_64=ON - TAG="android-armv8" - ARCH="armv8-a" - ARCH_ABI="arm64-v8a";; - "i686" ) - CLANG=i686-linux-androideabi-clang - CXXLANG=i686-linux-androideabi-clang++ - BUILD_64=OFF - TAG="android-x86" - ARCH="i686" - ARCH_ABI="x86";; - "x86_64" ) - CLANG=x86_64-linux-androideabi-clang - CXXLANG=x86_64-linux-androideabi-clang++ - BUILD_64=ON - TAG="android-x86_64" - ARCH="x86-64" - ARCH_ABI="x86_64";; -esac - -cd $HAVEN_SRC_DIR -rm -rf ./build/release -mkdir -p ./build/release -cd ./build/release -CC=${CLANG} CXX=${CXXLANG} cmake -D USE_DEVICE_TREZOR=OFF -D BUILD_GUI_DEPS=1 -D BUILD_TESTS=OFF -D ARCH=${ARCH} -D STATIC=ON -D BUILD_64=${BUILD_64} -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON -D BUILD_TAG=${TAG} -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI=${ARCH_ABI} $FLAGS ../.. - -make wallet_api -j$THREADS -find . -path ./lib -prune -o -name '*.a' -exec cp '{}' lib \; - -cp -r ./lib/* $DEST_LIB_DIR -cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR -done +#!/bin/sh +set -x -e + +. ./config.sh +HAVEN_VERSION=tags/v3.0.7 +HAVEN_SRC_DIR=${WORKDIR}/haven + +git clone https://github.com/haven-protocol-org/haven-main.git ${HAVEN_SRC_DIR} +cd $HAVEN_SRC_DIR +git checkout ${HAVEN_VERSION} +git submodule init +git submodule update + +for arch in "aarch" "aarch64" "i686" "x86_64" +do +FLAGS="" +PREFIX=${WORKDIR}/prefix_${arch} +DEST_LIB_DIR=${PREFIX}/lib/haven +DEST_INCLUDE_DIR=${PREFIX}/include/haven +export CMAKE_INCLUDE_PATH="${PREFIX}/include" +export CMAKE_LIBRARY_PATH="${PREFIX}/lib" +ANDROID_STANDALONE_TOOLCHAIN_PATH="${TOOLCHAIN_BASE_DIR}_${arch}" +PATH="${ANDROID_STANDALONE_TOOLCHAIN_PATH}/bin:${ORIGINAL_PATH}" + +mkdir -p $DEST_LIB_DIR +mkdir -p $DEST_INCLUDE_DIR + +case $arch in + "aarch" ) + CLANG=arm-linux-androideabi-clang + CXXLANG=arm-linux-androideabi-clang++ + BUILD_64=OFF + TAG="android-armv7" + ARCH="armv7-a" + ARCH_ABI="armeabi-v7a" + FLAGS="-D CMAKE_ANDROID_ARM_MODE=ON -D NO_AES=true";; + "aarch64" ) + CLANG=aarch64-linux-androideabi-clang + CXXLANG=aarch64-linux-androideabi-clang++ + BUILD_64=ON + TAG="android-armv8" + ARCH="armv8-a" + ARCH_ABI="arm64-v8a";; + "i686" ) + CLANG=i686-linux-androideabi-clang + CXXLANG=i686-linux-androideabi-clang++ + BUILD_64=OFF + TAG="android-x86" + ARCH="i686" + ARCH_ABI="x86";; + "x86_64" ) + CLANG=x86_64-linux-androideabi-clang + CXXLANG=x86_64-linux-androideabi-clang++ + BUILD_64=ON + TAG="android-x86_64" + ARCH="x86-64" + ARCH_ABI="x86_64";; +esac + +cd $HAVEN_SRC_DIR +rm -rf ./build/release +mkdir -p ./build/release +cd ./build/release +CC=${CLANG} CXX=${CXXLANG} cmake -D USE_DEVICE_TREZOR=OFF -D BUILD_GUI_DEPS=1 -D BUILD_TESTS=OFF -D ARCH=${ARCH} -D STATIC=ON -D BUILD_64=${BUILD_64} -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON -D BUILD_TAG=${TAG} -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI=${ARCH_ABI} $FLAGS ../.. + +make wallet_api -j$THREADS +find . -path ./lib -prune -o -name '*.a' -exec cp '{}' lib \; + +cp -r ./lib/* $DEST_LIB_DIR +cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR +done diff --git a/scripts/docker/copy_haven_deps.sh b/scripts/docker/copy_haven_deps.sh index 5be7a1bb68..cef6447013 100755 --- a/scripts/docker/copy_haven_deps.sh +++ b/scripts/docker/copy_haven_deps.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -x -e +set -x WORKDIR=/opt/android CW_DIR=${WORKDIR}/cake_wallet diff --git a/scripts/docker/copy_monero_deps.sh b/scripts/docker/copy_monero_deps.sh index 611fedd01a..1c2394c0d2 100755 --- a/scripts/docker/copy_monero_deps.sh +++ b/scripts/docker/copy_monero_deps.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -x -e +set -x WORKDIR=/opt/android CW_EXRTERNAL_DIR=${WORKDIR}/output/android diff --git a/scripts/docker/docker-compose.yml b/scripts/docker/docker-compose.yml index eaeea0f5b1..00f24ce2e1 100755 --- a/scripts/docker/docker-compose.yml +++ b/scripts/docker/docker-compose.yml @@ -7,3 +7,5 @@ services: MONERO_BRANCH: release-v0.18.2.2-android volumes: - ./output:/opt/android/output + - ./cache/dotcache:/root/.cache + - ./cache/dotccache:/root/.ccache diff --git a/scripts/docker/entrypoint.sh b/scripts/docker/entrypoint.sh index 84f106145f..14f02a1f8f 100755 --- a/scripts/docker/entrypoint.sh +++ b/scripts/docker/entrypoint.sh @@ -1,5 +1,11 @@ #!/bin/bash set -x -e +ls /opt/android + +rm -rf monero haven + ./build_monero.sh +./build_haven.sh ./copy_monero_deps.sh +./copy_haven_deps.sh From 7cf0a765618af27d3617005d9d28d37fb8050c92 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Tue, 23 Apr 2024 16:05:33 +0200 Subject: [PATCH 051/242] remove unnecessary code --- cw_monero/android/.classpath | 6 - cw_monero/android/.gitignore | 8 - cw_monero/android/.project | 23 - .../org.eclipse.buildship.core.prefs | 13 - cw_monero/android/CMakeLists.txt | 232 ---- cw_monero/android/build.gradle | 49 - cw_monero/android/gradle.properties | 4 - .../gradle/wrapper/gradle-wrapper.properties | 5 - cw_monero/android/jni/monero_jni.cpp | 74 -- cw_monero/android/settings.gradle | 1 - .../android/src/main/AndroidManifest.xml | 4 - .../com/cakewallet/monero/CwMoneroPlugin.kt | 74 -- cw_monero/example/.gitignore | 44 - cw_monero/example/README.md | 16 - cw_monero/example/analysis_options.yaml | 29 - cw_monero/example/ios/Podfile | 44 - cw_monero/example/lib/main.dart | 63 - cw_monero/example/macos/.gitignore | 7 - .../macos/Flutter/Flutter-Debug.xcconfig | 2 - .../macos/Flutter/Flutter-Release.xcconfig | 2 - .../Flutter/GeneratedPluginRegistrant.swift | 14 - cw_monero/example/macos/Podfile | 40 - cw_monero/example/macos/Podfile.lock | 22 - .../macos/Runner.xcodeproj/project.pbxproj | 632 ---------- .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 87 -- .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../example/macos/Runner/AppDelegate.swift | 9 - .../AppIcon.appiconset/Contents.json | 68 -- .../AppIcon.appiconset/app_icon_1024.png | Bin 102994 -> 0 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 5680 -> 0 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 520 -> 0 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 14142 -> 0 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 1066 -> 0 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 36406 -> 0 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 2218 -> 0 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 ------ .../macos/Runner/Configs/AppInfo.xcconfig | 14 - .../macos/Runner/Configs/Debug.xcconfig | 2 - .../macos/Runner/Configs/Release.xcconfig | 2 - .../macos/Runner/Configs/Warnings.xcconfig | 13 - .../macos/Runner/DebugProfile.entitlements | 12 - cw_monero/example/macos/Runner/Info.plist | 32 - .../macos/Runner/MainFlutterWindow.swift | 15 - .../example/macos/Runner/Release.entitlements | 8 - cw_monero/example/pubspec.lock | 444 ------- cw_monero/example/pubspec.yaml | 84 -- cw_monero/example/test/widget_test.dart | 27 - cw_monero/ios/.gitignore | 37 - cw_monero/ios/Assets/.gitkeep | 0 cw_monero/ios/Classes/CwMoneroPlugin.h | 4 - cw_monero/ios/Classes/CwMoneroPlugin.m | 8 - cw_monero/ios/Classes/CwWalletListener.h | 23 - .../ios/Classes/SwiftCwMoneroPlugin.swift | 14 - cw_monero/ios/Classes/monero_api.cpp | 1034 ----------------- cw_monero/ios/Classes/monero_api.h | 39 - cw_monero/ios/cw_monero.podspec | 62 - cw_monero/macos/Classes/CwMoneroPlugin.swift | 19 - cw_monero/macos/Classes/CwWalletListener.h | 23 - cw_monero/macos/Classes/monero_api.cpp | 1032 ---------------- cw_monero/macos/Classes/monero_api.h | 39 - cw_monero/macos/cw_monero_base.podspec | 56 - cw_monero/pubspec.yaml | 9 - .../test/cw_monero_method_channel_test.dart | 24 - cw_monero/test/cw_monero_test.dart | 29 - macos/Flutter/GeneratedPluginRegistrant.swift | 2 - 67 files changed, 5048 deletions(-) delete mode 100644 cw_monero/android/.classpath delete mode 100644 cw_monero/android/.gitignore delete mode 100644 cw_monero/android/.project delete mode 100644 cw_monero/android/.settings/org.eclipse.buildship.core.prefs delete mode 100644 cw_monero/android/CMakeLists.txt delete mode 100644 cw_monero/android/build.gradle delete mode 100644 cw_monero/android/gradle.properties delete mode 100644 cw_monero/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 cw_monero/android/jni/monero_jni.cpp delete mode 100644 cw_monero/android/settings.gradle delete mode 100644 cw_monero/android/src/main/AndroidManifest.xml delete mode 100644 cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt delete mode 100644 cw_monero/example/.gitignore delete mode 100644 cw_monero/example/README.md delete mode 100644 cw_monero/example/analysis_options.yaml delete mode 100644 cw_monero/example/ios/Podfile delete mode 100644 cw_monero/example/lib/main.dart delete mode 100644 cw_monero/example/macos/.gitignore delete mode 100644 cw_monero/example/macos/Flutter/Flutter-Debug.xcconfig delete mode 100644 cw_monero/example/macos/Flutter/Flutter-Release.xcconfig delete mode 100644 cw_monero/example/macos/Flutter/GeneratedPluginRegistrant.swift delete mode 100644 cw_monero/example/macos/Podfile delete mode 100644 cw_monero/example/macos/Podfile.lock delete mode 100644 cw_monero/example/macos/Runner.xcodeproj/project.pbxproj delete mode 100644 cw_monero/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 cw_monero/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 cw_monero/example/macos/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 cw_monero/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 cw_monero/example/macos/Runner/AppDelegate.swift delete mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png delete mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png delete mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png delete mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png delete mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png delete mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png delete mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png delete mode 100644 cw_monero/example/macos/Runner/Base.lproj/MainMenu.xib delete mode 100644 cw_monero/example/macos/Runner/Configs/AppInfo.xcconfig delete mode 100644 cw_monero/example/macos/Runner/Configs/Debug.xcconfig delete mode 100644 cw_monero/example/macos/Runner/Configs/Release.xcconfig delete mode 100644 cw_monero/example/macos/Runner/Configs/Warnings.xcconfig delete mode 100644 cw_monero/example/macos/Runner/DebugProfile.entitlements delete mode 100644 cw_monero/example/macos/Runner/Info.plist delete mode 100644 cw_monero/example/macos/Runner/MainFlutterWindow.swift delete mode 100644 cw_monero/example/macos/Runner/Release.entitlements delete mode 100644 cw_monero/example/pubspec.lock delete mode 100644 cw_monero/example/pubspec.yaml delete mode 100644 cw_monero/example/test/widget_test.dart delete mode 100644 cw_monero/ios/.gitignore delete mode 100644 cw_monero/ios/Assets/.gitkeep delete mode 100644 cw_monero/ios/Classes/CwMoneroPlugin.h delete mode 100644 cw_monero/ios/Classes/CwMoneroPlugin.m delete mode 100644 cw_monero/ios/Classes/CwWalletListener.h delete mode 100644 cw_monero/ios/Classes/SwiftCwMoneroPlugin.swift delete mode 100644 cw_monero/ios/Classes/monero_api.cpp delete mode 100644 cw_monero/ios/Classes/monero_api.h delete mode 100644 cw_monero/ios/cw_monero.podspec delete mode 100644 cw_monero/macos/Classes/CwMoneroPlugin.swift delete mode 100644 cw_monero/macos/Classes/CwWalletListener.h delete mode 100644 cw_monero/macos/Classes/monero_api.cpp delete mode 100644 cw_monero/macos/Classes/monero_api.h delete mode 100644 cw_monero/macos/cw_monero_base.podspec delete mode 100644 cw_monero/test/cw_monero_method_channel_test.dart delete mode 100644 cw_monero/test/cw_monero_test.dart diff --git a/cw_monero/android/.classpath b/cw_monero/android/.classpath deleted file mode 100644 index 4a04201ca2..0000000000 --- a/cw_monero/android/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/cw_monero/android/.gitignore b/cw_monero/android/.gitignore deleted file mode 100644 index c6cbe562a4..0000000000 --- a/cw_monero/android/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures diff --git a/cw_monero/android/.project b/cw_monero/android/.project deleted file mode 100644 index e0799208f9..0000000000 --- a/cw_monero/android/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - cw_monero - Project android created by Buildship. - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.buildship.core.gradleprojectnature - - diff --git a/cw_monero/android/.settings/org.eclipse.buildship.core.prefs b/cw_monero/android/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index a88c4d4840..0000000000 --- a/cw_monero/android/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,13 +0,0 @@ -arguments= -auto.sync=false -build.scans.enabled=false -connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0-20191016123526+0000)) -connection.project.dir=../../android -eclipse.preferences.version=1 -gradle.user.home= -java.home= -jvm.arguments= -offline.mode=false -override.workspace.settings=true -show.console.view=true -show.executions.view=true diff --git a/cw_monero/android/CMakeLists.txt b/cw_monero/android/CMakeLists.txt deleted file mode 100644 index f9f98927cd..0000000000 --- a/cw_monero/android/CMakeLists.txt +++ /dev/null @@ -1,232 +0,0 @@ -cmake_minimum_required(VERSION 3.4.1) - -add_library( cw_monero - SHARED - ./jni/monero_jni.cpp - ../ios/Classes/monero_api.cpp) - - find_library( log-lib log ) - -set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../../cw_shared_external/ios/External/android) - -############ -# libsodium -############ - -add_library(sodium STATIC IMPORTED) -set_target_properties(sodium PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libsodium.a) - -############ -# OpenSSL -############ - -add_library(crypto STATIC IMPORTED) -set_target_properties(crypto PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libcrypto.a) - -add_library(ssl STATIC IMPORTED) -set_target_properties(ssl PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libssl.a) - -############ -# Boost -############ - -add_library(boost_chrono STATIC IMPORTED) -set_target_properties(boost_chrono PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_chrono.a) - -add_library(boost_date_time STATIC IMPORTED) -set_target_properties(boost_date_time PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_date_time.a) - -add_library(boost_filesystem STATIC IMPORTED) -set_target_properties(boost_filesystem PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_filesystem.a) - -add_library(boost_program_options STATIC IMPORTED) -set_target_properties(boost_program_options PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_program_options.a) - -add_library(boost_regex STATIC IMPORTED) -set_target_properties(boost_regex PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_regex.a) - -add_library(boost_serialization STATIC IMPORTED) -set_target_properties(boost_serialization PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_serialization.a) - -add_library(boost_system STATIC IMPORTED) -set_target_properties(boost_system PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_system.a) - -add_library(boost_thread STATIC IMPORTED) -set_target_properties(boost_thread PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_thread.a) - -add_library(boost_wserialization STATIC IMPORTED) -set_target_properties(boost_wserialization PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_wserialization.a) - -############# -# Monero -############# - -add_library(wallet_api STATIC IMPORTED) -set_target_properties(wallet_api PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libwallet_api.a) - -add_library(wallet STATIC IMPORTED) -set_target_properties(wallet PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libwallet.a) - -add_library(cryptonote_core STATIC IMPORTED) -set_target_properties(cryptonote_core PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libcryptonote_core.a) - -add_library(cryptonote_basic STATIC IMPORTED) -set_target_properties(cryptonote_basic PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libcryptonote_basic.a) - -add_library(cryptonote_format_utils_basic STATIC IMPORTED) -set_target_properties(cryptonote_format_utils_basic PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libcryptonote_format_utils_basic.a) - -add_library(mnemonics STATIC IMPORTED) -set_target_properties(mnemonics PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libmnemonics.a) - -add_library(common STATIC IMPORTED) -set_target_properties(common PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libcommon.a) - -add_library(cncrypto STATIC IMPORTED) -set_target_properties(cncrypto PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libcncrypto.a) - -add_library(ringct STATIC IMPORTED) -set_target_properties(ringct PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libringct.a) - -add_library(ringct_basic STATIC IMPORTED) -set_target_properties(ringct_basic PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libringct_basic.a) - -add_library(blockchain_db STATIC IMPORTED) -set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libblockchain_db.a) - -add_library(lmdb STATIC IMPORTED) -set_target_properties(lmdb PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/liblmdb.a) - -add_library(easylogging STATIC IMPORTED) -set_target_properties(easylogging PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libeasylogging.a) - -add_library(unbound STATIC IMPORTED) -set_target_properties(unbound PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libunbound.a) - -add_library(epee STATIC IMPORTED) -set_target_properties(epee PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libepee.a) - -add_library(blocks STATIC IMPORTED) -set_target_properties(blocks PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libblocks.a) - -add_library(checkpoints STATIC IMPORTED) -set_target_properties(checkpoints PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libcheckpoints.a) - -add_library(device STATIC IMPORTED) -set_target_properties(device PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libdevice.a) - -add_library(device_trezor STATIC IMPORTED) -set_target_properties(device_trezor PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libdevice_trezor.a) - -add_library(multisig STATIC IMPORTED) -set_target_properties(multisig PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libmultisig.a) - -add_library(version STATIC IMPORTED) -set_target_properties(version PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libversion.a) - -add_library(net STATIC IMPORTED) -set_target_properties(net PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libnet.a) - -add_library(hardforks STATIC IMPORTED) -set_target_properties(hardforks PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libhardforks.a) - -add_library(randomx STATIC IMPORTED) -set_target_properties(randomx PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/librandomx.a) - -add_library(rpc_base STATIC IMPORTED) -set_target_properties(rpc_base PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/librpc_base.a) - -add_library(wallet-crypto STATIC IMPORTED) -set_target_properties(wallet-crypto PROPERTIES IMPORTED_LOCATION - ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/monero/libwallet-crypto.a) - -set(WALLET_CRYPTO "") - -if(${ANDROID_ABI} STREQUAL "x86_64") - set(WALLET_CRYPTO "wallet-crypto") -endif() - -include_directories( ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/include ) - -target_link_libraries( cw_monero - - wallet_api - wallet - cryptonote_core - cryptonote_basic - cryptonote_format_utils_basic - mnemonics - ringct - ringct_basic - net - common - cncrypto - blockchain_db - lmdb - easylogging - unbound - epee - blocks - checkpoints - device - device_trezor - multisig - version - randomx - hardforks - rpc_base - ${WALLET_CRYPTO} - - boost_chrono - boost_date_time - boost_filesystem - boost_program_options - boost_regex - boost_serialization - boost_system - boost_thread - boost_wserialization - - ssl - crypto - - sodium - - ${log-lib} ) \ No newline at end of file diff --git a/cw_monero/android/build.gradle b/cw_monero/android/build.gradle deleted file mode 100644 index fc4835e812..0000000000 --- a/cw_monero/android/build.gradle +++ /dev/null @@ -1,49 +0,0 @@ -group 'com.cakewallet.monero' -version '1.0-SNAPSHOT' - -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -rootProject.allprojects { - repositories { - google() - jcenter() - } -} - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -android { - compileSdkVersion 28 - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - defaultConfig { - minSdkVersion 21 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - lintOptions { - disable 'InvalidPackage' - } - externalNativeBuild { - cmake { - path "CMakeLists.txt" - } - } -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/cw_monero/android/gradle.properties b/cw_monero/android/gradle.properties deleted file mode 100644 index 38c8d4544f..0000000000 --- a/cw_monero/android/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableR8=true -android.useAndroidX=true -android.enableJetifier=true diff --git a/cw_monero/android/gradle/wrapper/gradle-wrapper.properties b/cw_monero/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 019065d1d6..0000000000 --- a/cw_monero/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/cw_monero/android/jni/monero_jni.cpp b/cw_monero/android/jni/monero_jni.cpp deleted file mode 100644 index 83e06a41ff..0000000000 --- a/cw_monero/android/jni/monero_jni.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include "../../ios/Classes/monero_api.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -JNIEXPORT void JNICALL -Java_com_cakewallet_monero_MoneroApi_setNodeAddressJNI( - JNIEnv *env, - jobject inst, - jstring uri, - jstring login, - jstring password, - jboolean use_ssl, - jboolean is_light_wallet) { - const char *_uri = env->GetStringUTFChars(uri, 0); - const char *_login = ""; - const char *_password = ""; - char *error; - - if (login != NULL) { - _login = env->GetStringUTFChars(login, 0); - } - - if (password != NULL) { - _password = env->GetStringUTFChars(password, 0); - } - char *__uri = (char*) _uri; - char *__login = (char*) _login; - char *__password = (char*) _password; - bool inited = setup_node(__uri, __login, __password, false, false, error); - - if (!inited) { - env->ThrowNew(env->FindClass("java/lang/Exception"), error); - } -} - -JNIEXPORT void JNICALL -Java_com_cakewallet_monero_MoneroApi_connectToNodeJNI( - JNIEnv *env, - jobject inst) { - char *error; - bool is_connected = connect_to_node(error); - - if (!is_connected) { - env->ThrowNew(env->FindClass("java/lang/Exception"), error); - } -} - -JNIEXPORT void JNICALL -Java_com_cakewallet_monero_MoneroApi_startSyncJNI( - JNIEnv *env, - jobject inst) { - start_refresh(); -} - -JNIEXPORT void JNICALL -Java_com_cakewallet_monero_MoneroApi_loadWalletJNI( - JNIEnv *env, - jobject inst, - jstring path, - jstring password) { - char *_path = (char *) env->GetStringUTFChars(path, 0); - char *_password = (char *) env->GetStringUTFChars(password, 0); - - load_wallet(_path, _password, 0); -} - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/cw_monero/android/settings.gradle b/cw_monero/android/settings.gradle deleted file mode 100644 index 1f9e2a39d6..0000000000 --- a/cw_monero/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'cw_monero' diff --git a/cw_monero/android/src/main/AndroidManifest.xml b/cw_monero/android/src/main/AndroidManifest.xml deleted file mode 100644 index 8152415a2e..0000000000 --- a/cw_monero/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt b/cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt deleted file mode 100644 index 37684a16ac..0000000000 --- a/cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.cakewallet.monero - -import android.app.Activity -import android.os.AsyncTask -import android.os.Looper -import android.os.Handler -import android.os.Process - -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.plugin.common.PluginRegistry.Registrar - -class doAsync(val handler: () -> Unit) : AsyncTask() { - override fun doInBackground(vararg params: Void?): Void? { - Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO); - handler() - return null - } -} - -class CwMoneroPlugin: MethodCallHandler { - companion object { -// val moneroApi = MoneroApi() - val main = Handler(Looper.getMainLooper()); - - init { - System.loadLibrary("cw_monero") - } - - @JvmStatic - fun registerWith(registrar: Registrar) { - val channel = MethodChannel(registrar.messenger(), "cw_monero") - channel.setMethodCallHandler(CwMoneroPlugin()) - } - } - - override fun onMethodCall(call: MethodCall, result: Result) { - if (call.method == "setupNode") { - val uri = call.argument("address") ?: "" - val login = call.argument("login") ?: "" - val password = call.argument("password") ?: "" - val useSSL = false - val isLightWallet = false -// doAsync { -// try { -// moneroApi.setNodeAddressJNI(uri, login, password, useSSL, isLightWallet) -// main.post({ -// result.success(true) -// }); -// } catch(e: Throwable) { -// main.post({ -// result.error("CONNECTION_ERROR", e.message, null) -// }); -// } -// }.execute() - } - if (call.method == "startSync") { -// doAsync { -// moneroApi.startSyncJNI() -// main.post({ -// result.success(true) -// }); -// }.execute() - } - if (call.method == "loadWallet") { - val path = call.argument("path") ?: "" - val password = call.argument("password") ?: "" -// moneroApi.loadWalletJNI(path, password) - result.success(true) - } - } -} diff --git a/cw_monero/example/.gitignore b/cw_monero/example/.gitignore deleted file mode 100644 index 24476c5d1e..0000000000 --- a/cw_monero/example/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release diff --git a/cw_monero/example/README.md b/cw_monero/example/README.md deleted file mode 100644 index 18cf6d1093..0000000000 --- a/cw_monero/example/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# cw_monero_example - -Demonstrates how to use the cw_monero plugin. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/cw_monero/example/analysis_options.yaml b/cw_monero/example/analysis_options.yaml deleted file mode 100644 index 61b6c4de17..0000000000 --- a/cw_monero/example/analysis_options.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/cw_monero/example/ios/Podfile b/cw_monero/example/ios/Podfile deleted file mode 100644 index fdcc671eb3..0000000000 --- a/cw_monero/example/ios/Podfile +++ /dev/null @@ -1,44 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '11.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/cw_monero/example/lib/main.dart b/cw_monero/example/lib/main.dart deleted file mode 100644 index e4374f0974..0000000000 --- a/cw_monero/example/lib/main.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/material.dart'; -import 'dart:async'; - -import 'package:flutter/services.dart'; -import 'package:cw_monero/cw_monero.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatefulWidget { - const MyApp({super.key}); - - @override - State createState() => _MyAppState(); -} - -class _MyAppState extends State { - String _platformVersion = 'Unknown'; - final _cwMoneroPlugin = CwMonero(); - - @override - void initState() { - super.initState(); - initPlatformState(); - } - - // Platform messages are asynchronous, so we initialize in an async method. - Future initPlatformState() async { - String platformVersion; - // Platform messages may fail, so we use a try/catch PlatformException. - // We also handle the message potentially returning null. - try { - platformVersion = - await _cwMoneroPlugin.getPlatformVersion() ?? 'Unknown platform version'; - } on PlatformException { - platformVersion = 'Failed to get platform version.'; - } - - // If the widget was removed from the tree while the asynchronous platform - // message was in flight, we want to discard the reply rather than calling - // setState to update our non-existent appearance. - if (!mounted) return; - - setState(() { - _platformVersion = platformVersion; - }); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), - body: Center( - child: Text('Running on: $_platformVersion\n'), - ), - ), - ); - } -} diff --git a/cw_monero/example/macos/.gitignore b/cw_monero/example/macos/.gitignore deleted file mode 100644 index 746adbb6b9..0000000000 --- a/cw_monero/example/macos/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Flutter-related -**/Flutter/ephemeral/ -**/Pods/ - -# Xcode-related -**/dgph -**/xcuserdata/ diff --git a/cw_monero/example/macos/Flutter/Flutter-Debug.xcconfig b/cw_monero/example/macos/Flutter/Flutter-Debug.xcconfig deleted file mode 100644 index 4b81f9b2d2..0000000000 --- a/cw_monero/example/macos/Flutter/Flutter-Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/cw_monero/example/macos/Flutter/Flutter-Release.xcconfig b/cw_monero/example/macos/Flutter/Flutter-Release.xcconfig deleted file mode 100644 index 5caa9d1579..0000000000 --- a/cw_monero/example/macos/Flutter/Flutter-Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/cw_monero/example/macos/Flutter/GeneratedPluginRegistrant.swift b/cw_monero/example/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index e25d640976..0000000000 --- a/cw_monero/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import cw_monero -import path_provider_foundation - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin")) - PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) -} diff --git a/cw_monero/example/macos/Podfile b/cw_monero/example/macos/Podfile deleted file mode 100644 index dade8dfad0..0000000000 --- a/cw_monero/example/macos/Podfile +++ /dev/null @@ -1,40 +0,0 @@ -platform :osx, '10.11' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_macos_build_settings(target) - end -end diff --git a/cw_monero/example/macos/Podfile.lock b/cw_monero/example/macos/Podfile.lock deleted file mode 100644 index 692176b308..0000000000 --- a/cw_monero/example/macos/Podfile.lock +++ /dev/null @@ -1,22 +0,0 @@ -PODS: - - FlutterMacOS (1.0.0) - - path_provider_macos (0.0.1): - - FlutterMacOS - -DEPENDENCIES: - - FlutterMacOS (from `Flutter/ephemeral`) - - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) - -EXTERNAL SOURCES: - FlutterMacOS: - :path: Flutter/ephemeral - path_provider_macos: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos - -SPEC CHECKSUMS: - FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 - path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19 - -PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c - -COCOAPODS: 1.11.2 diff --git a/cw_monero/example/macos/Runner.xcodeproj/project.pbxproj b/cw_monero/example/macos/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 472859e8c3..0000000000 --- a/cw_monero/example/macos/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,632 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 51; - objects = { - -/* Begin PBXAggregateTarget section */ - 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; - buildPhases = ( - 33CC111E2044C6BF0003C045 /* ShellScript */, - ); - dependencies = ( - ); - name = "Flutter Assemble"; - productName = FLX; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 428E7496E2068D0AB138F295 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C29B2253BA962B7A415DBA77 /* Pods_Runner.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC111A2044C6BA0003C045; - remoteInfo = FLX; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Bundle Framework"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* cw_monero_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cw_monero_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; - 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; - 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; - 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - A9CDA1605413332AB9056C23 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - C29B2253BA962B7A415DBA77 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E434913D71DC2682EF8E9059 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - EEF09839C86335F78056F812 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 33CC10EA2044A3C60003C045 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 428E7496E2068D0AB138F295 /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 33BA886A226E78AF003329D5 /* Configs */ = { - isa = PBXGroup; - children = ( - 33E5194F232828860026EE4D /* AppInfo.xcconfig */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, - ); - path = Configs; - sourceTree = ""; - }; - 33CC10E42044A3C60003C045 = { - isa = PBXGroup; - children = ( - 33FAB671232836740065AC1E /* Runner */, - 33CEB47122A05771004F2AC0 /* Flutter */, - 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, - 77870A4C94A9AB6EEC2EE261 /* Pods */, - ); - sourceTree = ""; - }; - 33CC10EE2044A3C60003C045 /* Products */ = { - isa = PBXGroup; - children = ( - 33CC10ED2044A3C60003C045 /* cw_monero_example.app */, - ); - name = Products; - sourceTree = ""; - }; - 33CC11242044D66E0003C045 /* Resources */ = { - isa = PBXGroup; - children = ( - 33CC10F22044A3C60003C045 /* Assets.xcassets */, - 33CC10F42044A3C60003C045 /* MainMenu.xib */, - 33CC10F72044A3C60003C045 /* Info.plist */, - ); - name = Resources; - path = ..; - sourceTree = ""; - }; - 33CEB47122A05771004F2AC0 /* Flutter */ = { - isa = PBXGroup; - children = ( - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - ); - path = Flutter; - sourceTree = ""; - }; - 33FAB671232836740065AC1E /* Runner */ = { - isa = PBXGroup; - children = ( - 33CC10F02044A3C60003C045 /* AppDelegate.swift */, - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, - 33E51913231747F40026EE4D /* DebugProfile.entitlements */, - 33E51914231749380026EE4D /* Release.entitlements */, - 33CC11242044D66E0003C045 /* Resources */, - 33BA886A226E78AF003329D5 /* Configs */, - ); - path = Runner; - sourceTree = ""; - }; - 77870A4C94A9AB6EEC2EE261 /* Pods */ = { - isa = PBXGroup; - children = ( - EEF09839C86335F78056F812 /* Pods-Runner.debug.xcconfig */, - A9CDA1605413332AB9056C23 /* Pods-Runner.release.xcconfig */, - E434913D71DC2682EF8E9059 /* Pods-Runner.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - C29B2253BA962B7A415DBA77 /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 33CC10EC2044A3C60003C045 /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 0A239C1738C005E3F6E4DFC6 /* [CP] Check Pods Manifest.lock */, - 33CC10E92044A3C60003C045 /* Sources */, - 33CC10EA2044A3C60003C045 /* Frameworks */, - 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, - 3399D490228B24CF009A79C7 /* ShellScript */, - 0CEAA82AE8A029C31B39F234 /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 33CC11202044C79F0003C045 /* PBXTargetDependency */, - ); - name = Runner; - productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* cw_monero_example.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 33CC10E52044A3C60003C045 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 33CC10EC2044A3C60003C045 = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 1; - }; - }; - }; - 33CC111A2044C6BA0003C045 = { - CreatedOnToolsVersion = 9.2; - ProvisioningStyle = Manual; - }; - }; - }; - buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 33CC10E42044A3C60003C045; - productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 33CC10EC2044A3C60003C045 /* Runner */, - 33CC111A2044C6BA0003C045 /* Flutter Assemble */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 33CC10EB2044A3C60003C045 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 0A239C1738C005E3F6E4DFC6 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 0CEAA82AE8A029C31B39F234 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 3399D490228B24CF009A79C7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; - }; - 33CC111E2044C6BF0003C045 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - Flutter/ephemeral/FlutterInputs.xcfilelist, - ); - inputPaths = ( - Flutter/ephemeral/tripwire, - ); - outputFileListPaths = ( - Flutter/ephemeral/FlutterOutputs.xcfilelist, - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 33CC10E92044A3C60003C045 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; - targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 33CC10F52044A3C60003C045 /* Base */, - ); - name = MainMenu.xib; - path = Runner; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 338D0CE9231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Profile; - }; - 338D0CEA231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Profile; - }; - 338D0CEB231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Profile; - }; - 33CC10F92044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 33CC10FA2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - 33CC10FC2044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 33CC10FD2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 33CC111C2044C6BA0003C045 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 33CC111D2044C6BA0003C045 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10F92044A3C60003C045 /* Debug */, - 33CC10FA2044A3C60003C045 /* Release */, - 338D0CE9231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10FC2044A3C60003C045 /* Debug */, - 33CC10FD2044A3C60003C045 /* Release */, - 338D0CEA231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC111C2044C6BA0003C045 /* Debug */, - 33CC111D2044C6BA0003C045 /* Release */, - 338D0CEB231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 33CC10E52044A3C60003C045 /* Project object */; -} diff --git a/cw_monero/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/cw_monero/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d..0000000000 --- a/cw_monero/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/cw_monero/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/cw_monero/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index 4e44b7ced7..0000000000 --- a/cw_monero/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cw_monero/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/cw_monero/example/macos/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14c7..0000000000 --- a/cw_monero/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/cw_monero/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/cw_monero/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d..0000000000 --- a/cw_monero/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/cw_monero/example/macos/Runner/AppDelegate.swift b/cw_monero/example/macos/Runner/AppDelegate.swift deleted file mode 100644 index d53ef64377..0000000000 --- a/cw_monero/example/macos/Runner/AppDelegate.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Cocoa -import FlutterMacOS - -@NSApplicationMain -class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } -} diff --git a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index a2ec33f19f..0000000000 --- a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" - }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png deleted file mode 100644 index 82b6f9d9a33e198f5747104729e1fcef999772a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY diff --git a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png deleted file mode 100644 index 13b35eba55c6dabc3aac36f33d859266c18fa0d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5680 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 diff --git a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png deleted file mode 100644 index 0a3f5fa40fb3d1e0710331a48de5d256da3f275d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 520 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 diff --git a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png deleted file mode 100644 index 2f1632cfddf3d9dade342351e627a0a75609fb46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cw_monero/example/macos/Runner/Configs/AppInfo.xcconfig b/cw_monero/example/macos/Runner/Configs/AppInfo.xcconfig deleted file mode 100644 index a80a256027..0000000000 --- a/cw_monero/example/macos/Runner/Configs/AppInfo.xcconfig +++ /dev/null @@ -1,14 +0,0 @@ -// Application-level settings for the Runner target. -// -// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the -// future. If not, the values below would default to using the project name when this becomes a -// 'flutter create' template. - -// The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = cw_monero_example - -// The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cwMoneroExample - -// The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2022 com.cakewallet. All rights reserved. diff --git a/cw_monero/example/macos/Runner/Configs/Debug.xcconfig b/cw_monero/example/macos/Runner/Configs/Debug.xcconfig deleted file mode 100644 index 36b0fd9464..0000000000 --- a/cw_monero/example/macos/Runner/Configs/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Debug.xcconfig" -#include "Warnings.xcconfig" diff --git a/cw_monero/example/macos/Runner/Configs/Release.xcconfig b/cw_monero/example/macos/Runner/Configs/Release.xcconfig deleted file mode 100644 index dff4f49561..0000000000 --- a/cw_monero/example/macos/Runner/Configs/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Release.xcconfig" -#include "Warnings.xcconfig" diff --git a/cw_monero/example/macos/Runner/Configs/Warnings.xcconfig b/cw_monero/example/macos/Runner/Configs/Warnings.xcconfig deleted file mode 100644 index 42bcbf4780..0000000000 --- a/cw_monero/example/macos/Runner/Configs/Warnings.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings -GCC_WARN_UNDECLARED_SELECTOR = YES -CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES -CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES -CLANG_WARN_PRAGMA_PACK = YES -CLANG_WARN_STRICT_PROTOTYPES = YES -CLANG_WARN_COMMA = YES -GCC_WARN_STRICT_SELECTOR_MATCH = YES -CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES -CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES -GCC_WARN_SHADOW = YES -CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/cw_monero/example/macos/Runner/DebugProfile.entitlements b/cw_monero/example/macos/Runner/DebugProfile.entitlements deleted file mode 100644 index dddb8a30c8..0000000000 --- a/cw_monero/example/macos/Runner/DebugProfile.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.server - - - diff --git a/cw_monero/example/macos/Runner/Info.plist b/cw_monero/example/macos/Runner/Info.plist deleted file mode 100644 index 4789daa6a4..0000000000 --- a/cw_monero/example/macos/Runner/Info.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - diff --git a/cw_monero/example/macos/Runner/MainFlutterWindow.swift b/cw_monero/example/macos/Runner/MainFlutterWindow.swift deleted file mode 100644 index 2722837ec9..0000000000 --- a/cw_monero/example/macos/Runner/MainFlutterWindow.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Cocoa -import FlutterMacOS - -class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - - super.awakeFromNib() - } -} diff --git a/cw_monero/example/macos/Runner/Release.entitlements b/cw_monero/example/macos/Runner/Release.entitlements deleted file mode 100644 index 852fa1a472..0000000000 --- a/cw_monero/example/macos/Runner/Release.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.security.app-sandbox - - - diff --git a/cw_monero/example/pubspec.lock b/cw_monero/example/pubspec.lock deleted file mode 100644 index 333c0299b7..0000000000 --- a/cw_monero/example/pubspec.lock +++ /dev/null @@ -1,444 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - args: - dependency: transitive - description: - name: args - sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - asn1lib: - dependency: transitive - description: - name: asn1lib - sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - collection: - dependency: transitive - description: - name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" - url: "https://pub.dev" - source: hosted - version: "1.17.1" - convert: - dependency: transitive - description: - name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" - url: "https://pub.dev" - source: hosted - version: "3.1.1" - crypto: - dependency: transitive - description: - name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 - url: "https://pub.dev" - source: hosted - version: "3.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be - url: "https://pub.dev" - source: hosted - version: "1.0.5" - cw_core: - dependency: transitive - description: - path: "../../cw_core" - relative: true - source: path - version: "0.0.1" - cw_monero: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.0.1" - encrypt: - dependency: transitive - description: - name: encrypt - sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" - url: "https://pub.dev" - source: hosted - version: "5.0.1" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - ffi: - dependency: transitive - description: - name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - file: - dependency: transitive - description: - name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" - url: "https://pub.dev" - source: hosted - version: "6.1.4" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c - url: "https://pub.dev" - source: hosted - version: "2.0.1" - flutter_mobx: - dependency: transitive - description: - name: flutter_mobx - sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" - url: "https://pub.dev" - source: hosted - version: "2.0.6+5" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - hashlib: - dependency: transitive - description: - name: hashlib - sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96" - url: "https://pub.dev" - source: hosted - version: "1.12.0" - hashlib_codecs: - dependency: transitive - description: - name: hashlib_codecs - sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - http: - dependency: transitive - description: - name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - intl: - dependency: transitive - description: - name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" - url: "https://pub.dev" - source: hosted - version: "0.18.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" - lints: - dependency: transitive - description: - name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" - url: "https://pub.dev" - source: hosted - version: "2.0.1" - matcher: - dependency: transitive - description: - name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" - url: "https://pub.dev" - source: hosted - version: "0.12.15" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 - url: "https://pub.dev" - source: hosted - version: "0.2.0" - meta: - dependency: transitive - description: - name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - mobx: - dependency: transitive - description: - name: mobx - sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a - url: "https://pub.dev" - source: hosted - version: "2.1.3+1" - monero: - dependency: transitive - description: - path: "." - ref: "57e075ee67d16aa0f3f75fba67d79529fbc73a6c" - resolved-ref: "57e075ee67d16aa0f3f75fba67d79529fbc73a6c" - url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart" - source: git - version: "0.0.0" - path: - dependency: transitive - description: - name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" - url: "https://pub.dev" - source: hosted - version: "1.8.3" - path_provider: - dependency: transitive - description: - name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa - url: "https://pub.dev" - source: hosted - version: "2.1.1" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" - url: "https://pub.dev" - source: hosted - version: "2.2.1" - platform: - dependency: transitive - description: - name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a - url: "https://pub.dev" - source: hosted - version: "2.1.3" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" - url: "https://pub.dev" - source: hosted - version: "3.7.3" - polyseed: - dependency: transitive - description: - name: polyseed - sha256: "9b48ec535b10863f78f6354ec983b4cc0c88ca69ff48fee469d0fd1954b01d4f" - url: "https://pub.dev" - source: hosted - version: "0.0.2" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - socks5_proxy: - dependency: transitive - description: - name: socks5_proxy - sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - source_span: - dependency: transitive - description: - name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 - url: "https://pub.dev" - source: hosted - version: "1.9.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 - url: "https://pub.dev" - source: hosted - version: "1.11.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb - url: "https://pub.dev" - source: hosted - version: "0.5.1" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - win32: - dependency: transitive - description: - name: win32 - sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 - url: "https://pub.dev" - source: hosted - version: "3.1.4" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 - url: "https://pub.dev" - source: hosted - version: "0.2.0+3" -sdks: - dart: ">=3.0.6 <4.0.0" - flutter: ">=3.7.0" diff --git a/cw_monero/example/pubspec.yaml b/cw_monero/example/pubspec.yaml deleted file mode 100644 index 2dee5337f7..0000000000 --- a/cw_monero/example/pubspec.yaml +++ /dev/null @@ -1,84 +0,0 @@ -name: cw_monero_example -description: Demonstrates how to use the cw_monero plugin. - -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -environment: - sdk: '>=2.18.1 <3.0.0' - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - - cw_monero: - # When depending on this package from a real application you should use: - # cw_monero: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../ - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - -dev_dependencies: - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^2.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/cw_monero/example/test/widget_test.dart b/cw_monero/example/test/widget_test.dart deleted file mode 100644 index b37e6313d5..0000000000 --- a/cw_monero/example/test/widget_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:cw_monero_example/main.dart'; - -void main() { - testWidgets('Verify Platform version', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => widget is Text && - widget.data!.startsWith('Running on:'), - ), - findsOneWidget, - ); - }); -} diff --git a/cw_monero/ios/.gitignore b/cw_monero/ios/.gitignore deleted file mode 100644 index aa479fd3ce..0000000000 --- a/cw_monero/ios/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -.idea/ -.vagrant/ -.sconsign.dblite -.svn/ - -.DS_Store -*.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m - -.generated/ - -*.pbxuser -*.mode1v3 -*.mode2v3 -*.perspectivev3 - -!default.pbxuser -!default.mode1v3 -!default.mode2v3 -!default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/Generated.xcconfig -/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/cw_monero/ios/Assets/.gitkeep b/cw_monero/ios/Assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cw_monero/ios/Classes/CwMoneroPlugin.h b/cw_monero/ios/Classes/CwMoneroPlugin.h deleted file mode 100644 index a42018098e..0000000000 --- a/cw_monero/ios/Classes/CwMoneroPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface CwMoneroPlugin : NSObject -@end diff --git a/cw_monero/ios/Classes/CwMoneroPlugin.m b/cw_monero/ios/Classes/CwMoneroPlugin.m deleted file mode 100644 index eee2512122..0000000000 --- a/cw_monero/ios/Classes/CwMoneroPlugin.m +++ /dev/null @@ -1,8 +0,0 @@ -#import "CwMoneroPlugin.h" -#import - -@implementation CwMoneroPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftCwMoneroPlugin registerWithRegistrar:registrar]; -} -@end diff --git a/cw_monero/ios/Classes/CwWalletListener.h b/cw_monero/ios/Classes/CwWalletListener.h deleted file mode 100644 index cbfcb0c4e9..0000000000 --- a/cw_monero/ios/Classes/CwWalletListener.h +++ /dev/null @@ -1,23 +0,0 @@ -#include - -struct CWMoneroWalletListener; - -typedef int8_t (*on_new_block_callback)(uint64_t height); -typedef int8_t (*on_need_to_refresh_callback)(); - -typedef struct CWMoneroWalletListener -{ - // on_money_spent_callback *on_money_spent; - // on_money_received_callback *on_money_received; - // on_unconfirmed_money_received_callback *on_unconfirmed_money_received; - // on_new_block_callback *on_new_block; - // on_updated_callback *on_updated; - // on_refreshed_callback *on_refreshed; - - on_new_block_callback on_new_block; -} CWMoneroWalletListener; - -struct TestListener { - // int8_t x; - on_new_block_callback on_new_block; -}; \ No newline at end of file diff --git a/cw_monero/ios/Classes/SwiftCwMoneroPlugin.swift b/cw_monero/ios/Classes/SwiftCwMoneroPlugin.swift deleted file mode 100644 index 4c03a3e44d..0000000000 --- a/cw_monero/ios/Classes/SwiftCwMoneroPlugin.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Flutter -import UIKit - -public class SwiftCwMoneroPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "cw_monero", binaryMessenger: registrar.messenger()) - let instance = SwiftCwMoneroPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - result("iOS " + UIDevice.current.systemVersion) - } -} diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp deleted file mode 100644 index 87be785ac2..0000000000 --- a/cw_monero/ios/Classes/monero_api.cpp +++ /dev/null @@ -1,1034 +0,0 @@ -#include -#include "cstdlib" -#include -#include -#include -#include -#include -#include -#include -#include "thread" -#include "CwWalletListener.h" -#if __APPLE__ -// Fix for randomx on ios -void __clear_cache(void* start, void* end) { } -#include "../External/ios/include/wallet2_api.h" -#else -#include "../External/android/include/wallet2_api.h" -#endif - -using namespace std::chrono_literals; -#ifdef __cplusplus -extern "C" -{ -#endif - const uint64_t MONERO_BLOCK_SIZE = 1000; - - struct Utf8Box - { - char *value; - - Utf8Box(char *_value) - { - value = _value; - } - }; - - struct SubaddressRow - { - uint64_t id; - char *address; - char *label; - - SubaddressRow(std::size_t _id, char *_address, char *_label) - { - id = static_cast(_id); - address = _address; - label = _label; - } - }; - - struct AccountRow - { - uint64_t id; - char *label; - - AccountRow(std::size_t _id, char *_label) - { - id = static_cast(_id); - label = _label; - } - }; - - struct MoneroWalletListener : Monero::WalletListener - { - uint64_t m_height; - bool m_need_to_refresh; - bool m_new_transaction; - - MoneroWalletListener() - { - m_height = 0; - m_need_to_refresh = false; - m_new_transaction = false; - } - - void moneySpent(const std::string &txId, uint64_t amount) - { - m_new_transaction = true; - } - - void moneyReceived(const std::string &txId, uint64_t amount) - { - m_new_transaction = true; - } - - void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) - { - m_new_transaction = true; - } - - void newBlock(uint64_t height) - { - m_height = height; - } - - void updated() - { - m_new_transaction = true; - } - - void refreshed() - { - m_need_to_refresh = true; - } - - void resetNeedToRefresh() - { - m_need_to_refresh = false; - } - - bool isNeedToRefresh() - { - return m_need_to_refresh; - } - - bool isNewTransactionExist() - { - return m_new_transaction; - } - - void resetIsNewTransactionExist() - { - m_new_transaction = false; - } - - uint64_t height() - { - return m_height; - } - }; - - struct TransactionInfoRow - { - uint64_t amount; - uint64_t fee; - uint64_t blockHeight; - uint64_t confirmations; - uint32_t subaddrAccount; - int8_t direction; - int8_t isPending; - uint32_t subaddrIndex; - - char *hash; - char *paymentId; - - int64_t datetime; - - TransactionInfoRow(Monero::TransactionInfo *transaction) - { - amount = transaction->amount(); - fee = transaction->fee(); - blockHeight = transaction->blockHeight(); - subaddrAccount = transaction->subaddrAccount(); - std::set::iterator it = transaction->subaddrIndex().begin(); - subaddrIndex = *it; - confirmations = transaction->confirmations(); - datetime = static_cast(transaction->timestamp()); - direction = transaction->direction(); - isPending = static_cast(transaction->isPending()); - std::string *hash_str = new std::string(transaction->hash()); - hash = strdup(hash_str->c_str()); - paymentId = strdup(transaction->paymentId().c_str()); - } - }; - - struct PendingTransactionRaw - { - uint64_t amount; - uint64_t fee; - char *hash; - char *hex; - char *txKey; - Monero::PendingTransaction *transaction; - - PendingTransactionRaw(Monero::PendingTransaction *_transaction) - { - transaction = _transaction; - amount = _transaction->amount(); - fee = _transaction->fee(); - hash = strdup(_transaction->txid()[0].c_str()); - hex = strdup(_transaction->hex()[0].c_str()); - txKey = strdup(_transaction->txKey()[0].c_str()); - } - }; - - struct CoinsInfoRow - { - uint64_t blockHeight; - char *hash; - uint64_t internalOutputIndex; - uint64_t globalOutputIndex; - bool spent; - bool frozen; - uint64_t spentHeight; - uint64_t amount; - bool rct; - bool keyImageKnown; - uint64_t pkIndex; - uint32_t subaddrIndex; - uint32_t subaddrAccount; - char *address; - char *addressLabel; - char *keyImage; - uint64_t unlockTime; - bool unlocked; - char *pubKey; - bool coinbase; - char *description; - - CoinsInfoRow(Monero::CoinsInfo *coinsInfo) - { - blockHeight = coinsInfo->blockHeight(); - std::string *hash_str = new std::string(coinsInfo->hash()); - hash = strdup(hash_str->c_str()); - internalOutputIndex = coinsInfo->internalOutputIndex(); - globalOutputIndex = coinsInfo->globalOutputIndex(); - spent = coinsInfo->spent(); - frozen = coinsInfo->frozen(); - spentHeight = coinsInfo->spentHeight(); - amount = coinsInfo->amount(); - rct = coinsInfo->rct(); - keyImageKnown = coinsInfo->keyImageKnown(); - pkIndex = coinsInfo->pkIndex(); - subaddrIndex = coinsInfo->subaddrIndex(); - subaddrAccount = coinsInfo->subaddrAccount(); - address = strdup(coinsInfo->address().c_str()) ; - addressLabel = strdup(coinsInfo->addressLabel().c_str()); - keyImage = strdup(coinsInfo->keyImage().c_str()); - unlockTime = coinsInfo->unlockTime(); - unlocked = coinsInfo->unlocked(); - pubKey = strdup(coinsInfo->pubKey().c_str()); - coinbase = coinsInfo->coinbase(); - description = strdup(coinsInfo->description().c_str()); - } - - void setUnlocked(bool unlocked); - }; - - Monero::Coins *m_coins; - - Monero::Wallet *m_wallet; - Monero::TransactionHistory *m_transaction_history; - MoneroWalletListener *m_listener; - Monero::Subaddress *m_subaddress; - Monero::SubaddressAccount *m_account; - uint64_t m_last_known_wallet_height; - uint64_t m_cached_syncing_blockchain_height = 0; - std::list m_coins_info; - std::mutex store_lock; - bool is_storing = false; - - void change_current_wallet(Monero::Wallet *wallet) - { - m_wallet = wallet; - m_listener = nullptr; - - - if (wallet != nullptr) - { - m_transaction_history = wallet->history(); - } - else - { - m_transaction_history = nullptr; - } - - if (wallet != nullptr) - { - m_account = wallet->subaddressAccount(); - } - else - { - m_account = nullptr; - } - - if (wallet != nullptr) - { - m_subaddress = wallet->subaddress(); - } - else - { - m_subaddress = nullptr; - } - - m_coins_info = std::list(); - - if (wallet != nullptr) - { - m_coins = wallet->coins(); - } - else - { - m_coins = nullptr; - } - } - - Monero::Wallet *get_current_wallet() - { - return m_wallet; - } - - bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error) - { - Monero::NetworkType _networkType = static_cast(networkType); - Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); - Monero::Wallet *wallet = walletManager->createWallet(path, password, language, _networkType); - - int status; - std::string errorString; - - wallet->statusWithErrorString(status, errorString); - - if (wallet->status() != Monero::Wallet::Status_Ok) - { - error = strdup(wallet->errorString().c_str()); - return false; - } - - change_current_wallet(wallet); - - return true; - } - - bool restore_wallet_from_seed(char *path, char *password, char *seed, int32_t networkType, uint64_t restoreHeight, char *error) - { - Monero::NetworkType _networkType = static_cast(networkType); - Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->recoveryWallet( - std::string(path), - std::string(password), - std::string(seed), - _networkType, - (uint64_t)restoreHeight); - - int status; - std::string errorString; - - wallet->statusWithErrorString(status, errorString); - - if (status != Monero::Wallet::Status_Ok || !errorString.empty()) - { - error = strdup(errorString.c_str()); - return false; - } - - change_current_wallet(wallet); - return true; - } - - bool restore_wallet_from_keys(char *path, char *password, char *language, char *address, char *viewKey, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error) - { - Monero::NetworkType _networkType = static_cast(networkType); - Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createWalletFromKeys( - std::string(path), - std::string(password), - std::string(language), - _networkType, - (uint64_t)restoreHeight, - std::string(address), - std::string(viewKey), - std::string(spendKey)); - - int status; - std::string errorString; - - wallet->statusWithErrorString(status, errorString); - - if (status != Monero::Wallet::Status_Ok || !errorString.empty()) - { - error = strdup(errorString.c_str()); - return false; - } - - change_current_wallet(wallet); - return true; - } - - bool restore_wallet_from_spend_key(char *path, char *password, char *seed, char *language, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error) - { - Monero::NetworkType _networkType = static_cast(networkType); - Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createDeterministicWalletFromSpendKey( - std::string(path), - std::string(password), - std::string(language), - _networkType, - (uint64_t)restoreHeight, - std::string(spendKey)); - - // Cache Raw to support Polyseed - wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); - - int status; - std::string errorString; - - wallet->statusWithErrorString(status, errorString); - - if (status != Monero::Wallet::Status_Ok || !errorString.empty()) - { - error = strdup(errorString.c_str()); - return false; - } - - change_current_wallet(wallet); - return true; - } - - bool load_wallet(char *path, char *password, int32_t nettype) - { - nice(19); - Monero::NetworkType networkType = static_cast(nettype); - Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); - Monero::Wallet *wallet = walletManager->openWallet(std::string(path), std::string(password), networkType); - int status; - std::string errorString; - - wallet->statusWithErrorString(status, errorString); - change_current_wallet(wallet); - - return !(status != Monero::Wallet::Status_Ok || !errorString.empty()); - } - - char *error_string() { - return strdup(get_current_wallet()->errorString().c_str()); - } - - - bool is_wallet_exist(char *path) - { - return Monero::WalletManagerFactory::getWalletManager()->walletExists(std::string(path)); - } - - void close_current_wallet() - { - Monero::WalletManagerFactory::getWalletManager()->closeWallet(get_current_wallet()); - change_current_wallet(nullptr); - } - - char *get_filename() - { - return strdup(get_current_wallet()->filename().c_str()); - } - - char *secret_view_key() - { - return strdup(get_current_wallet()->secretViewKey().c_str()); - } - - char *public_view_key() - { - return strdup(get_current_wallet()->publicViewKey().c_str()); - } - - char *secret_spend_key() - { - return strdup(get_current_wallet()->secretSpendKey().c_str()); - } - - char *public_spend_key() - { - return strdup(get_current_wallet()->publicSpendKey().c_str()); - } - - char *get_address(uint32_t account_index, uint32_t address_index) - { - return strdup(get_current_wallet()->address(account_index, address_index).c_str()); - } - - - const char *seed() - { - std::string _rawSeed = get_current_wallet()->getCacheAttribute("cakewallet.seed"); - if (!_rawSeed.empty()) - { - return strdup(_rawSeed.c_str()); - } - return strdup(get_current_wallet()->seed().c_str()); - } - - uint64_t get_full_balance(uint32_t account_index) - { - return get_current_wallet()->balance(account_index); - } - - uint64_t get_unlocked_balance(uint32_t account_index) - { - return get_current_wallet()->unlockedBalance(account_index); - } - - uint64_t get_current_height() - { - return get_current_wallet()->blockChainHeight(); - } - - uint64_t get_node_height() - { - return get_current_wallet()->daemonBlockChainHeight(); - } - - bool connect_to_node(char *error) - { - nice(19); - bool is_connected = get_current_wallet()->connectToDaemon(); - - if (!is_connected) - { - error = strdup(get_current_wallet()->errorString().c_str()); - } - - return is_connected; - } - - bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *socksProxyAddress, char *error) - { - nice(19); - Monero::Wallet *wallet = get_current_wallet(); - - std::string _login = ""; - std::string _password = ""; - std::string _socksProxyAddress = ""; - - if (login != nullptr) - { - _login = std::string(login); - } - - if (password != nullptr) - { - _password = std::string(password); - } - - if (socksProxyAddress != nullptr) - { - _socksProxyAddress = std::string(socksProxyAddress); - } - - bool inited = wallet->init(std::string(address), 0, _login, _password, use_ssl, is_light_wallet, _socksProxyAddress); - - if (!inited) - { - error = strdup(wallet->errorString().c_str()); - } else if (!wallet->connectToDaemon()) { - error = strdup(wallet->errorString().c_str()); - } - - return inited; - } - - bool is_connected() - { - return get_current_wallet()->connected(); - } - - void start_refresh() - { - get_current_wallet()->refreshAsync(); - get_current_wallet()->startRefresh(); - } - - void set_refresh_from_block_height(uint64_t height) - { - get_current_wallet()->setRefreshFromBlockHeight(height); - } - - void set_recovering_from_seed(bool is_recovery) - { - get_current_wallet()->setRecoveringFromSeed(is_recovery); - } - - void store(char *path) - { - store_lock.lock(); - if (is_storing) { - return; - } - - is_storing = true; - get_current_wallet()->store(std::string(path)); - is_storing = false; - store_lock.unlock(); - } - - bool set_password(char *password, Utf8Box &error) { - bool is_changed = get_current_wallet()->setPassword(std::string(password)); - - if (!is_changed) { - error = Utf8Box(strdup(get_current_wallet()->errorString().c_str())); - } - - return is_changed; - } - - bool transaction_create(char *address, char *payment_id, char *amount, - uint8_t priority_raw, uint32_t subaddr_account, - char **preferred_inputs, uint32_t preferred_inputs_size, - Utf8Box &error, PendingTransactionRaw &pendingTransaction) - { - nice(19); - - std::set _preferred_inputs; - - for (int i = 0; i < preferred_inputs_size; i++) { - _preferred_inputs.insert(std::string(*preferred_inputs)); - preferred_inputs++; - } - - auto priority = static_cast(priority_raw); - std::string _payment_id; - Monero::PendingTransaction *transaction; - - if (payment_id != nullptr) - { - _payment_id = std::string(payment_id); - } - - if (amount != nullptr) - { - uint64_t _amount = Monero::Wallet::amountFromString(std::string(amount)); - transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs); - } - else - { - transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional(), m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs); - } - - int status = transaction->status(); - - if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical) - { - error = Utf8Box(strdup(transaction->errorString().c_str())); - return false; - } - - if (m_listener != nullptr) { - m_listener->m_new_transaction = true; - } - - pendingTransaction = PendingTransactionRaw(transaction); - return true; - } - - bool transaction_create_mult_dest(char **addresses, char *payment_id, char **amounts, uint32_t size, - uint8_t priority_raw, uint32_t subaddr_account, - char **preferred_inputs, uint32_t preferred_inputs_size, - Utf8Box &error, PendingTransactionRaw &pendingTransaction) - { - nice(19); - - std::vector _addresses; - std::vector _amounts; - - for (int i = 0; i < size; i++) { - _addresses.push_back(std::string(*addresses)); - _amounts.push_back(Monero::Wallet::amountFromString(std::string(*amounts))); - addresses++; - amounts++; - } - - std::set _preferred_inputs; - - for (int i = 0; i < preferred_inputs_size; i++) { - _preferred_inputs.insert(std::string(*preferred_inputs)); - preferred_inputs++; - } - - auto priority = static_cast(priority_raw); - std::string _payment_id; - Monero::PendingTransaction *transaction; - - if (payment_id != nullptr) - { - _payment_id = std::string(payment_id); - } - - transaction = m_wallet->createTransactionMultDest(_addresses, _payment_id, _amounts, m_wallet->defaultMixin(), priority, subaddr_account); - - int status = transaction->status(); - - if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical) - { - error = Utf8Box(strdup(transaction->errorString().c_str())); - return false; - } - - if (m_listener != nullptr) { - m_listener->m_new_transaction = true; - } - - pendingTransaction = PendingTransactionRaw(transaction); - return true; - } - - bool transaction_commit(PendingTransactionRaw *transaction, Utf8Box &error) - { - bool committed = transaction->transaction->commit(); - - if (!committed) - { - error = Utf8Box(strdup(transaction->transaction->errorString().c_str())); - } else if (m_listener != nullptr) { - m_listener->m_new_transaction = true; - } - - return committed; - } - - uint64_t get_node_height_or_update(uint64_t base_eight) - { - if (m_cached_syncing_blockchain_height < base_eight) { - m_cached_syncing_blockchain_height = base_eight; - } - - return m_cached_syncing_blockchain_height; - } - - uint64_t get_syncing_height() - { - if (m_listener == nullptr) { - return 0; - } - - uint64_t height = m_listener->height(); - - if (height <= 1) { - return 0; - } - - if (height != m_last_known_wallet_height) - { - m_last_known_wallet_height = height; - } - - return height; - } - - uint64_t is_needed_to_refresh() - { - if (m_listener == nullptr) { - return false; - } - - bool should_refresh = m_listener->isNeedToRefresh(); - - if (should_refresh) { - m_listener->resetNeedToRefresh(); - } - - return should_refresh; - } - - uint8_t is_new_transaction_exist() - { - if (m_listener == nullptr) { - return false; - } - - bool is_new_transaction_exist = m_listener->isNewTransactionExist(); - - if (is_new_transaction_exist) - { - m_listener->resetIsNewTransactionExist(); - } - - return is_new_transaction_exist; - } - - void set_listener() - { - m_last_known_wallet_height = 0; - - if (m_listener != nullptr) - { - free(m_listener); - } - - m_listener = new MoneroWalletListener(); - get_current_wallet()->setListener(m_listener); - } - - int64_t *subaddrress_get_all() - { - std::vector _subaddresses = m_subaddress->getAll(); - size_t size = _subaddresses.size(); - int64_t *subaddresses = (int64_t *)malloc(size * sizeof(int64_t)); - - for (int i = 0; i < size; i++) - { - Monero::SubaddressRow *row = _subaddresses[i]; - SubaddressRow *_row = new SubaddressRow(row->getRowId(), strdup(row->getAddress().c_str()), strdup(row->getLabel().c_str())); - subaddresses[i] = reinterpret_cast(_row); - } - - return subaddresses; - } - - int32_t subaddrress_size() - { - std::vector _subaddresses = m_subaddress->getAll(); - return _subaddresses.size(); - } - - void subaddress_add_row(uint32_t accountIndex, char *label) - { - m_subaddress->addRow(accountIndex, std::string(label)); - } - - void subaddress_set_label(uint32_t accountIndex, uint32_t addressIndex, char *label) - { - m_subaddress->setLabel(accountIndex, addressIndex, std::string(label)); - } - - void subaddress_refresh(uint32_t accountIndex) - { - m_subaddress->refresh(accountIndex); - } - - int32_t account_size() - { - std::vector _accocunts = m_account->getAll(); - return _accocunts.size(); - } - - int64_t *account_get_all() - { - std::vector _accocunts = m_account->getAll(); - size_t size = _accocunts.size(); - int64_t *accocunts = (int64_t *)malloc(size * sizeof(int64_t)); - - for (int i = 0; i < size; i++) - { - Monero::SubaddressAccountRow *row = _accocunts[i]; - AccountRow *_row = new AccountRow(row->getRowId(), strdup(row->getLabel().c_str())); - accocunts[i] = reinterpret_cast(_row); - } - - return accocunts; - } - - void account_add_row(char *label) - { - m_account->addRow(std::string(label)); - } - - void account_set_label_row(uint32_t account_index, char *label) - { - m_account->setLabel(account_index, label); - } - - void account_refresh() - { - m_account->refresh(); - } - - int64_t *transactions_get_all() - { - std::vector transactions = m_transaction_history->getAll(); - size_t size = transactions.size(); - int64_t *transactionAddresses = (int64_t *)malloc(size * sizeof(int64_t)); - - for (int i = 0; i < size; i++) - { - Monero::TransactionInfo *row = transactions[i]; - TransactionInfoRow *tx = new TransactionInfoRow(row); - transactionAddresses[i] = reinterpret_cast(tx); - } - - return transactionAddresses; - } - - void transactions_refresh() - { - m_transaction_history->refresh(); - } - - int64_t transactions_count() - { - return m_transaction_history->count(); - } - - TransactionInfoRow* get_transaction(char * txId) - { - Monero::TransactionInfo *row = m_transaction_history->transaction(std::string(txId)); - return new TransactionInfoRow(row); - } - - int LedgerExchange( - unsigned char *command, - unsigned int cmd_len, - unsigned char *response, - unsigned int max_resp_len) - { - return -1; - } - - int LedgerFind(char *buffer, size_t len) - { - return -1; - } - - void on_startup() - { - Monero::Utils::onStartup(); - Monero::WalletManagerFactory::setLogLevel(0); - } - - void rescan_blockchain() - { - m_wallet->rescanBlockchainAsync(); - } - - char * get_tx_key(char * txId) - { - return strdup(m_wallet->getTxKey(std::string(txId)).c_str()); - } - - char *get_subaddress_label(uint32_t accountIndex, uint32_t addressIndex) - { - return strdup(get_current_wallet()->getSubaddressLabel(accountIndex, addressIndex).c_str()); - } - - void set_trusted_daemon(bool arg) - { - m_wallet->setTrustedDaemon(arg); - } - - bool trusted_daemon() - { - return m_wallet->trustedDaemon(); - } - - // Coin Control // - - CoinsInfoRow* coin(int index) - { - if (index >= 0 && index < m_coins_info.size()) { - std::list::iterator it = m_coins_info.begin(); - std::advance(it, index); - Monero::CoinsInfo* element = *it; - std::cout << "Element at index " << index << ": " << element << std::endl; - return new CoinsInfoRow(element); - } else { - std::cout << "Invalid index." << std::endl; - return nullptr; // Return a default value (nullptr) for invalid index - } - } - - void refresh_coins(uint32_t accountIndex) - { - m_coins_info.clear(); - - m_coins->refresh(); - for (const auto i : m_coins->getAll()) { - if (i->subaddrAccount() == accountIndex && !(i->spent())) { - m_coins_info.push_back(i); - } - } - } - - uint64_t coins_count() - { - return m_coins_info.size(); - } - - CoinsInfoRow** coins_from_account(uint32_t accountIndex) - { - std::vector matchingCoins; - - for (int i = 0; i < coins_count(); i++) { - CoinsInfoRow* coinInfo = coin(i); - if (coinInfo->subaddrAccount == accountIndex) { - matchingCoins.push_back(coinInfo); - } - } - - CoinsInfoRow** result = new CoinsInfoRow*[matchingCoins.size()]; - std::copy(matchingCoins.begin(), matchingCoins.end(), result); - return result; - } - - CoinsInfoRow** coins_from_txid(const char* txid, size_t* count) - { - std::vector matchingCoins; - - for (int i = 0; i < coins_count(); i++) { - CoinsInfoRow* coinInfo = coin(i); - if (std::string(coinInfo->hash) == txid) { - matchingCoins.push_back(coinInfo); - } - } - - *count = matchingCoins.size(); - CoinsInfoRow** result = new CoinsInfoRow*[*count]; - std::copy(matchingCoins.begin(), matchingCoins.end(), result); - return result; - } - - CoinsInfoRow** coins_from_key_image(const char** keyimages, size_t keyimageCount, size_t* count) - { - std::vector matchingCoins; - - for (int i = 0; i < coins_count(); i++) { - CoinsInfoRow* coinsInfoRow = coin(i); - for (size_t j = 0; j < keyimageCount; j++) { - if (coinsInfoRow->keyImageKnown && std::string(coinsInfoRow->keyImage) == keyimages[j]) { - matchingCoins.push_back(coinsInfoRow); - break; - } - } - } - - *count = matchingCoins.size(); - CoinsInfoRow** result = new CoinsInfoRow*[*count]; - std::copy(matchingCoins.begin(), matchingCoins.end(), result); - return result; - } - - void freeze_coin(int index) - { - m_coins->setFrozen(index); - } - - void thaw_coin(int index) - { - m_coins->thaw(index); - } - - // Sign Messages // - - char *sign_message(char *message, char *address = "") - { - return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str()); - } - -#ifdef __cplusplus -} -#endif diff --git a/cw_monero/ios/Classes/monero_api.h b/cw_monero/ios/Classes/monero_api.h deleted file mode 100644 index fa92a038dc..0000000000 --- a/cw_monero/ios/Classes/monero_api.h +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include -#include -#include "CwWalletListener.h" - -#ifdef __cplusplus -extern "C" { -#endif - -bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error); -bool restore_wallet_from_seed(char *path, char *password, char *seed, int32_t networkType, uint64_t restoreHeight, char *error); -bool restore_wallet_from_keys(char *path, char *password, char *language, char *address, char *viewKey, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error); -void load_wallet(char *path, char *password, int32_t nettype); -bool is_wallet_exist(char *path); - -char *get_filename(); -const char *seed(); -char *get_address(uint32_t account_index, uint32_t address_index); -uint64_t get_full_balance(uint32_t account_index); -uint64_t get_unlocked_balance(uint32_t account_index); -uint64_t get_current_height(); -uint64_t get_node_height(); - -bool is_connected(); - -bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error); -bool connect_to_node(char *error); -void start_refresh(); -void set_refresh_from_block_height(uint64_t height); -void set_recovering_from_seed(bool is_recovery); -void store(char *path); - -void set_trusted_daemon(bool arg); -bool trusted_daemon(); -char *sign_message(char *message, char *address); - -#ifdef __cplusplus -} -#endif diff --git a/cw_monero/ios/cw_monero.podspec b/cw_monero/ios/cw_monero.podspec deleted file mode 100644 index d99bba923f..0000000000 --- a/cw_monero/ios/cw_monero.podspec +++ /dev/null @@ -1,62 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint cw_monero.podspec' to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'cw_monero' - s.version = '0.0.2' - s.summary = 'CW Monero' - s.description = 'Cake Wallet wrapper over Monero project.' - s.homepage = 'http://cakewallet.com' - s.license = { :file => '../LICENSE' } - s.author = { 'CakeWallet' => 'support@cakewallet.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h, Classes/*.h, External/ios/libs/monero/include/External/ios/**/*.h' - s.dependency 'Flutter' - s.dependency 'cw_shared_external' - s.platform = :ios, '10.0' - s.swift_version = '4.0' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'arm64', 'ENABLE_BITCODE' => 'NO' } - s.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/Classes/*.h" } - - s.subspec 'OpenSSL' do |openssl| - openssl.preserve_paths = '../../../../../cw_shared_external/ios/External/ios/include/**/*.h' - openssl.vendored_libraries = '../../../../../cw_shared_external/ios/External/ios/lib/libcrypto.a', '../../../../../cw_shared_external/ios/External/ios/lib/libssl.a' - openssl.libraries = 'ssl', 'crypto' - openssl.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/include/**" } - end - - s.subspec 'Sodium' do |sodium| - sodium.preserve_paths = '../../../../../cw_shared_external/ios/External/ios/include/**/*.h' - sodium.vendored_libraries = '../../../../../cw_shared_external/ios/External/ios/lib/libsodium.a' - sodium.libraries = 'sodium' - sodium.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/include/**" } - end - - s.subspec 'Unbound' do |unbound| - unbound.preserve_paths = '../../../../../cw_shared_external/ios/External/ios/include/**/*.h' - unbound.vendored_libraries = '../../../../../cw_shared_external/ios/External/ios/lib/libunbound.a' - unbound.libraries = 'unbound' - unbound.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/include/**" } - end - - s.subspec 'Boost' do |boost| - boost.preserve_paths = '../../../../../cw_shared_external/ios/External/ios/include/**/*.h', - boost.vendored_libraries = '../../../../../cw_shared_external/ios/External/ios/lib/libboost.a', - boost.libraries = 'boost' - boost.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/include/**" } - end - - s.subspec 'Monero' do |monero| - monero.preserve_paths = 'External/ios/include/**/*.h' - monero.vendored_libraries = 'External/ios/lib/libmonero.a' - monero.libraries = 'monero' - monero.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/include" } - end - - # s.subspec 'lmdb' do |lmdb| - # lmdb.vendored_libraries = 'External/ios/lib/liblmdb.a' - # lmdb.libraries = 'lmdb' - # end -end diff --git a/cw_monero/macos/Classes/CwMoneroPlugin.swift b/cw_monero/macos/Classes/CwMoneroPlugin.swift deleted file mode 100644 index d4ff81e1cc..0000000000 --- a/cw_monero/macos/Classes/CwMoneroPlugin.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Cocoa -import FlutterMacOS - -public class CwMoneroPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "cw_monero", binaryMessenger: registrar.messenger) - let instance = CwMoneroPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) - default: - result(FlutterMethodNotImplemented) - } - } -} diff --git a/cw_monero/macos/Classes/CwWalletListener.h b/cw_monero/macos/Classes/CwWalletListener.h deleted file mode 100644 index cbfcb0c4e9..0000000000 --- a/cw_monero/macos/Classes/CwWalletListener.h +++ /dev/null @@ -1,23 +0,0 @@ -#include - -struct CWMoneroWalletListener; - -typedef int8_t (*on_new_block_callback)(uint64_t height); -typedef int8_t (*on_need_to_refresh_callback)(); - -typedef struct CWMoneroWalletListener -{ - // on_money_spent_callback *on_money_spent; - // on_money_received_callback *on_money_received; - // on_unconfirmed_money_received_callback *on_unconfirmed_money_received; - // on_new_block_callback *on_new_block; - // on_updated_callback *on_updated; - // on_refreshed_callback *on_refreshed; - - on_new_block_callback on_new_block; -} CWMoneroWalletListener; - -struct TestListener { - // int8_t x; - on_new_block_callback on_new_block; -}; \ No newline at end of file diff --git a/cw_monero/macos/Classes/monero_api.cpp b/cw_monero/macos/Classes/monero_api.cpp deleted file mode 100644 index fe75dea981..0000000000 --- a/cw_monero/macos/Classes/monero_api.cpp +++ /dev/null @@ -1,1032 +0,0 @@ -#include -#include "cstdlib" -#include -#include -#include -#include -#include -#include -#include -#include "thread" -#include "CwWalletListener.h" -#if __APPLE__ -// Fix for randomx on ios -void __clear_cache(void* start, void* end) { } -#include "../External/macos/include/wallet2_api.h" -#else -#include "../External/android/include/wallet2_api.h" -#endif - -using namespace std::chrono_literals; -#ifdef __cplusplus -extern "C" -{ -#endif - const uint64_t MONERO_BLOCK_SIZE = 1000; - - struct Utf8Box - { - char *value; - - Utf8Box(char *_value) - { - value = _value; - } - }; - - struct SubaddressRow - { - uint64_t id; - char *address; - char *label; - - SubaddressRow(std::size_t _id, char *_address, char *_label) - { - id = static_cast(_id); - address = _address; - label = _label; - } - }; - - struct AccountRow - { - uint64_t id; - char *label; - - AccountRow(std::size_t _id, char *_label) - { - id = static_cast(_id); - label = _label; - } - }; - - struct MoneroWalletListener : Monero::WalletListener - { - uint64_t m_height; - bool m_need_to_refresh; - bool m_new_transaction; - - MoneroWalletListener() - { - m_height = 0; - m_need_to_refresh = false; - m_new_transaction = false; - } - - void moneySpent(const std::string &txId, uint64_t amount) - { - m_new_transaction = true; - } - - void moneyReceived(const std::string &txId, uint64_t amount) - { - m_new_transaction = true; - } - - void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) - { - m_new_transaction = true; - } - - void newBlock(uint64_t height) - { - m_height = height; - } - - void updated() - { - m_new_transaction = true; - } - - void refreshed() - { - m_need_to_refresh = true; - } - - void resetNeedToRefresh() - { - m_need_to_refresh = false; - } - - bool isNeedToRefresh() - { - return m_need_to_refresh; - } - - bool isNewTransactionExist() - { - return m_new_transaction; - } - - void resetIsNewTransactionExist() - { - m_new_transaction = false; - } - - uint64_t height() - { - return m_height; - } - }; - - struct TransactionInfoRow - { - uint64_t amount; - uint64_t fee; - uint64_t blockHeight; - uint64_t confirmations; - uint32_t subaddrAccount; - int8_t direction; - int8_t isPending; - uint32_t subaddrIndex; - - char *hash; - char *paymentId; - - int64_t datetime; - - TransactionInfoRow(Monero::TransactionInfo *transaction) - { - amount = transaction->amount(); - fee = transaction->fee(); - blockHeight = transaction->blockHeight(); - subaddrAccount = transaction->subaddrAccount(); - std::set::iterator it = transaction->subaddrIndex().begin(); - subaddrIndex = *it; - confirmations = transaction->confirmations(); - datetime = static_cast(transaction->timestamp()); - direction = transaction->direction(); - isPending = static_cast(transaction->isPending()); - std::string *hash_str = new std::string(transaction->hash()); - hash = strdup(hash_str->c_str()); - paymentId = strdup(transaction->paymentId().c_str()); - } - }; - - struct PendingTransactionRaw - { - uint64_t amount; - uint64_t fee; - char *hash; - char *hex; - char *txKey; - Monero::PendingTransaction *transaction; - - PendingTransactionRaw(Monero::PendingTransaction *_transaction) - { - transaction = _transaction; - amount = _transaction->amount(); - fee = _transaction->fee(); - hash = strdup(_transaction->txid()[0].c_str()); - hex = strdup(_transaction->hex()[0].c_str()); - txKey = strdup(_transaction->txKey()[0].c_str()); - } - }; - - struct CoinsInfoRow - { - uint64_t blockHeight; - char *hash; - uint64_t internalOutputIndex; - uint64_t globalOutputIndex; - bool spent; - bool frozen; - uint64_t spentHeight; - uint64_t amount; - bool rct; - bool keyImageKnown; - uint64_t pkIndex; - uint32_t subaddrIndex; - uint32_t subaddrAccount; - char *address; - char *addressLabel; - char *keyImage; - uint64_t unlockTime; - bool unlocked; - char *pubKey; - bool coinbase; - char *description; - - CoinsInfoRow(Monero::CoinsInfo *coinsInfo) - { - blockHeight = coinsInfo->blockHeight(); - std::string *hash_str = new std::string(coinsInfo->hash()); - hash = strdup(hash_str->c_str()); - internalOutputIndex = coinsInfo->internalOutputIndex(); - globalOutputIndex = coinsInfo->globalOutputIndex(); - spent = coinsInfo->spent(); - frozen = coinsInfo->frozen(); - spentHeight = coinsInfo->spentHeight(); - amount = coinsInfo->amount(); - rct = coinsInfo->rct(); - keyImageKnown = coinsInfo->keyImageKnown(); - pkIndex = coinsInfo->pkIndex(); - subaddrIndex = coinsInfo->subaddrIndex(); - subaddrAccount = coinsInfo->subaddrAccount(); - address = strdup(coinsInfo->address().c_str()) ; - addressLabel = strdup(coinsInfo->addressLabel().c_str()); - keyImage = strdup(coinsInfo->keyImage().c_str()); - unlockTime = coinsInfo->unlockTime(); - unlocked = coinsInfo->unlocked(); - pubKey = strdup(coinsInfo->pubKey().c_str()); - coinbase = coinsInfo->coinbase(); - description = strdup(coinsInfo->description().c_str()); - } - - void setUnlocked(bool unlocked); - }; - - Monero::Coins *m_coins; - - Monero::Wallet *m_wallet; - Monero::TransactionHistory *m_transaction_history; - MoneroWalletListener *m_listener; - Monero::Subaddress *m_subaddress; - Monero::SubaddressAccount *m_account; - uint64_t m_last_known_wallet_height; - uint64_t m_cached_syncing_blockchain_height = 0; - std::list m_coins_info; - std::mutex store_lock; - bool is_storing = false; - - void change_current_wallet(Monero::Wallet *wallet) - { - m_wallet = wallet; - m_listener = nullptr; - - - if (wallet != nullptr) - { - m_transaction_history = wallet->history(); - } - else - { - m_transaction_history = nullptr; - } - - if (wallet != nullptr) - { - m_account = wallet->subaddressAccount(); - } - else - { - m_account = nullptr; - } - - if (wallet != nullptr) - { - m_subaddress = wallet->subaddress(); - } - else - { - m_subaddress = nullptr; - } - - m_coins_info = std::list(); - - if (wallet != nullptr) - { - m_coins = wallet->coins(); - } - else - { - m_coins = nullptr; - } - } - - Monero::Wallet *get_current_wallet() - { - return m_wallet; - } - - bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error) - { - Monero::NetworkType _networkType = static_cast(networkType); - Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); - Monero::Wallet *wallet = walletManager->createWallet(path, password, language, _networkType); - - int status; - std::string errorString; - - wallet->statusWithErrorString(status, errorString); - - if (wallet->status() != Monero::Wallet::Status_Ok) - { - error = strdup(wallet->errorString().c_str()); - return false; - } - - change_current_wallet(wallet); - - return true; - } - - bool restore_wallet_from_seed(char *path, char *password, char *seed, int32_t networkType, uint64_t restoreHeight, char *error) - { - Monero::NetworkType _networkType = static_cast(networkType); - Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->recoveryWallet( - std::string(path), - std::string(password), - std::string(seed), - _networkType, - (uint64_t)restoreHeight); - - int status; - std::string errorString; - - wallet->statusWithErrorString(status, errorString); - - if (status != Monero::Wallet::Status_Ok || !errorString.empty()) - { - error = strdup(errorString.c_str()); - return false; - } - - change_current_wallet(wallet); - return true; - } - - bool restore_wallet_from_keys(char *path, char *password, char *language, char *address, char *viewKey, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error) - { - Monero::NetworkType _networkType = static_cast(networkType); - Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createWalletFromKeys( - std::string(path), - std::string(password), - std::string(language), - _networkType, - (uint64_t)restoreHeight, - std::string(address), - std::string(viewKey), - std::string(spendKey)); - - int status; - std::string errorString; - - wallet->statusWithErrorString(status, errorString); - - if (status != Monero::Wallet::Status_Ok || !errorString.empty()) - { - error = strdup(errorString.c_str()); - return false; - } - - change_current_wallet(wallet); - return true; - } - - bool restore_wallet_from_spend_key(char *path, char *password, char *seed, char *language, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error) - { - Monero::NetworkType _networkType = static_cast(networkType); - Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createDeterministicWalletFromSpendKey( - std::string(path), - std::string(password), - std::string(language), - _networkType, - (uint64_t)restoreHeight, - std::string(spendKey)); - - // Cache Raw to support Polyseed - wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); - - int status; - std::string errorString; - - wallet->statusWithErrorString(status, errorString); - - if (status != Monero::Wallet::Status_Ok || !errorString.empty()) - { - error = strdup(errorString.c_str()); - return false; - } - - change_current_wallet(wallet); - return true; - } - - bool load_wallet(char *path, char *password, int32_t nettype) - { - nice(19); - Monero::NetworkType networkType = static_cast(nettype); - Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); - Monero::Wallet *wallet = walletManager->openWallet(std::string(path), std::string(password), networkType); - int status; - std::string errorString; - - wallet->statusWithErrorString(status, errorString); - change_current_wallet(wallet); - - return !(status != Monero::Wallet::Status_Ok || !errorString.empty()); - } - - char *error_string() { - return strdup(get_current_wallet()->errorString().c_str()); - } - - - bool is_wallet_exist(char *path) - { - return Monero::WalletManagerFactory::getWalletManager()->walletExists(std::string(path)); - } - - void close_current_wallet() - { - Monero::WalletManagerFactory::getWalletManager()->closeWallet(get_current_wallet()); - change_current_wallet(nullptr); - } - - char *get_filename() - { - return strdup(get_current_wallet()->filename().c_str()); - } - - char *secret_view_key() - { - return strdup(get_current_wallet()->secretViewKey().c_str()); - } - - char *public_view_key() - { - return strdup(get_current_wallet()->publicViewKey().c_str()); - } - - char *secret_spend_key() - { - return strdup(get_current_wallet()->secretSpendKey().c_str()); - } - - char *public_spend_key() - { - return strdup(get_current_wallet()->publicSpendKey().c_str()); - } - - char *get_address(uint32_t account_index, uint32_t address_index) - { - return strdup(get_current_wallet()->address(account_index, address_index).c_str()); - } - - - const char *seed() - { - std::string _rawSeed = get_current_wallet()->getCacheAttribute("cakewallet.seed"); - if (!_rawSeed.empty()) - { - return strdup(_rawSeed.c_str()); - } - return strdup(get_current_wallet()->seed().c_str()); - } - - uint64_t get_full_balance(uint32_t account_index) - { - return get_current_wallet()->balance(account_index); - } - - uint64_t get_unlocked_balance(uint32_t account_index) - { - return get_current_wallet()->unlockedBalance(account_index); - } - - uint64_t get_current_height() - { - return get_current_wallet()->blockChainHeight(); - } - - uint64_t get_node_height() - { - return get_current_wallet()->daemonBlockChainHeight(); - } - - bool connect_to_node(char *error) - { - nice(19); - bool is_connected = get_current_wallet()->connectToDaemon(); - - if (!is_connected) - { - error = strdup(get_current_wallet()->errorString().c_str()); - } - - return is_connected; - } - - bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *socksProxyAddress, char *error) - { - nice(19); - Monero::Wallet *wallet = get_current_wallet(); - - std::string _login = ""; - std::string _password = ""; - std::string _socksProxyAddress = ""; - - if (login != nullptr) - { - _login = std::string(login); - } - - if (password != nullptr) - { - _password = std::string(password); - } - - if (socksProxyAddress != nullptr) - { - _socksProxyAddress = std::string(socksProxyAddress); - } - - bool inited = wallet->init(std::string(address), 0, _login, _password, use_ssl, is_light_wallet, _socksProxyAddress); - - if (!inited) - { - error = strdup(wallet->errorString().c_str()); - } else if (!wallet->connectToDaemon()) { - error = strdup(wallet->errorString().c_str()); - } - - return inited; - } - - bool is_connected() - { - return get_current_wallet()->connected(); - } - - void start_refresh() - { - get_current_wallet()->refreshAsync(); - get_current_wallet()->startRefresh(); - } - - void set_refresh_from_block_height(uint64_t height) - { - get_current_wallet()->setRefreshFromBlockHeight(height); - } - - void set_recovering_from_seed(bool is_recovery) - { - get_current_wallet()->setRecoveringFromSeed(is_recovery); - } - - void store(char *path) - { - store_lock.lock(); - if (is_storing) { - return; - } - - is_storing = true; - get_current_wallet()->store(std::string(path)); - is_storing = false; - store_lock.unlock(); - } - - bool set_password(char *password, Utf8Box &error) { - bool is_changed = get_current_wallet()->setPassword(std::string(password)); - - if (!is_changed) { - error = Utf8Box(strdup(get_current_wallet()->errorString().c_str())); - } - - return is_changed; - } - - bool transaction_create(char *address, char *payment_id, char *amount, - uint8_t priority_raw, uint32_t subaddr_account, - char **preferred_inputs, uint32_t preferred_inputs_size, - Utf8Box &error, PendingTransactionRaw &pendingTransaction) - { - nice(19); - - std::set _preferred_inputs; - - for (int i = 0; i < preferred_inputs_size; i++) { - _preferred_inputs.insert(std::string(*preferred_inputs)); - preferred_inputs++; - } - - auto priority = static_cast(priority_raw); - std::string _payment_id; - Monero::PendingTransaction *transaction; - - if (payment_id != nullptr) - { - _payment_id = std::string(payment_id); - } - - if (amount != nullptr) - { - uint64_t _amount = Monero::Wallet::amountFromString(std::string(amount)); - transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs); - } - else - { - transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional(), m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs); - } - - int status = transaction->status(); - - if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical) - { - error = Utf8Box(strdup(transaction->errorString().c_str())); - return false; - } - - if (m_listener != nullptr) { - m_listener->m_new_transaction = true; - } - - pendingTransaction = PendingTransactionRaw(transaction); - return true; - } - - bool transaction_create_mult_dest(char **addresses, char *payment_id, char **amounts, uint32_t size, - uint8_t priority_raw, uint32_t subaddr_account, - char **preferred_inputs, uint32_t preferred_inputs_size, - Utf8Box &error, PendingTransactionRaw &pendingTransaction) - { - nice(19); - - std::vector _addresses; - std::vector _amounts; - - for (int i = 0; i < size; i++) { - _addresses.push_back(std::string(*addresses)); - _amounts.push_back(Monero::Wallet::amountFromString(std::string(*amounts))); - addresses++; - amounts++; - } - - std::set _preferred_inputs; - - for (int i = 0; i < preferred_inputs_size; i++) { - _preferred_inputs.insert(std::string(*preferred_inputs)); - preferred_inputs++; - } - - auto priority = static_cast(priority_raw); - std::string _payment_id; - Monero::PendingTransaction *transaction; - - if (payment_id != nullptr) - { - _payment_id = std::string(payment_id); - } - - transaction = m_wallet->createTransactionMultDest(_addresses, _payment_id, _amounts, m_wallet->defaultMixin(), priority, subaddr_account); - - int status = transaction->status(); - - if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical) - { - error = Utf8Box(strdup(transaction->errorString().c_str())); - return false; - } - - if (m_listener != nullptr) { - m_listener->m_new_transaction = true; - } - - pendingTransaction = PendingTransactionRaw(transaction); - return true; - } - - bool transaction_commit(PendingTransactionRaw *transaction, Utf8Box &error) - { - bool committed = transaction->transaction->commit(); - - if (!committed) - { - error = Utf8Box(strdup(transaction->transaction->errorString().c_str())); - } else if (m_listener != nullptr) { - m_listener->m_new_transaction = true; - } - - return committed; - } - - uint64_t get_node_height_or_update(uint64_t base_eight) - { - if (m_cached_syncing_blockchain_height < base_eight) { - m_cached_syncing_blockchain_height = base_eight; - } - - return m_cached_syncing_blockchain_height; - } - - uint64_t get_syncing_height() - { - if (m_listener == nullptr) { - return 0; - } - - uint64_t height = m_listener->height(); - - if (height <= 1) { - return 0; - } - - if (height != m_last_known_wallet_height) - { - m_last_known_wallet_height = height; - } - - return height; - } - - uint64_t is_needed_to_refresh() - { - if (m_listener == nullptr) { - return false; - } - - bool should_refresh = m_listener->isNeedToRefresh(); - - if (should_refresh) { - m_listener->resetNeedToRefresh(); - } - - return should_refresh; - } - - uint8_t is_new_transaction_exist() - { - if (m_listener == nullptr) { - return false; - } - - bool is_new_transaction_exist = m_listener->isNewTransactionExist(); - - if (is_new_transaction_exist) - { - m_listener->resetIsNewTransactionExist(); - } - - return is_new_transaction_exist; - } - - void set_listener() - { - m_last_known_wallet_height = 0; - - if (m_listener != nullptr) - { - free(m_listener); - } - - m_listener = new MoneroWalletListener(); - get_current_wallet()->setListener(m_listener); - } - - int64_t *subaddrress_get_all() - { - std::vector _subaddresses = m_subaddress->getAll(); - size_t size = _subaddresses.size(); - int64_t *subaddresses = (int64_t *)malloc(size * sizeof(int64_t)); - - for (int i = 0; i < size; i++) - { - Monero::SubaddressRow *row = _subaddresses[i]; - SubaddressRow *_row = new SubaddressRow(row->getRowId(), strdup(row->getAddress().c_str()), strdup(row->getLabel().c_str())); - subaddresses[i] = reinterpret_cast(_row); - } - - return subaddresses; - } - - int32_t subaddrress_size() - { - std::vector _subaddresses = m_subaddress->getAll(); - return _subaddresses.size(); - } - - void subaddress_add_row(uint32_t accountIndex, char *label) - { - m_subaddress->addRow(accountIndex, std::string(label)); - } - - void subaddress_set_label(uint32_t accountIndex, uint32_t addressIndex, char *label) - { - m_subaddress->setLabel(accountIndex, addressIndex, std::string(label)); - } - - void subaddress_refresh(uint32_t accountIndex) - { - m_subaddress->refresh(accountIndex); - } - - int32_t account_size() - { - std::vector _accocunts = m_account->getAll(); - return _accocunts.size(); - } - - int64_t *account_get_all() - { - std::vector _accocunts = m_account->getAll(); - size_t size = _accocunts.size(); - int64_t *accocunts = (int64_t *)malloc(size * sizeof(int64_t)); - - for (int i = 0; i < size; i++) - { - Monero::SubaddressAccountRow *row = _accocunts[i]; - AccountRow *_row = new AccountRow(row->getRowId(), strdup(row->getLabel().c_str())); - accocunts[i] = reinterpret_cast(_row); - } - - return accocunts; - } - - void account_add_row(char *label) - { - m_account->addRow(std::string(label)); - } - - void account_set_label_row(uint32_t account_index, char *label) - { - m_account->setLabel(account_index, label); - } - - void account_refresh() - { - m_account->refresh(); - } - - int64_t *transactions_get_all() - { - std::vector transactions = m_transaction_history->getAll(); - size_t size = transactions.size(); - int64_t *transactionAddresses = (int64_t *)malloc(size * sizeof(int64_t)); - - for (int i = 0; i < size; i++) - { - Monero::TransactionInfo *row = transactions[i]; - TransactionInfoRow *tx = new TransactionInfoRow(row); - transactionAddresses[i] = reinterpret_cast(tx); - } - - return transactionAddresses; - } - - void transactions_refresh() - { - m_transaction_history->refresh(); - } - - int64_t transactions_count() - { - return m_transaction_history->count(); - } - - TransactionInfoRow* get_transaction(char * txId) - { - Monero::TransactionInfo *row = m_transaction_history->transaction(std::string(txId)); - return new TransactionInfoRow(row); - } - - int LedgerExchange( - unsigned char *command, - unsigned int cmd_len, - unsigned char *response, - unsigned int max_resp_len) - { - return -1; - } - - int LedgerFind(char *buffer, size_t len) - { - return -1; - } - - void on_startup() - { - Monero::Utils::onStartup(); - Monero::WalletManagerFactory::setLogLevel(0); - } - - void rescan_blockchain() - { - m_wallet->rescanBlockchainAsync(); - } - - char * get_tx_key(char * txId) - { - return strdup(m_wallet->getTxKey(std::string(txId)).c_str()); - } - - char *get_subaddress_label(uint32_t accountIndex, uint32_t addressIndex) - { - return strdup(get_current_wallet()->getSubaddressLabel(accountIndex, addressIndex).c_str()); - } - - void set_trusted_daemon(bool arg) - { - m_wallet->setTrustedDaemon(arg); - } - - bool trusted_daemon() - { - return m_wallet->trustedDaemon(); - } - - CoinsInfoRow* coin(int index) - { - if (index >= 0 && index < m_coins_info.size()) { - std::list::iterator it = m_coins_info.begin(); - std::advance(it, index); - Monero::CoinsInfo* element = *it; - std::cout << "Element at index " << index << ": " << element << std::endl; - return new CoinsInfoRow(element); - } else { - std::cout << "Invalid index." << std::endl; - return nullptr; // Return a default value (nullptr) for invalid index - } - } - - void refresh_coins(uint32_t accountIndex) - { - m_coins_info.clear(); - - m_coins->refresh(); - for (const auto i : m_coins->getAll()) { - if (i->subaddrAccount() == accountIndex && !(i->spent())) { - m_coins_info.push_back(i); - } - } - } - - uint64_t coins_count() - { - return m_coins_info.size(); - } - - CoinsInfoRow** coins_from_account(uint32_t accountIndex) - { - std::vector matchingCoins; - - for (int i = 0; i < coins_count(); i++) { - CoinsInfoRow* coinInfo = coin(i); - if (coinInfo->subaddrAccount == accountIndex) { - matchingCoins.push_back(coinInfo); - } - } - - CoinsInfoRow** result = new CoinsInfoRow*[matchingCoins.size()]; - std::copy(matchingCoins.begin(), matchingCoins.end(), result); - return result; - } - - CoinsInfoRow** coins_from_txid(const char* txid, size_t* count) - { - std::vector matchingCoins; - - for (int i = 0; i < coins_count(); i++) { - CoinsInfoRow* coinInfo = coin(i); - if (std::string(coinInfo->hash) == txid) { - matchingCoins.push_back(coinInfo); - } - } - - *count = matchingCoins.size(); - CoinsInfoRow** result = new CoinsInfoRow*[*count]; - std::copy(matchingCoins.begin(), matchingCoins.end(), result); - return result; - } - - CoinsInfoRow** coins_from_key_image(const char** keyimages, size_t keyimageCount, size_t* count) - { - std::vector matchingCoins; - - for (int i = 0; i < coins_count(); i++) { - CoinsInfoRow* coinsInfoRow = coin(i); - for (size_t j = 0; j < keyimageCount; j++) { - if (coinsInfoRow->keyImageKnown && std::string(coinsInfoRow->keyImage) == keyimages[j]) { - matchingCoins.push_back(coinsInfoRow); - break; - } - } - } - - *count = matchingCoins.size(); - CoinsInfoRow** result = new CoinsInfoRow*[*count]; - std::copy(matchingCoins.begin(), matchingCoins.end(), result); - return result; - } - - void freeze_coin(int index) - { - m_coins->setFrozen(index); - } - - void thaw_coin(int index) - { - m_coins->thaw(index); - } - - // Sign Messages // - - char *sign_message(char *message, char *address = "") - { - return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str()); - } - -#ifdef __cplusplus -} -#endif diff --git a/cw_monero/macos/Classes/monero_api.h b/cw_monero/macos/Classes/monero_api.h deleted file mode 100644 index fa92a038dc..0000000000 --- a/cw_monero/macos/Classes/monero_api.h +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include -#include -#include "CwWalletListener.h" - -#ifdef __cplusplus -extern "C" { -#endif - -bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error); -bool restore_wallet_from_seed(char *path, char *password, char *seed, int32_t networkType, uint64_t restoreHeight, char *error); -bool restore_wallet_from_keys(char *path, char *password, char *language, char *address, char *viewKey, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error); -void load_wallet(char *path, char *password, int32_t nettype); -bool is_wallet_exist(char *path); - -char *get_filename(); -const char *seed(); -char *get_address(uint32_t account_index, uint32_t address_index); -uint64_t get_full_balance(uint32_t account_index); -uint64_t get_unlocked_balance(uint32_t account_index); -uint64_t get_current_height(); -uint64_t get_node_height(); - -bool is_connected(); - -bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error); -bool connect_to_node(char *error); -void start_refresh(); -void set_refresh_from_block_height(uint64_t height); -void set_recovering_from_seed(bool is_recovery); -void store(char *path); - -void set_trusted_daemon(bool arg); -bool trusted_daemon(); -char *sign_message(char *message, char *address); - -#ifdef __cplusplus -} -#endif diff --git a/cw_monero/macos/cw_monero_base.podspec b/cw_monero/macos/cw_monero_base.podspec deleted file mode 100644 index aac972c0f9..0000000000 --- a/cw_monero/macos/cw_monero_base.podspec +++ /dev/null @@ -1,56 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint cw_monero.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'cw_monero' - s.version = '0.0.1' - s.summary = 'CW Monero' - s.description = 'Cake Wallet wrapper over Monero project.' - s.homepage = 'http://cakewallet.com' - s.license = { :file => '../LICENSE' } - s.author = { 'CakeWallet' => 'support@cakewallet.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'FlutterMacOS' - s.public_header_files = 'Classes/**/*.h, Classes/*.h, External/macos/libs/monero/include/External/ios/**/*.h' - s.platform = :osx, '10.11' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => '#___VALID_ARCHS___#', 'ENABLE_BITCODE' => 'NO' } - s.swift_version = '5.0' - s.libraries = 'iconv' - - s.subspec 'OpenSSL' do |openssl| - openssl.preserve_paths = '../../../../../cw_shared_external/ios/External/macos/include/**/*.h' - openssl.vendored_libraries = '../../../../../cw_shared_external/ios/External/macos/lib/libcrypto.a', '../../../../../cw_shared_external/ios/External/ios/lib/libssl.a' - openssl.libraries = 'ssl', 'crypto' - openssl.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/macos/include/**" } - end - - s.subspec 'Sodium' do |sodium| - sodium.preserve_paths = '../../../../../cw_shared_external/ios/External/macos/include/**/*.h' - sodium.vendored_libraries = '../../../../../cw_shared_external/ios/External/macos/lib/libsodium.a' - sodium.libraries = 'sodium' - sodium.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/macos/include/**" } - end - - s.subspec 'Unbound' do |unbound| - unbound.preserve_paths = '../../../../../cw_shared_external/ios/External/macos/include/**/*.h' - unbound.vendored_libraries = '../../../../../cw_shared_external/ios/External/macos/lib/libunbound.a' - unbound.libraries = 'unbound' - unbound.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/macos/include/**" } - end - - s.subspec 'Boost' do |boost| - boost.preserve_paths = '../../../../../cw_shared_external/ios/External/macos/include/**/*.h', - boost.vendored_libraries = '../../../../../cw_shared_external/ios/External/macos/lib/libboost.a', - boost.libraries = 'boost' - boost.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/macos/include/**" } - end - - s.subspec 'Monero' do |monero| - monero.preserve_paths = 'External/macos/include/**/*.h' - monero.vendored_libraries = 'External/macos/lib/libmonero.a' - monero.libraries = 'monero' - monero.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/macos/include" } - end -end diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 7d3b464953..ce04159091 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -44,16 +44,7 @@ flutter: # The androidPackage and pluginClass identifiers should not ordinarily # be modified. They are used by the tooling to maintain consistency when # adding or updating assets for this project. - plugin: - platforms: - android: - package: com.cakewallet.monero - pluginClass: CwMoneroPlugin - ios: - pluginClass: CwMoneroPlugin - macos: - pluginClass: CwMoneroPlugin # To add assets to your plugin package, add an assets section, like this: # assets: diff --git a/cw_monero/test/cw_monero_method_channel_test.dart b/cw_monero/test/cw_monero_method_channel_test.dart deleted file mode 100644 index 8c1f329f03..0000000000 --- a/cw_monero/test/cw_monero_method_channel_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:cw_monero/cw_monero_method_channel.dart'; - -void main() { - MethodChannelCwMonero platform = MethodChannelCwMonero(); - const MethodChannel channel = MethodChannel('cw_monero'); - - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return '42'; - }); - }); - - tearDown(() { - channel.setMockMethodCallHandler(null); - }); - - test('getPlatformVersion', () async { - expect(await platform.getPlatformVersion(), '42'); - }); -} diff --git a/cw_monero/test/cw_monero_test.dart b/cw_monero/test/cw_monero_test.dart deleted file mode 100644 index 1eb8d6f798..0000000000 --- a/cw_monero/test/cw_monero_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:cw_monero/cw_monero.dart'; -import 'package:cw_monero/cw_monero_platform_interface.dart'; -import 'package:cw_monero/cw_monero_method_channel.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -class MockCwMoneroPlatform - with MockPlatformInterfaceMixin - implements CwMoneroPlatform { - - @override - Future getPlatformVersion() => Future.value('42'); -} - -void main() { - final CwMoneroPlatform initialPlatform = CwMoneroPlatform.instance; - - test('$MethodChannelCwMonero is the default instance', () { - expect(initialPlatform, isInstanceOf()); - }); - - test('getPlatformVersion', () async { - CwMonero cwMoneroPlugin = CwMonero(); - MockCwMoneroPlatform fakePlatform = MockCwMoneroPlatform(); - CwMoneroPlatform.instance = fakePlatform; - - expect(await cwMoneroPlugin.getPlatformVersion(), '42'); - }); -} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 75a78404fd..5ed5fe8481 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,6 @@ import FlutterMacOS import Foundation import connectivity_plus -import cw_monero import device_info_plus import devicelocale import flutter_inappwebview_macos @@ -22,7 +21,6 @@ import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) - CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) From 5a03c04125cf23aee0fdc6917e485e25fbb67cf5 Mon Sep 17 00:00:00 2001 From: M Date: Wed, 27 Sep 2023 18:33:40 +0100 Subject: [PATCH 052/242] Add windows app, build scripts and build guide for it. --- .metadata | 16 +- build-guide-win.md | 30 ++ cakewallet.bat | 47 +++ cw_bitcoin/pubspec.lock | 20 +- cw_core/lib/pathForWallet.dart | 7 +- cw_core/lib/root_dir.dart | 28 ++ cw_core/lib/sec_random_native.dart | 8 + cw_core/pubspec.lock | 20 +- cw_haven/pubspec.lock | 18 +- cw_monero/pubspec.lock | 28 +- env.json | 6 + lib/core/backup_service.dart | 15 +- lib/entities/language_service.dart | 15 +- lib/locales/hausa_intl.dart | 21 ++ lib/locales/yoruba_intl.dart | 21 ++ lib/main.dart | 4 +- lib/monero/cw_monero.dart | 5 + lib/store/settings_store.dart | 2 +- lib/utils/distribution_info.dart | 2 +- lib/utils/exception_handler.dart | 6 +- lib/utils/package_info.dart | 54 ++++ lib/view_model/backup_view_model.dart | 5 +- .../settings/other_settings_view_model.dart | 1 + lib/view_model/wallet_keys_view_model.dart | 2 +- lib/wallet_type_utils.dart | 6 +- tool/configure.dart | 10 +- windows/.gitignore | 17 ++ windows/CMakeLists.txt | 102 +++++++ windows/flutter/CMakeLists.txt | 104 +++++++ .../flutter/generated_plugin_registrant.cc | 29 ++ windows/flutter/generated_plugin_registrant.h | 15 + windows/flutter/generated_plugins.cmake | 29 ++ windows/runner/CMakeLists.txt | 40 +++ windows/runner/Runner.rc | 121 ++++++++ windows/runner/flutter_window.cpp | 71 +++++ windows/runner/flutter_window.h | 33 ++ windows/runner/main.cpp | 43 +++ windows/runner/resource.h | 16 + windows/runner/resources/app_icon.ico | Bin 0 -> 17470 bytes windows/runner/runner.exe.manifest | 20 ++ windows/runner/utils.cpp | 65 ++++ windows/runner/utils.h | 19 ++ windows/runner/win32_window.cpp | 288 ++++++++++++++++++ windows/runner/win32_window.h | 102 +++++++ 44 files changed, 1438 insertions(+), 73 deletions(-) create mode 100644 build-guide-win.md create mode 100644 cakewallet.bat create mode 100644 cw_core/lib/root_dir.dart create mode 100644 env.json create mode 100644 lib/utils/package_info.dart create mode 100644 windows/.gitignore create mode 100644 windows/CMakeLists.txt create mode 100644 windows/flutter/CMakeLists.txt create mode 100644 windows/flutter/generated_plugin_registrant.cc create mode 100644 windows/flutter/generated_plugin_registrant.h create mode 100644 windows/flutter/generated_plugins.cmake create mode 100644 windows/runner/CMakeLists.txt create mode 100644 windows/runner/Runner.rc create mode 100644 windows/runner/flutter_window.cpp create mode 100644 windows/runner/flutter_window.h create mode 100644 windows/runner/main.cpp create mode 100644 windows/runner/resource.h create mode 100644 windows/runner/resources/app_icon.ico create mode 100644 windows/runner/runner.exe.manifest create mode 100644 windows/runner/utils.cpp create mode 100644 windows/runner/utils.h create mode 100644 windows/runner/win32_window.cpp create mode 100644 windows/runner/win32_window.h diff --git a/.metadata b/.metadata index cdddb93504..7d00ca21a5 100644 --- a/.metadata +++ b/.metadata @@ -1,11 +1,11 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled. +# This file should be version controlled and should not be manually edited. version: - revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 - channel: stable + revision: "367f9ea16bfae1ca451b9cc27c1366870b187ae2" + channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 - base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 - - platform: macos - create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 - base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 + base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 + - platform: windows + create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 + base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 # User provided section diff --git a/build-guide-win.md b/build-guide-win.md new file mode 100644 index 0000000000..30c0219149 --- /dev/null +++ b/build-guide-win.md @@ -0,0 +1,30 @@ +# Building CakeWallet for Windows + +## Requirements and Setup + +The following are the system requirements to build CakeWallet for your Windows PC. + +``` +Windows 10 or later (64-bit), x86-64 based +Flutter 3 or above +``` + +## Building CakeWallet on Windows + +These steps will help you configure and execute a build of CakeWallet from its source code. + +### 1. Installing Package Dependencies + +For build CakeWallet windows application from sources you will be needed to have: +> [Install Flutter](https://docs.flutter.dev/get-started/install/windows) follow this guide until `Android setup` section (it's not necessary for this build process). +> [Install adition for Flutter SDK](https://docs.flutter.dev/platform-integration/desktop#additional-windows-requirements). Then install `Desktop development with C++` packages via GUI Visual Studio 2022, or Visual Studio Build Tools 2022 including: `C++ Build Tools core features`, `C++ 2022 Redistributable Update`, `C++ core desktop features`, `MVC v143 - VS 2022 C++ x64/x86 build tools`, `C++ CMake tools for Windwos`, `Testing tools core features - Build Tools`, `C++ AddressSanitizer`. + +### 2. Pull CakeWallet source code + +You can downlaod CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git by following next command: +`$ git clone https://github.com/cake-tech/cake_wallet.git --branch windows` +OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/windows.zip) + +### 3. Configure and build CakeWallet application +To configure the application open directory where you have downloaded or unarchived CakeWallet sources and run `cakewallet.bat`. +After execution of `cakewallet.bat` you should to get `Cake Wallet.zip` in project root directory which will contains `CakeWallet.exe` file and another needed files for run the application. Now you can extract files from `Cake Wallet.zip` archive and run the application. diff --git a/cakewallet.bat b/cakewallet.bat new file mode 100644 index 0000000000..dd9b4b9161 --- /dev/null +++ b/cakewallet.bat @@ -0,0 +1,47 @@ +@echo off +set cw_win_app_config=--bitcoin --ethereum +set cw_root=%cd% +set cw_archive_name=Cake Wallet.zip +set cw_archive_path=%cw_root%\%cw_archive_name% +set secrets_file_path=lib\.secrets.g.dart +set release_dir=build\windows\runner\Release +set tools_root=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\14.36.32532\x64\Microsoft.VC143.CRT + +echo === Generating pubspec.yaml === +copy /Y pubspec_description.yaml pubspec.yaml > nul +call flutter pub get > nul +call dart run tool\generate_pubspec.dart +call flutter pub get > nul +call dart run tool\configure.dart %cw_win_app_config% + +IF NOT EXIST "%secrets_file_path%" ( + echo === Generating new secrets file === + call dart run tool\generate_new_secrets.dart +) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===) + +echo === Generating mobx models === +for /d %%i in (cw_core cw_bitcoin cw_ethereum cw_monero .) do ( + cd %%i + call flutter pub get > nul + call dart run build_runner build --delete-conflicting-outputs > nul + cd /d %cw_root% +) + +echo === Generating localization files === +call dart run tool\generate_localization.dart + +echo === Building the application executable file === +call flutter build windows --dart-define-from-file=env.json --release + +echo === Prepare distribution actions. Copy needed files to the application bundle === +copy /Y "%tools_root%\msvcp140.dll" "%release_dir%\" > nul +copy /Y "%tools_root%\vcruntime140.dll" "%release_dir%\" > nul +copy /Y "%tools_root%\vcruntime140_1.dll" "%release_dir%\" > nul + +echo === Generate the application archive === +xcopy /s /e /v /Y "%release_dir%\*.*" "build\Cake Wallet\" > nul +tar acf "%cw_archive_name%" -C build\ "Cake Wallet" + +echo === Open Explorer with the application archive === +echo Cake Wallet created archive at: %cw_archive_path% +%SystemRoot%\explorer.exe /select, %cw_archive_path% diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 3d828243c1..1439b9ea01 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -217,10 +217,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -434,18 +434,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -663,10 +663,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -711,10 +711,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" timing: dependency: transitive description: diff --git a/cw_core/lib/pathForWallet.dart b/cw_core/lib/pathForWallet.dart index cfc33ef210..9aa7219233 100644 --- a/cw_core/lib/pathForWallet.dart +++ b/cw_core/lib/pathForWallet.dart @@ -1,9 +1,10 @@ import 'dart:io'; +import 'package:cw_core/root_dir.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:path_provider/path_provider.dart'; Future pathForWalletDir({required String name, required WalletType type}) async { - final root = await getApplicationDocumentsDirectory(); + final root = await getAppDir(); final prefix = walletTypeToString(type).toLowerCase(); final walletsDir = Directory('${root.path}/wallets'); final walletDire = Directory('${walletsDir.path}/$prefix/$name'); @@ -20,8 +21,8 @@ Future pathForWallet({required String name, required WalletType type}) a .then((path) => path + '/$name'); Future outdatedAndroidPathForWalletDir({required String name}) async { - final directory = await getApplicationDocumentsDirectory(); + final directory = await getAppDir(); final pathDir = directory.path + '/$name'; return pathDir; -} \ No newline at end of file +} diff --git a/cw_core/lib/root_dir.dart b/cw_core/lib/root_dir.dart new file mode 100644 index 0000000000..6549d0d6e7 --- /dev/null +++ b/cw_core/lib/root_dir.dart @@ -0,0 +1,28 @@ +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; + +String? _rootDirPath; + +void setRootDirFromEnv() + => _rootDirPath = Platform.environment['CAKE_WALLET_DIR']; + +Future getAppDir({String appName = 'cake_wallet'}) async { + Directory dir; + + if (_rootDirPath != null && _rootDirPath!.isNotEmpty) { + dir = Directory.fromUri(Uri.file(_rootDirPath!)); + dir.create(recursive: true); + } else { + dir = await getApplicationDocumentsDirectory(); + + if (Platform.isWindows) { + dir = await getApplicationSupportDirectory(); + } else if (Platform.isLinux) { + final appDirPath = '${dir.path}/$appName'; + dir = Directory.fromUri(Uri.file(appDirPath)); + await dir.create(recursive: true); + } + } + + return dir; +} diff --git a/cw_core/lib/sec_random_native.dart b/cw_core/lib/sec_random_native.dart index ce251efc02..2011602bfb 100644 --- a/cw_core/lib/sec_random_native.dart +++ b/cw_core/lib/sec_random_native.dart @@ -1,3 +1,5 @@ +import 'dart:io'; +import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/services.dart'; @@ -6,6 +8,12 @@ const utils = const MethodChannel('com.cake_wallet/native_utils'); Future secRandom(int count) async { try { + if (Platform.isWindows || Platform.isLinux) { + // Used method to get securely generated random bytes from cake backups + const byteSize = 256; + final rng = Random.secure(); + return Uint8List.fromList(List.generate(count, (_) => rng.nextInt(byteSize))); + } return await utils.invokeMethod('sec_random', {'count': count}) ?? Uint8List.fromList([]); } on PlatformException catch (_) { return Uint8List.fromList([]); diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index 678e57b54a..5d794d7ec7 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -343,18 +343,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -564,10 +564,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -612,10 +612,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" timing: dependency: transitive description: diff --git a/cw_haven/pubspec.lock b/cw_haven/pubspec.lock index b0a350cc71..cfd624c880 100644 --- a/cw_haven/pubspec.lock +++ b/cw_haven/pubspec.lock @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -350,18 +350,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -563,10 +563,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -611,7 +611,7 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted version: "0.5.1" diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 7503cd96da..b82c43c15a 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -366,18 +366,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -596,10 +596,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -644,10 +644,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" timing: dependency: transitive description: @@ -680,6 +680,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: diff --git a/env.json b/env.json new file mode 100644 index 0000000000..33a4bc52ba --- /dev/null +++ b/env.json @@ -0,0 +1,6 @@ +{ + "CW_WIN_APP_NAME":"Cake Wallet", + "CW_WIN_APP_PACKAGE_NAME": "com.cakewallet.cake_wallet", + "CW_WIN_APP_VERSION": "1.0.0", + "CW_WIN_APP_BUILD_NUMBER": "1" +} diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 2ec5f293d4..98b7100ce2 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:cake_wallet/themes/theme_list.dart'; import 'package:cake_wallet/utils/device_info.dart'; +import 'package:cw_core/root_dir.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; @@ -76,7 +77,7 @@ class BackupService { Future _exportBackupV2(String password) async { final zipEncoder = ZipFileEncoder(); - final appDir = await getApplicationDocumentsDirectory(); + final appDir = await getAppDir(); final now = DateTime.now(); final tmpDir = Directory('${appDir.path}/~_BACKUP_TMP'); final archivePath = '${tmpDir.path}/backup_${now.toString()}.zip'; @@ -116,7 +117,7 @@ class BackupService { } Future _importBackupV1(Uint8List data, String password, {required String nonce}) async { - final appDir = await getApplicationDocumentsDirectory(); + final appDir = await getAppDir(); final decryptedData = await _decryptV1(data, password, nonce); final zip = ZipDecoder().decodeBytes(decryptedData); @@ -139,7 +140,7 @@ class BackupService { } Future _importBackupV2(Uint8List data, String password) async { - final appDir = await getApplicationDocumentsDirectory(); + final appDir = await getAppDir(); final decryptedData = await _decryptV2(data, password); final zip = ZipDecoder().decodeBytes(decryptedData); @@ -172,7 +173,7 @@ class BackupService { } Future> _reloadHiveWalletInfoBox() async { - final appDir = await getApplicationDocumentsDirectory(); + final appDir = await getAppDir(); await CakeHive.close(); CakeHive.init(appDir.path); @@ -184,7 +185,7 @@ class BackupService { } Future _importPreferencesDump() async { - final appDir = await getApplicationDocumentsDirectory(); + final appDir = await getAppDir(); final preferencesFile = File('${appDir.path}/~_preferences_dump'); if (!preferencesFile.existsSync()) { @@ -361,7 +362,7 @@ class BackupService { Future _importKeychainDumpV1(String password, {required String nonce, String keychainSalt = secrets.backupKeychainSalt}) async { - final appDir = await getApplicationDocumentsDirectory(); + final appDir = await getAppDir(); final keychainDumpFile = File('${appDir.path}/~_keychain_dump'); final decryptedKeychainDumpFileData = await _decryptV1(keychainDumpFile.readAsBytesSync(), '$keychainSalt$password', nonce); @@ -389,7 +390,7 @@ class BackupService { Future _importKeychainDumpV2(String password, {String keychainSalt = secrets.backupKeychainSalt}) async { - final appDir = await getApplicationDocumentsDirectory(); + final appDir = await getAppDir(); final keychainDumpFile = File('${appDir.path}/~_keychain_dump'); final decryptedKeychainDumpFileData = await _decryptV2(keychainDumpFile.readAsBytesSync(), '$keychainSalt$password'); diff --git a/lib/entities/language_service.dart b/lib/entities/language_service.dart index cfb8508890..23d27dd38e 100644 --- a/lib/entities/language_service.dart +++ b/lib/entities/language_service.dart @@ -63,6 +63,8 @@ class LanguageService { static final list = {}; + static const defaultLocale = 'en'; + static void loadLocaleList() { supportedLocales.forEach((key, value) { if (locales.contains(key)) { @@ -72,9 +74,16 @@ class LanguageService { } static Future localeDetection() async { - var locale = await Devicelocale.currentLocale ?? ''; - locale = Intl.shortLocale(locale); + try { + var locale = await Devicelocale.currentLocale ?? ''; + locale = Intl.shortLocale(locale); - return list.keys.contains(locale) ? locale : 'en'; + if (list.keys.contains(locale)) { + return locale; + } + return LanguageService.defaultLocale; + } catch(_) { + return LanguageService.defaultLocale; + } } } diff --git a/lib/locales/hausa_intl.dart b/lib/locales/hausa_intl.dart index 749d39a4d5..972c2b4cdb 100644 --- a/lib/locales/hausa_intl.dart +++ b/lib/locales/hausa_intl.dart @@ -751,6 +751,27 @@ class HaMaterialLocalizations extends GlobalMaterialLocalizations { @override String get scrimOnTapHintRaw => "Scrip on Tap"; + + @override + String get collapsedHint => 'Expanded'; + + @override + String get expandedHint => 'Collapsed'; + + @override + String get expansionTileCollapsedHint => 'double tap to expand'; + + @override + String get expansionTileCollapsedTapHint => 'Expand for more details'; + + @override + String get expansionTileExpandedHint => 'double tap to collapse'; + + @override + String get expansionTileExpandedTapHint => 'Collapse'; + + @override + String get scanTextButtonLabel => 'Scan'; } /// Cupertino Support diff --git a/lib/locales/yoruba_intl.dart b/lib/locales/yoruba_intl.dart index 889c21cb73..d124734086 100644 --- a/lib/locales/yoruba_intl.dart +++ b/lib/locales/yoruba_intl.dart @@ -751,6 +751,27 @@ String get keyboardKeyMetaWindows => 'Windows'; @override String get scrimOnTapHintRaw => "Scrip on Tap"; + + @override + String get collapsedHint => 'Expanded'; + + @override + String get expandedHint => 'Collapsed'; + + @override + String get expansionTileCollapsedHint => 'double tap to expand'; + + @override + String get expansionTileCollapsedTapHint => 'Expand for more details'; + + @override + String get expansionTileExpandedHint => 'double tap to collapse'; + + @override + String get expansionTileExpandedTapHint => 'Collapse'; + + @override + String get scanTextButtonLabel => 'Scan'; } /// Cupertino Support diff --git a/lib/main.dart b/lib/main.dart index 3a2340024c..5528da4851 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -40,6 +40,7 @@ import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:cw_core/root_dir.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:uni_links/uni_links.dart'; @@ -72,7 +73,8 @@ Future main() async { } Future initializeAppConfigs() async { - final appDir = await getApplicationDocumentsDirectory(); + setRootDirFromEnv(); + final appDir = await getAppDir(); CakeHive.init(appDir.path); if (!CakeHive.isAdapterRegistered(Contact.typeId)) { diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 959ae92ce0..251a4ffd5f 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -335,4 +335,9 @@ class CWMonero extends Monero { final moneroWallet = wallet as MoneroWallet; await moneroWallet.updateUnspent(); } + + @override + Future getCurrentHeight() async { + return monero_wallet.getCurrentHeight(); + } } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 165c722428..2004156317 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -28,7 +28,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; -import 'package:package_info/package_info.dart'; +import 'package:cake_wallet/utils/package_info.dart'; import 'package:cake_wallet/di.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:shared_preferences/shared_preferences.dart'; diff --git a/lib/utils/distribution_info.dart b/lib/utils/distribution_info.dart index 859c507a3c..5a2cb8e9da 100644 --- a/lib/utils/distribution_info.dart +++ b/lib/utils/distribution_info.dart @@ -1,5 +1,5 @@ import 'dart:io'; -import 'package:package_info/package_info.dart'; +import 'package:cake_wallet/utils/package_info.dart'; enum DistributionType { googleplay, github, appstore, fdroid } diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 6e93fc5cdb..fffe6a7702 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -5,11 +5,12 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/root_dir.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mailer/flutter_mailer.dart'; -import 'package:package_info/package_info.dart'; +import 'package:cake_wallet/utils/package_info.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -52,8 +53,7 @@ class ExceptionHandler { static void _sendExceptionFile() async { try { - if (_file == null) { - final appDocDir = await getApplicationDocumentsDirectory(); + final appDocDir = await getAppDir(); _file = File('${appDocDir.path}/error.txt'); } diff --git a/lib/utils/package_info.dart b/lib/utils/package_info.dart new file mode 100644 index 0000000000..8b911f8873 --- /dev/null +++ b/lib/utils/package_info.dart @@ -0,0 +1,54 @@ +import 'dart:io'; +import 'package:package_info/package_info.dart' as __package_info__; + +abstract class _EnvKeys { + static const kWinAppName = 'CW_WIN_APP_NAME'; + static const kWinAppPackageName = 'CW_WIN_APP_PACKAGE_NAME'; + static const kWinAppVersion = 'CW_WIN_APP_VERSION'; + static const kWinAppBuildNumber = 'CW_WIN_APP_BUILD_NUMBER'; +} + +class PackageInfo { + static Future fromPlatform() async { + if (Platform.isWindows) { + return _windowsPackageInfo; + } + + final packageInfo = await __package_info__.PackageInfo.fromPlatform(); + return PackageInfo._( + appName: packageInfo.appName, + packageName: packageInfo.packageName, + version: packageInfo.version, + buildNumber: packageInfo.buildNumber); + } + + static const _defaultCWAppName = 'Cake Wallet'; + static const _defaultCWAppPackageName = 'com.cakewallet.cake_wallet'; + static const _defaultCWAppVersion = '1.0.0'; + static const _defaultCWAppBuildNumber = '1'; + + static const _windowsPackageInfo = PackageInfo._( + appName: const String + .fromEnvironment(_EnvKeys.kWinAppName, + defaultValue: _defaultCWAppName), + packageName: const String + .fromEnvironment(_EnvKeys.kWinAppPackageName, + defaultValue: _defaultCWAppPackageName), + version: const String + .fromEnvironment(_EnvKeys.kWinAppVersion, + defaultValue: _defaultCWAppVersion), + buildNumber: const String + .fromEnvironment(_EnvKeys.kWinAppBuildNumber, + defaultValue: _defaultCWAppBuildNumber)); + + final String appName; + final String packageName; + final String version; + final String buildNumber; + + const PackageInfo._({ + required this.appName, + required this.packageName, + required this.version, + required this.buildNumber}); +} diff --git a/lib/view_model/backup_view_model.dart b/lib/view_model/backup_view_model.dart index 8cc651b175..3e713e709b 100644 --- a/lib/view_model/backup_view_model.dart +++ b/lib/view_model/backup_view_model.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/core/backup_service.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/store/secret_store.dart'; +import 'package:cw_core/root_dir.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mobx/mobx.dart'; @@ -73,7 +74,7 @@ abstract class BackupViewModelBase with Store { } Future saveBackupFileLocally(BackupExportFile backup) async { - final appDir = await getApplicationDocumentsDirectory(); + final appDir = await getAppDir(); final path = '${appDir.path}/${backup.name}'; final backupFile = File(path); await backupFile.writeAsBytes(backup.content); @@ -81,7 +82,7 @@ abstract class BackupViewModelBase with Store { } Future removeBackupFileLocally(BackupExportFile backup) async { - final appDir = await getApplicationDocumentsDirectory(); + final appDir = await getAppDir(); final path = '${appDir.path}/${backup.name}'; final backupFile = File(path); await backupFile.delete(); diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index cf410a1a97..e940c37e22 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -12,6 +12,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:package_info/package_info.dart'; import 'package:collection/collection.dart'; +import 'package:cake_wallet/utils/package_info.dart'; part 'other_settings_view_model.g.dart'; diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index c33c855048..9c44c6510e 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -148,7 +148,7 @@ abstract class WalletKeysViewModelBase with Store { return await haven!.getCurrentHeight(); } if (_appStore.wallet!.type == WalletType.monero) { - return monero_wallet.getCurrentHeight(); + return await monero!.getCurrentHeight(); } return null; } diff --git a/lib/wallet_type_utils.dart b/lib/wallet_type_utils.dart index 5ed78dc64b..459ca992b1 100644 --- a/lib/wallet_type_utils.dart +++ b/lib/wallet_type_utils.dart @@ -16,6 +16,10 @@ bool get isSingleCoin { return availableWalletTypes.length == 1; } +bool get hasMonero { + return availableWalletTypes.contains(WalletType.monero); +} + String get approximatedAppName { if (isMoneroOnly) { return 'Monero.com'; @@ -26,4 +30,4 @@ String get approximatedAppName { } return 'Cake Wallet'; -} \ No newline at end of file +} diff --git a/tool/configure.dart b/tool/configure.dart index 6ee84d63a2..f6c87b5304 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -184,7 +184,6 @@ Future generateMonero(bool hasImplementation) async { const moneroCommonHeaders = """ import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_monero/monero_unspent.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; @@ -297,6 +296,8 @@ abstract class Monero { List getUnspents(Object wallet); Future updateUnspents(Object wallet); + Future getCurrentHeight(); + WalletCredentials createMoneroRestoreWalletFromKeysCredentials({ required String name, required String spendKey, @@ -317,7 +318,7 @@ abstract class Monero { void setCurrentAccount(Object wallet, int id, String label, String? balance); void onStartup(); int getTransactionInfoAccountId(TransactionInfo tx); - WalletService createMoneroWalletService(Box walletInfoSource, Box unspentCoinSource); + WalletService createMoneroWalletService(Box walletInfoSource, Box unspentCoinSource); Map pendingTransactionInfo(Object transaction); } @@ -1057,7 +1058,10 @@ Future generatePubspec( final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); - final dependenciesIndex = inputLines.indexWhere((line) => line.toLowerCase().contains('dependencies:')); + final dependenciesIndex = inputLines.indexWhere( + (line) => Platform.isWindows + ? line.toLowerCase() == 'dependencies:\r' // On Windows it could contains `\r` (Carriage Return) + : line.toLowerCase() == 'dependencies:'); var output = cwCore; if (hasMonero) { diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000000..d492d0d98c --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000000..76256dc45b --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,102 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(cake_wallet LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "CakeWallet") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000000..930d2071a3 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000..565f9eec5a --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,29 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + LocalAuthPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("LocalAuthPlugin")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + PlatformDeviceIdWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PlatformDeviceIdWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000000..dc139d85a9 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000000..74f32ec52a --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,29 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + connectivity_plus_windows + flutter_secure_storage_windows + local_auth_windows + permission_handler_windows + platform_device_id_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000000..394917c053 --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000000..0a899f86e9 --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.cakewallet.cake_wallet" "\0" + VALUE "FileDescription", "Cake Wallet" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "Cake Wallet" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 Cake Wallet. All rights reserved." "\0" + VALUE "OriginalFilename", "Cake Wallet.exe" "\0" + VALUE "ProductName", "Cake Wallet" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000000..955ee3038f --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000000..6da0652f05 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000000..a7eecbf9c6 --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"Cake Wallet", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000000..66a65d1e4a --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..242fb9cb5f35992d02996c591a2bc69a08b5ac94 GIT binary patch literal 17470 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 + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000000..b2b08734db --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000000..3879d54755 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000000..60608d0fe5 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000000..e901dde684 --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ From 2ffd8ed0bdfe5ef4080610e3fff8b74a5ef18bcd Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Thu, 28 Sep 2023 01:35:39 +0300 Subject: [PATCH 053/242] Minor fix in generated monero configs --- lib/monero/cw_monero.dart | 2 +- tool/configure.dart | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 251a4ffd5f..003ab920c3 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -338,6 +338,6 @@ class CWMonero extends Monero { @override Future getCurrentHeight() async { - return monero_wallet.getCurrentHeight(); + return monero_wallet_api.getCurrentHeight(); } } diff --git a/tool/configure.dart b/tool/configure.dart index f6c87b5304..05621d02ae 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -200,6 +200,7 @@ import 'package:polyseed/polyseed.dart';"""; import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/monero_transaction_priority.dart'; +import 'package:cw_monero/monero_unspent.dart'; import 'package:cw_monero/monero_wallet_service.dart'; import 'package:cw_monero/monero_wallet.dart'; import 'package:cw_monero/monero_transaction_info.dart'; @@ -318,7 +319,7 @@ abstract class Monero { void setCurrentAccount(Object wallet, int id, String label, String? balance); void onStartup(); int getTransactionInfoAccountId(TransactionInfo tx); - WalletService createMoneroWalletService(Box walletInfoSource, Box unspentCoinSource); + WalletService createMoneroWalletService(Box walletInfoSource, Box unspentCoinSource); Map pendingTransactionInfo(Object transaction); } From 4a7d8ca006892de72da1cd59621199c1bacb4c06 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 30 Apr 2024 12:50:58 -0300 Subject: [PATCH 054/242] fix: send all with multiple outs --- cw_bitcoin/lib/electrum_wallet.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index b171d08d64..ac2b8e4fd0 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -569,8 +569,9 @@ abstract class ElectrumWalletBase } } - outputs[outputs.length - 1] = - BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount)); + if (outputs.length == 1) { + outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount)); + } return EstimatedTxResult( utxos: utxoDetails.utxos, From 04fa18a9512fe265d52d80b041216378f72faf99 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Wed, 1 May 2024 16:57:32 +0200 Subject: [PATCH 055/242] add missing monero_c command --- .github/workflows/pr_test_build.yml | 1 + Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index dc231df42d..98f95ae885 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -158,6 +158,7 @@ jobs: - name: Build run: | cd /opt/android/cake_wallet + make libs flutter build apk --release --split-per-abi # - name: Push to App Center diff --git a/Makefile b/Makefile index 642d4fac66..f09db4a9f2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # TODO(mrcyjanek): Cleanup, this is borrowed from unnamed_monero_wallet repo. -MONERO_C_TAG=v0.18.3.3-RC35 +MONERO_C_TAG=v0.18.3.3-RC41 LIBCPP_SHARED_SO_TAG=latest-RC1 LIBCPP_SHARED_SO_NDKVERSION=r17c From a0fd73ac37166439cfd1a8bc746ffc8fad4ce2b0 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Wed, 1 May 2024 17:47:11 +0200 Subject: [PATCH 056/242] add android build script --- scripts/android/build_monero.sh | 68 ----------------------------- scripts/android/build_monero_all.sh | 53 +++++++++++++++++++--- scripts/prepare_moneroc.sh | 33 ++++++++++++++ 3 files changed, 79 insertions(+), 75 deletions(-) delete mode 100755 scripts/android/build_monero.sh create mode 100755 scripts/prepare_moneroc.sh diff --git a/scripts/android/build_monero.sh b/scripts/android/build_monero.sh deleted file mode 100755 index fb596e4529..0000000000 --- a/scripts/android/build_monero.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/sh - -. ./config.sh -MONERO_BRANCH=release-v0.18.3.2-android -MONERO_SRC_DIR=${WORKDIR}/monero - -git clone https://github.com/cake-tech/monero.git ${MONERO_SRC_DIR} --branch ${MONERO_BRANCH} -cd $MONERO_SRC_DIR -git submodule update --init --force - -for arch in "aarch" "aarch64" "i686" "x86_64" -do -FLAGS="" -PREFIX=${WORKDIR}/prefix_${arch} -DEST_LIB_DIR=${PREFIX}/lib/monero -DEST_INCLUDE_DIR=${PREFIX}/include/monero -export CMAKE_INCLUDE_PATH="${PREFIX}/include" -export CMAKE_LIBRARY_PATH="${PREFIX}/lib" -ANDROID_STANDALONE_TOOLCHAIN_PATH="${TOOLCHAIN_BASE_DIR}_${arch}" -PATH="${ANDROID_STANDALONE_TOOLCHAIN_PATH}/bin:${ORIGINAL_PATH}" - -mkdir -p $DEST_LIB_DIR -mkdir -p $DEST_INCLUDE_DIR - -case $arch in - "aarch" ) - CLANG=arm-linux-androideabi-clang - CXXLANG=arm-linux-androideabi-clang++ - BUILD_64=OFF - TAG="android-armv7" - ARCH="armv7-a" - ARCH_ABI="armeabi-v7a" - FLAGS="-D CMAKE_ANDROID_ARM_MODE=ON -D NO_AES=true";; - "aarch64" ) - CLANG=aarch64-linux-androideabi-clang - CXXLANG=aarch64-linux-androideabi-clang++ - BUILD_64=ON - TAG="android-armv8" - ARCH="armv8-a" - ARCH_ABI="arm64-v8a";; - "i686" ) - CLANG=i686-linux-androideabi-clang - CXXLANG=i686-linux-androideabi-clang++ - BUILD_64=OFF - TAG="android-x86" - ARCH="i686" - ARCH_ABI="x86";; - "x86_64" ) - CLANG=x86_64-linux-androideabi-clang - CXXLANG=x86_64-linux-androideabi-clang++ - BUILD_64=ON - TAG="android-x86_64" - ARCH="x86-64" - ARCH_ABI="x86_64";; -esac - -cd $MONERO_SRC_DIR -rm -rf ./build/release -mkdir -p ./build/release -cd ./build/release -CC=${CLANG} CXX=${CXXLANG} cmake -D USE_DEVICE_TREZOR=OFF -D BUILD_GUI_DEPS=1 -D BUILD_TESTS=OFF -D ARCH=${ARCH} -D STATIC=ON -D BUILD_64=${BUILD_64} -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON -D BUILD_TAG=${TAG} -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI=${ARCH_ABI} -D MANUAL_SUBMODULES=1 $FLAGS ../.. - -make wallet_api -j$THREADS -find . -path ./lib -prune -o -name '*.a' -exec cp '{}' lib \; - -cp -r ./lib/* $DEST_LIB_DIR -cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR -done diff --git a/scripts/android/build_monero_all.sh b/scripts/android/build_monero_all.sh index 69ec37b5f0..f683d5d276 100755 --- a/scripts/android/build_monero_all.sh +++ b/scripts/android/build_monero_all.sh @@ -1,9 +1,48 @@ #!/bin/bash -./build_iconv.sh -./build_boost.sh -./build_openssl.sh -./build_sodium.sh -./build_unbound.sh -./build_zmq.sh -./build_monero.sh +# Usage: env USE_DOCKER= ./build_all.sh + +set -x -e + +cd "$(dirname "$0")" + +NPROC="-j$(nproc)" + +if [[ "x$(uname)" == "xDarwin" ]]; +then + USE_DOCKER="ON" + NPROC="-j1" +fi + +../prepare_moneroc.sh + +# NOTE: -j1 is intentional. Otherwise you will run into weird behaviour on macos +if [[ ! "x$USE_DOCKER" == "x" ]]; +then + for COIN in monero; + do + pushd ../monero_c + docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} x86_64-linux-android $NPROC" + # docker run --platform linux/amd64 -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} i686-linux-android $NPROC" + docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} arm-linux-androideabi $NPROC" + docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} aarch64-linux-android $NPROC" + popd + done +else + for COIN in monero; + do + pushd ../monero_c + ./build_single.sh ${COIN} x86_64-linux-android $NPROC + # ./build_single.sh ${COIN} i686-linux-android $NPROC + ./build_single.sh ${COIN} arm-linux-androideabi $NPROC + ./build_single.sh ${COIN} aarch64-linux-android $NPROC + popd + done +fi + +unxz -f ../monero_c/release/monero/x86_64-linux-android_libwallet2_api_c.so.xz +unxz -f ../monero_c/release/wownero/x86_64-linux-android_libwallet2_api_c.so.xz +unxz -f ../monero_c/release/monero/arm-linux-androideabi_libwallet2_api_c.so.xz +unxz -f ../monero_c/release/wownero/arm-linux-androideabi_libwallet2_api_c.so.xz +unxz -f ../monero_c/release/monero/aarch64-linux-android_libwallet2_api_c.so.xz +unxz -f ../monero_c/release/wownero/aarch64-linux-android_libwallet2_api_c.so.xz \ No newline at end of file diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh new file mode 100755 index 0000000000..0ae2705886 --- /dev/null +++ b/scripts/prepare_moneroc.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -x -e + +cd "$(dirname "$0")" + + + +if [[ ! -d "monero_c" ]]; +then + git clone https://github.com/mrcyjanek/monero_c --branch rewrite-wip + cd monero_c + git checkout cd90f3bcd0349759030751ec7ce84eec6ee80c43 + git reset --hard + git submodule update --init --force --recursive + ./apply_patches.sh monero + ./apply_patches.sh wownero +else + cd monero_c +fi + +if [[ ! -f "monero/.patch-applied" ]]; +then + ./apply_patches.sh monero +fi + +if [[ ! -f "wownero/.patch-applied" ]]; +then + ./apply_patches.sh wownero +fi +cd .. + +echo "monero_c source prepared". From e5b78cd2979ab3fb6c7a0bd2ac7fa231ebda46f0 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Thu, 2 May 2024 05:32:32 +0300 Subject: [PATCH 057/242] Merge and fix main --- .github/workflows/pr_test_build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 98f95ae885..dc231df42d 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -158,7 +158,6 @@ jobs: - name: Build run: | cd /opt/android/cake_wallet - make libs flutter build apk --release --split-per-abi # - name: Push to App Center From bc1cfc5fe98b8fb48f9751e6c30e11a550bee308 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Thu, 2 May 2024 15:26:04 +0300 Subject: [PATCH 058/242] undo android ndk removal --- .github/workflows/pr_test_build.yml | 1 + scripts/android/install_ndk.sh | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index dc231df42d..98f95ae885 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -158,6 +158,7 @@ jobs: - name: Build run: | cd /opt/android/cake_wallet + make libs flutter build apk --release --split-per-abi # - name: Push to App Center diff --git a/scripts/android/install_ndk.sh b/scripts/android/install_ndk.sh index 4ec794371b..ea131eb39b 100755 --- a/scripts/android/install_ndk.sh +++ b/scripts/android/install_ndk.sh @@ -8,9 +8,9 @@ TOOLCHAIN_x86_DIR=${TOOLCHAIN_DIR}_i686 TOOLCHAIN_x86_64_DIR=${TOOLCHAIN_DIR}_x86_64 ANDROID_NDK_SHA256="3f541adbd0330a9205ba12697f6d04ec90752c53d6b622101a2a8a856e816589" -# curl https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip -o ${ANDROID_NDK_ZIP} -# echo $ANDROID_NDK_SHA256 $ANDROID_NDK_ZIP | sha256sum -c || exit 1 -# unzip $ANDROID_NDK_ZIP -d $WORKDIR + curl https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip -o ${ANDROID_NDK_ZIP} + echo $ANDROID_NDK_SHA256 $ANDROID_NDK_ZIP | sha256sum -c || exit 1 + unzip $ANDROID_NDK_ZIP -d $WORKDIR ${ANDROID_NDK_ROOT}/build/tools/make_standalone_toolchain.py --arch arm64 --api $API --install-dir ${TOOLCHAIN_A64_DIR} --stl=libc++ ${ANDROID_NDK_ROOT}/build/tools/make_standalone_toolchain.py --arch arm --api $API --install-dir ${TOOLCHAIN_A32_DIR} --stl=libc++ From 0510cee83d824677cfe87aa3c40361eb3a4232cd Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Thu, 2 May 2024 18:07:54 +0300 Subject: [PATCH 059/242] Fix modified exception_handler.dart --- lib/utils/exception_handler.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index fffe6a7702..f967115591 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -11,7 +11,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mailer/flutter_mailer.dart'; import 'package:cake_wallet/utils/package_info.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; class ExceptionHandler { @@ -21,7 +20,7 @@ class ExceptionHandler { static void _saveException(String? error, StackTrace? stackTrace, {String? library}) async { if (_file == null) { - final appDocDir = await getApplicationDocumentsDirectory(); + final appDocDir = await getAppDir(); _file = File('${appDocDir.path}/error.txt'); } @@ -53,7 +52,8 @@ class ExceptionHandler { static void _sendExceptionFile() async { try { - final appDocDir = await getAppDir(); + if (_file == null) { + final appDocDir = await getAppDir(); _file = File('${appDocDir.path}/error.txt'); } From d6081c4b474678d5c9a57d2e7b158e7ffd83cba8 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Thu, 2 May 2024 21:32:21 +0300 Subject: [PATCH 060/242] Temporarily remove haven --- scripts/android/pubspec_gen.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index d238052fe7..77318351e5 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --polygon --nano --bitcoinCash --solana" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana" # TODO: fix and add back --haven ;; $HAVEN) CONFIG_ARGS="--haven" From bda1a85f247adc9e56fb958000accfd7359f2110 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Thu, 2 May 2024 22:47:20 +0200 Subject: [PATCH 061/242] fix build issues --- Makefile | 33 +------------- .../arm64-v8a/libmonero_libwallet2_api_c.so | 1 + .../armeabi-v7a/libmonero_libwallet2_api_c.so | 1 + .../x86_64/libmonero_libwallet2_api_c.so | 1 + configure_cake_wallet.sh | 2 + cw_bitcoin/pubspec.lock | 20 ++++----- cw_core/pubspec.lock | 20 ++++----- cw_haven/pubspec.lock | 32 +++++-------- cw_monero/pubspec.lock | 28 +++++------- .../settings/other_settings_view_model.dart | 45 +++++++++++-------- scripts/android/build_all.sh | 2 +- scripts/android/build_monero_all.sh | 6 +-- scripts/android/build_openssl.sh | 4 +- scripts/android/build_unbound.sh | 4 +- scripts/android/copy_monero_deps.sh | 1 - scripts/android/pubspec_gen.sh | 2 +- .../flutter/generated_plugin_registrant.cc | 5 +-- windows/flutter/generated_plugins.cmake | 3 +- 18 files changed, 85 insertions(+), 125 deletions(-) create mode 120000 android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so create mode 120000 android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so create mode 120000 android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so diff --git a/Makefile b/Makefile index f09db4a9f2..51c68f6b53 100644 --- a/Makefile +++ b/Makefile @@ -1,48 +1,21 @@ -# TODO(mrcyjanek): Cleanup, this is borrowed from unnamed_monero_wallet repo. - -MONERO_C_TAG=v0.18.3.3-RC41 LIBCPP_SHARED_SO_TAG=latest-RC1 LIBCPP_SHARED_SO_NDKVERSION=r17c -libs: android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so -.PHONY: android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so -android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so: - wget -q https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/monero/aarch64-linux-android_libwallet2_api_c.so.xz -O android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so.xz - unxz android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so.xz - libs: android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so .PHONY: android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so: wget -q https://git.mrcyjanek.net/mrcyjanek/libcpp_shared.so/releases/download/${LIBCPP_SHARED_SO_TAG}/${LIBCPP_SHARED_SO_NDKVERSION}_arm64-v8a_libc++_shared.so -O android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so -libs: android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so -.PHONY: android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so -android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so: - wget -q https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/monero/arm-linux-androideabi_libwallet2_api_c.so.xz -O android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so.xz - unxz android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so.xz - libs: android/app/src/main/jniLibs/armeabi-v7a/libc++_shared.so .PHONY: android/app/src/main/jniLibs/armeabi-v7a/libc++_shared.so android/app/src/main/jniLibs/armeabi-v7a/libc++_shared.so: wget -q https://git.mrcyjanek.net/mrcyjanek/libcpp_shared.so/releases/download/${LIBCPP_SHARED_SO_TAG}/${LIBCPP_SHARED_SO_NDKVERSION}_armeabi-v7a_libc++_shared.so -O android/app/src/main/jniLibs/armeabi-v7a/libc++_shared.so -# libs: android/app/src/main/jniLibs/x86/libmonero_libwallet2_api_c.so -# .PHONY: android/app/src/main/jniLibs/x86/libmonero_libwallet2_api_c.so -# android/app/src/main/jniLibs/x86/libmonero_libwallet2_api_c.so: -# wget -q https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/monero/i686-linux-android_libwallet2_api_c.so.xz -O android/app/src/main/jniLibs/x86/libmonero_libwallet2_api_c.so.xz -# unxz android/app/src/main/jniLibs/x86/libmonero_libwallet2_api_c.so.xz - libs: android/app/src/main/jniLibs/x86/libc++_shared.so .PHONY: android/app/src/main/jniLibs/x86/libc++_shared.so android/app/src/main/jniLibs/x86/libc++_shared.so: wget -q https://git.mrcyjanek.net/mrcyjanek/libcpp_shared.so/releases/download/${LIBCPP_SHARED_SO_TAG}/${LIBCPP_SHARED_SO_NDKVERSION}_x86_libc++_shared.so -O android/app/src/main/jniLibs/x86/libc++_shared.so -libs: android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so -.PHONY: android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so -android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so: - wget -q https://static.mrcyjanek.net/monero_c/${MONERO_C_TAG}/monero/x86_64-linux-android_libwallet2_api_c.so.xz -O android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so.xz - unxz android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so.xz - libs: android/app/src/main/jniLibs/x86_64/libc++_shared.so .PHONY: android/app/src/main/jniLibs/x86_64/libc++_shared.so android/app/src/main/jniLibs/x86_64/libc++_shared.so: @@ -50,10 +23,6 @@ android/app/src/main/jniLibs/x86_64/libc++_shared.so: clean_libs: -rm android/app/src/main/jniLibs/x86_64/libc++_shared.so* - -rm android/app/src/main/jniLibs/x86_64/*_libwallet2_api_c.so* -rm android/app/src/main/jniLibs/armeabi-v7a/libc++_shared.so* - -rm android/app/src/main/jniLibs/armeabi-v7a/*_libwallet2_api_c.so* -rm android/app/src/main/jniLibs/x86/libc++_shared.so* - -rm android/app/src/main/jniLibs/x86/*_libwallet2_api_c.so* - -rm android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so* - -rm android/app/src/main/jniLibs/arm64-v8a/*_libwallet2_api_c.so* \ No newline at end of file + -rm android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so* \ No newline at end of file diff --git a/android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so b/android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so new file mode 120000 index 0000000000..6cdcd70a20 --- /dev/null +++ b/android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so @@ -0,0 +1 @@ +../../../../../../scripts/monero_c/release/monero/aarch64-linux-android_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so b/android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so new file mode 120000 index 0000000000..ea65fc4f90 --- /dev/null +++ b/android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so @@ -0,0 +1 @@ +../../../../../../scripts/monero_c/release/monero/arm-linux-androideabi_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so b/android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so new file mode 120000 index 0000000000..654be50b95 --- /dev/null +++ b/android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so @@ -0,0 +1 @@ +../../../../../../scripts/monero_c/release/monero/x86_64-linux-android_libwallet2_api_c.so \ No newline at end of file diff --git a/configure_cake_wallet.sh b/configure_cake_wallet.sh index 837a002e9f..d18f6c22a8 100755 --- a/configure_cake_wallet.sh +++ b/configure_cake_wallet.sh @@ -1,3 +1,5 @@ +#!/bin/bash + IOS="ios" ANDROID="android" diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index e82ce59d08..86d58b9b15 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -217,10 +217,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.17.1" convert: dependency: transitive description: @@ -434,18 +434,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -663,10 +663,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: @@ -711,10 +711,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.5.1" timing: dependency: transitive description: diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index 5d794d7ec7..678e57b54a 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.17.1" convert: dependency: transitive description: @@ -343,18 +343,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -564,10 +564,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: @@ -612,10 +612,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.5.1" timing: dependency: transitive description: diff --git a/cw_haven/pubspec.lock b/cw_haven/pubspec.lock index d845235391..b134879368 100644 --- a/cw_haven/pubspec.lock +++ b/cw_haven/pubspec.lock @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.17.1" convert: dependency: transitive description: @@ -350,18 +350,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -563,10 +563,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: @@ -611,10 +611,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.5.1" timing: dependency: transitive description: @@ -640,21 +640,13 @@ packages: source: hosted version: "2.1.4" watcher: - dependency: "direct overridden" + dependency: transitive description: name: watcher sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -688,5 +680,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.7.0" diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index b82c43c15a..7503cd96da 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.17.1" convert: dependency: transitive description: @@ -366,18 +366,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -596,10 +596,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: @@ -644,10 +644,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.5.1" timing: dependency: transitive description: @@ -680,14 +680,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" web_socket_channel: dependency: transitive description: diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index f4c817b32a..edce34c5d7 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -1,8 +1,11 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/utils/package_info.dart'; +// import 'package:package_info/package_info.dart'; +import 'package:collection/collection.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/transaction_info.dart'; @@ -10,20 +13,18 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; -import 'package:package_info/package_info.dart'; -import 'package:collection/collection.dart'; -import 'package:cake_wallet/utils/package_info.dart'; part 'other_settings_view_model.g.dart'; -class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel; +class OtherSettingsViewModel = OtherSettingsViewModelBase + with _$OtherSettingsViewModel; abstract class OtherSettingsViewModelBase with Store { OtherSettingsViewModelBase(this._settingsStore, this._wallet) : walletType = _wallet.type, currentVersion = '' { - PackageInfo.fromPlatform() - .then((PackageInfo packageInfo) => currentVersion = packageInfo.version); + PackageInfo.fromPlatform().then( + (PackageInfo packageInfo) => currentVersion = packageInfo.version); final priority = _settingsStore.priority[_wallet.type]; final priorities = priorityForWalletType(_wallet.type); @@ -34,7 +35,8 @@ abstract class OtherSettingsViewModelBase with Store { } final WalletType walletType; - final WalletBase, TransactionInfo> _wallet; + final WalletBase, + TransactionInfo> _wallet; @observable String currentVersion; @@ -61,10 +63,12 @@ abstract class OtherSettingsViewModelBase with Store { !(changeRepresentativeEnabled || _wallet.type == WalletType.solana); @computed - bool get isEnabledBuyAction => !_settingsStore.disableBuy && _wallet.type != WalletType.haven; + bool get isEnabledBuyAction => + !_settingsStore.disableBuy && _wallet.type != WalletType.haven; @computed - bool get isEnabledSellAction => !_settingsStore.disableSell && _wallet.type != WalletType.haven; + bool get isEnabledSellAction => + !_settingsStore.disableSell && _wallet.type != WalletType.haven; List get availableBuyProvidersTypes { return ProvidersHelper.getAvailableBuyProviderTypes(walletType); @@ -74,12 +78,12 @@ abstract class OtherSettingsViewModelBase with Store { ProvidersHelper.getAvailableSellProviderTypes(walletType); ProviderType get buyProviderType => - _settingsStore.defaultBuyProviders[walletType] ?? ProviderType.askEachTime; + _settingsStore.defaultBuyProviders[walletType] ?? + ProviderType.askEachTime; ProviderType get sellProviderType => - _settingsStore.defaultSellProviders[walletType] ?? ProviderType.askEachTime; - - + _settingsStore.defaultSellProviders[walletType] ?? + ProviderType.askEachTime; String getDisplayPriority(dynamic priority) { final _priority = priority as TransactionPriority; @@ -101,7 +105,8 @@ abstract class OtherSettingsViewModelBase with Store { _wallet.type == WalletType.litecoin || _wallet.type == WalletType.bitcoinCash) { final rate = bitcoin!.getFeeRate(_wallet, _priority); - return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue); + return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, + customRate: customValue); } return priority.toString(); @@ -124,7 +129,8 @@ abstract class OtherSettingsViewModelBase with Store { void onDisplayPrioritySelected(TransactionPriority priority) => _settingsStore.priority[walletType] = priority; - void onDisplayBitcoinPrioritySelected(TransactionPriority priority, double customValue) { + void onDisplayBitcoinPrioritySelected( + TransactionPriority priority, double customValue) { if (_wallet.type == WalletType.bitcoin) { _settingsStore.customBitcoinFeeRate = customValue.round(); } @@ -132,12 +138,13 @@ abstract class OtherSettingsViewModelBase with Store { } @computed - double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble(); + double get customBitcoinFeeRate => + _settingsStore.customBitcoinFeeRate.toDouble(); int? get customPriorityItemIndex { final priorities = priorityForWalletType(walletType); - final customItem = priorities - .firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom()); + final customItem = priorities.firstWhereOrNull( + (element) => element == bitcoin!.getBitcoinTransactionPriorityCustom()); return customItem != null ? priorities.indexOf(customItem) : null; } diff --git a/scripts/android/build_all.sh b/scripts/android/build_all.sh index 5093d231d0..ec70f02a60 100755 --- a/scripts/android/build_all.sh +++ b/scripts/android/build_all.sh @@ -10,6 +10,6 @@ DIR=$(dirname "$0") case $APP_ANDROID_TYPE in "monero.com") $DIR/build_monero_all.sh ;; "cakewallet") $DIR/build_monero_all.sh - $DIR/build_haven.sh ;; + $DIR/build_haven_all.sh ;; "haven") $DIR/build_haven_all.sh ;; esac diff --git a/scripts/android/build_monero_all.sh b/scripts/android/build_monero_all.sh index f683d5d276..1c3490d747 100755 --- a/scripts/android/build_monero_all.sh +++ b/scripts/android/build_monero_all.sh @@ -41,8 +41,8 @@ else fi unxz -f ../monero_c/release/monero/x86_64-linux-android_libwallet2_api_c.so.xz -unxz -f ../monero_c/release/wownero/x86_64-linux-android_libwallet2_api_c.so.xz +# unxz -f ../monero_c/release/wownero/x86_64-linux-android_libwallet2_api_c.so.xz unxz -f ../monero_c/release/monero/arm-linux-androideabi_libwallet2_api_c.so.xz -unxz -f ../monero_c/release/wownero/arm-linux-androideabi_libwallet2_api_c.so.xz +# unxz -f ../monero_c/release/wownero/arm-linux-androideabi_libwallet2_api_c.so.xz unxz -f ../monero_c/release/monero/aarch64-linux-android_libwallet2_api_c.so.xz -unxz -f ../monero_c/release/wownero/aarch64-linux-android_libwallet2_api_c.so.xz \ No newline at end of file +# unxz -f ../monero_c/release/wownero/aarch64-linux-android_libwallet2_api_c.so.xz \ No newline at end of file diff --git a/scripts/android/build_openssl.sh b/scripts/android/build_openssl.sh index b67479464f..33788c8b9e 100755 --- a/scripts/android/build_openssl.sh +++ b/scripts/android/build_openssl.sh @@ -1,6 +1,6 @@ #!/bin/sh -set -e +set -e -x . ./config.sh OPENSSL_FILENAME=openssl-1.1.1q.tar.gz @@ -24,7 +24,7 @@ echo $OPENSSL_SHA256 $OPENSSL_FILE_PATH | sha256sum -c - || exit 1 for arch in "aarch" "aarch64" "i686" "x86_64" do PREFIX=$WORKDIR/prefix_${arch} -TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/darwin-x86_64 +TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64 PATH="${TOOLCHAIN}/bin:${ORIGINAL_PATH}" case $arch in "aarch") X_ARCH="android-arm";; diff --git a/scripts/android/build_unbound.sh b/scripts/android/build_unbound.sh index fe3549f07c..afe848a413 100755 --- a/scripts/android/build_unbound.sh +++ b/scripts/android/build_unbound.sh @@ -9,7 +9,7 @@ EXPAT_SRC_DIR=$WORKDIR/libexpat for arch in "aarch" "aarch64" "i686" "x86_64" do PREFIX=$WORKDIR/prefix_${arch} -TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/darwin-x86_64 +TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64 PATH="${TOOLCHAIN_BASE_DIR}_${arch}/bin:${ORIGINAL_PATH}" cd $WORKDIR @@ -38,7 +38,7 @@ UNBOUND_SRC_DIR=$WORKDIR/unbound-1.16.2 for arch in "aarch" "aarch64" "i686" "x86_64" do PREFIX=$WORKDIR/prefix_${arch} -TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/darwin-x86_64 +TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64 case $arch in "aarch") TOOLCHAIN_BIN_PATH=${TOOLCHAIN_BASE_DIR}_${arch}/arm-linux-androideabi/bin;; diff --git a/scripts/android/copy_monero_deps.sh b/scripts/android/copy_monero_deps.sh index d59e9d7f01..ed8181e6be 100755 --- a/scripts/android/copy_monero_deps.sh +++ b/scripts/android/copy_monero_deps.sh @@ -41,5 +41,4 @@ done mkdir -p ${CW_HAVEN_EXTERNAL_DIR}/include mkdir -p ${CW_MONERO_EXTERNAL_DIR}/include -cp $CW_EXRTERNAL_DIR/x86/include/monero/wallet2_api.h ${CW_MONERO_EXTERNAL_DIR}/include cp $CW_EXRTERNAL_DIR/x86/include/haven/wallet2_api.h ${CW_HAVEN_EXTERNAL_DIR}/include diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index 77318351e5..8eac33e2a6 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana" # TODO: fix and add back --haven + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --polygon --nano --bitcoinCash --solana" # TODO: fix and add back --haven ;; $HAVEN) CONFIG_ARGS="--haven" diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 565f9eec5a..28094fc5a8 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,11 +6,10 @@ #include "generated_plugin_registrant.h" -#include +#include #include #include #include -#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -22,8 +21,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("LocalAuthPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); - PlatformDeviceIdWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("PlatformDeviceIdWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 74f32ec52a..57d44c08e4 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,11 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST - connectivity_plus_windows + connectivity_plus flutter_secure_storage_windows local_auth_windows permission_handler_windows - platform_device_id_windows url_launcher_windows ) From 2d0ab866f3eeb73ad560628f470d351fc7b9fd55 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Fri, 3 May 2024 13:16:34 +0200 Subject: [PATCH 062/242] fix pr script --- .github/workflows/pr_test_build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 98f95ae885..bd64ab33a7 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -72,7 +72,8 @@ jobs: /opt/android/cake_wallet/cw_monero/android/.cxx /opt/android/cake_wallet/cw_monero/ios/External /opt/android/cake_wallet/cw_shared_external/ios/External - key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }} + /opt/android/cake_wallet/scripts/monero_c/release + key: ${{ hashFiles('**/build_monero_all.sh', '**/build_haven.sh', '**/monero_api.cpp') }} - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} name: Generate Externals From 924afdf433011577587872bbc1df4fdb1f77904c Mon Sep 17 00:00:00 2001 From: m Date: Fri, 3 May 2024 16:50:13 +0100 Subject: [PATCH 063/242] Fixes for build monero.dart (monero_c) for windows. --- .gitignore | 3 +++ build-guide-win.md | 18 +++++++++++++----- cakewallet.bat | 10 +++++----- tool/configure.dart | 9 +++++---- windows/CMakeLists.txt | 3 +++ windows/flutter/CMakeLists.txt | 7 ++++++- 6 files changed, 35 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 6f2d0a1823..a9c21599d8 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,6 @@ macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png macos/Runner/Configs/AppInfo.xcconfig + +# Monero.dart (Monero_C) +scripts/monero_c \ No newline at end of file diff --git a/build-guide-win.md b/build-guide-win.md index 30c0219149..6ace961af5 100644 --- a/build-guide-win.md +++ b/build-guide-win.md @@ -16,15 +16,23 @@ These steps will help you configure and execute a build of CakeWallet from its s ### 1. Installing Package Dependencies For build CakeWallet windows application from sources you will be needed to have: -> [Install Flutter](https://docs.flutter.dev/get-started/install/windows) follow this guide until `Android setup` section (it's not necessary for this build process). -> [Install adition for Flutter SDK](https://docs.flutter.dev/platform-integration/desktop#additional-windows-requirements). Then install `Desktop development with C++` packages via GUI Visual Studio 2022, or Visual Studio Build Tools 2022 including: `C++ Build Tools core features`, `C++ 2022 Redistributable Update`, `C++ core desktop features`, `MVC v143 - VS 2022 C++ x64/x86 build tools`, `C++ CMake tools for Windwos`, `Testing tools core features - Build Tools`, `C++ AddressSanitizer`. +> [Install Flutter]Follow installation guide (https://docs.flutter.dev/get-started/install/windows) and install do not miss to dev tools (install https://docs.flutter.dev/get-started/install/windows/desktop#development-tools) which are required for windows desktop development (need to install Git for Windows and Visual Studio 2022). Then install `Desktop development with C++` packages via GUI Visual Studio 2022, or Visual Studio Build Tools 2022 including: `C++ Build Tools core features`, `C++ 2022 Redistributable Update`, `C++ core desktop features`, `MVC v143 - VS 2022 C++ x64/x86 build tools`, `C++ CMake tools for Windwos`, `Testing tools core features - Build Tools`, `C++ AddressSanitizer`. +> [Install WSL] for building monero dependencies need to install Windows WSL (https://learn.microsoft.com/en-us/windows/wsl/install) and required packages for WSL (Ubuntu): +`$ sudo apt update ` +`$ sudo apt build-essential cmake gcc-mingw-w64 g++-mingw-w64 autoconf libtool pkg-config` ### 2. Pull CakeWallet source code You can downlaod CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git by following next command: -`$ git clone https://github.com/cake-tech/cake_wallet.git --branch windows` -OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/windows.zip) +`$ git clone https://github.com/cake-tech/cake_wallet.git --branch MrCyjaneK-cyjan-monerodart` +OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/MrCyjaneK-cyjan-monerodart.zip) + +### 3. Build Monero, Monero_c and their dependencies + +For use monero in the application need to build Monero wrapper - Monero_C which will be used by monero.dart package. For that need to run shell (bash - typically same named utility should be available after WSL is enabled in your system) with previously installed WSL, then change current directory to the application project directory with your used shell and then change current directory to `scripts/windows`: `$ cd scripts/windows`. Run build script: `$ ./build_all.sh`. + +### 4. Configure and build CakeWallet application -### 3. Configure and build CakeWallet application To configure the application open directory where you have downloaded or unarchived CakeWallet sources and run `cakewallet.bat`. +Or if you used WSL and have active shell session you can run `$ ./cakewallet.sh` script in `scripts/windows` which will run `cakewallet.bat` in WSL. After execution of `cakewallet.bat` you should to get `Cake Wallet.zip` in project root directory which will contains `CakeWallet.exe` file and another needed files for run the application. Now you can extract files from `Cake Wallet.zip` archive and run the application. diff --git a/cakewallet.bat b/cakewallet.bat index dd9b4b9161..dba666eed7 100644 --- a/cakewallet.bat +++ b/cakewallet.bat @@ -1,12 +1,12 @@ @echo off -set cw_win_app_config=--bitcoin --ethereum +set cw_win_app_config=--monero --bitcoin --ethereum set cw_root=%cd% set cw_archive_name=Cake Wallet.zip set cw_archive_path=%cw_root%\%cw_archive_name% set secrets_file_path=lib\.secrets.g.dart -set release_dir=build\windows\runner\Release -set tools_root=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\14.36.32532\x64\Microsoft.VC143.CRT - +set release_dir=build\windows\x64\runner\Release +@REM Path could be different +if [%~1]==[] (set tools_root=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.38.33135\x64\Microsoft.VC143.CRT) else (set tools_root=%1) echo === Generating pubspec.yaml === copy /Y pubspec_description.yaml pubspec.yaml > nul call flutter pub get > nul @@ -20,7 +20,7 @@ IF NOT EXIST "%secrets_file_path%" ( ) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===) echo === Generating mobx models === -for /d %%i in (cw_core cw_bitcoin cw_ethereum cw_monero .) do ( +for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm .) do ( cd %%i call flutter pub get > nul call dart run build_runner build --delete-conflicting-outputs > nul diff --git a/tool/configure.dart b/tool/configure.dart index 8c77f16e95..d74b948073 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1087,10 +1087,11 @@ Future generatePubspec( final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); - final dependenciesIndex = inputLines.indexWhere( - (line) => Platform.isWindows - ? line.toLowerCase() == 'dependencies:\r' // On Windows it could contains `\r` (Carriage Return) - : line.toLowerCase() == 'dependencies:'); + final dependenciesIndex = inputLines.indexWhere((line) => Platform.isWindows + // On Windows it could contains `\r` (Carriage Return). It could be fixed in newer dart versions. + ? line.toLowerCase() == 'dependencies:\r' || + line.toLowerCase() == 'dependencies:' + : line.toLowerCase() == 'dependencies:'); var output = cwCore; if (hasMonero) { diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 76256dc45b..1af4450847 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -81,6 +81,9 @@ install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR} install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x86_64-w64-mingw32_libwallet2_api_c.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "monero_libwallet2_api_c.dll" + COMPONENT Runtime) + if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index 930d2071a3..903f4899d6 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS From ac30e14352d9b235f81673bb1c3e02ba855a4515 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Sat, 4 May 2024 00:26:08 +0200 Subject: [PATCH 064/242] monero build script --- macos/Runner.xcodeproj/project.pbxproj | 20 ++++++++-- .../xcshareddata/xcschemes/Runner.xcscheme | 8 ++-- macos/monero_libwallet2_api_c.dylib | 1 + scripts/macos/build_monero_all.sh | 40 ++++++++++++------- 4 files changed, 47 insertions(+), 22 deletions(-) create mode 120000 macos/monero_libwallet2_api_c.dylib diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 7bf0cd5e8f..de97e6bf97 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 4171CB1F5A4EA2E4DC33F52F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B38D1DBC56DBD386923BC063 /* Pods_Runner.framework */; }; 9F565D5929954F53009A75FB /* secRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F565D5729954F53009A75FB /* secRandom.swift */; }; + CED5DBE42BE59BBF0065028F /* monero_libwallet2_api_c.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = CED5DBE32BE59BBF0065028F /* monero_libwallet2_api_c.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -51,6 +52,16 @@ name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; + CED5DBE02BE59B230065028F /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + CED5DBE42BE59BBF0065028F /* monero_libwallet2_api_c.dylib in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -59,7 +70,7 @@ 2A820A13B0719E9E0CD6686F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* Monero.com.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Monero.com.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = .app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,6 +87,7 @@ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 9F565D5729954F53009A75FB /* secRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = secRandom.swift; path = CakeWallet/secRandom.swift; sourceTree = ""; }; B38D1DBC56DBD386923BC063 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CED5DBE32BE59BBF0065028F /* monero_libwallet2_api_c.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = monero_libwallet2_api_c.dylib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -104,6 +116,7 @@ 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( + CED5DBE32BE59BBF0065028F /* monero_libwallet2_api_c.dylib */, 9F565D5729954F53009A75FB /* secRandom.swift */, 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, @@ -116,7 +129,7 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* Monero.com.app */, + 33CC10ED2044A3C60003C045 /* .app */, ); name = Products; sourceTree = ""; @@ -189,6 +202,7 @@ 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 5592D00118C2EA3C5E0B5FDF /* [CP] Embed Pods Frameworks */, + CED5DBE02BE59B230065028F /* CopyFiles */, ); buildRules = ( ); @@ -197,7 +211,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* Monero.com.app */; + productReference = 33CC10ED2044A3C60003C045 /* .app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1f89f68354..4640940719 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -54,7 +54,7 @@ @@ -71,7 +71,7 @@ diff --git a/macos/monero_libwallet2_api_c.dylib b/macos/monero_libwallet2_api_c.dylib new file mode 120000 index 0000000000..b0ccd724f6 --- /dev/null +++ b/macos/monero_libwallet2_api_c.dylib @@ -0,0 +1 @@ +../scripts/monero_c/release/monero/host-apple-darwin_libwallet2_api_c.dylib \ No newline at end of file diff --git a/scripts/macos/build_monero_all.sh b/scripts/macos/build_monero_all.sh index f7e55909b6..53083f1caf 100755 --- a/scripts/macos/build_monero_all.sh +++ b/scripts/macos/build_monero_all.sh @@ -1,20 +1,30 @@ #!/bin/sh +set -x -e -ARCH=`uname -m` +cd "$(dirname "$0")" -. ./config.sh +NPROC="-j$(nproc)" -case $ARCH in - arm64) - ./build_openssl_arm64.sh - ./build_boost_arm64.sh;; - x86_64) - ./build_openssl_x86_64.sh - ./build_boost_x86_64.sh;; -esac +../prepare_moneroc.sh -./build_zmq.sh -./build_expat.sh -./build_unbound.sh -./build_sodium.sh -./build_monero.sh \ No newline at end of file +# NOTE: -j1 is intentional. Otherwise you will run into weird behaviour on macos +if [[ ! "x$USE_DOCKER" == "x" ]]; +then + for COIN in monero; + do + pushd ../monero_c + echo "unsupported!" + exit 1 + popd + done +else + for COIN in monero; + do + pushd ../monero_c + ./build_single.sh ${COIN} host-apple-darwin $NPROC + popd + done +fi + +unxz -f ../monero_c/release/monero/host-apple-darwin_libwallet2_api_c.dylib.xz +# unxz -f ../monero_c/release/wownero/host-apple-darwin_libwallet2_api_c.dylib.xz From e504cf1087b5f1e68368e2964d74ff19b60d7d36 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Sat, 4 May 2024 14:26:04 +0200 Subject: [PATCH 065/242] wip: ios build script --- scripts/ios/build_monero_all.sh | 43 +++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/scripts/ios/build_monero_all.sh b/scripts/ios/build_monero_all.sh index 2b61f6db0c..89ba7e8042 100755 --- a/scripts/ios/build_monero_all.sh +++ b/scripts/ios/build_monero_all.sh @@ -1,10 +1,39 @@ #!/bin/sh . ./config.sh -./install_missing_headers.sh -./build_openssl.sh -./build_boost.sh -./build_sodium.sh -./build_zmq.sh -./build_unbound.sh -./build_monero.sh \ No newline at end of file +# ./install_missing_headers.sh +# ./build_openssl.sh +# ./build_boost.sh +# ./build_sodium.sh +# ./build_zmq.sh +# ./build_unbound.sh + +set -x -e + +cd "$(dirname "$0")" + +NPROC="-j$(nproc)" + +../prepare_moneroc.sh + +# NOTE: -j1 is intentional. Otherwise you will run into weird behaviour on macos +if [[ ! "x$USE_DOCKER" == "x" ]]; +then + for COIN in monero; + do + pushd ../monero_c + echo "unsupported!" + exit 1 + popd + done +else + for COIN in monero; + do + pushd ../monero_c + ./build_single.sh ${COIN} host-apple-ios $NPROC + popd + done +fi + +unxz -f ../monero_c/release/monero/host-apple-ios_libwallet2_api_c.dylib.xz +# unxz -f ../monero_c/release/wownero/host-apple-ios_libwallet2_api_c.dylib.xz From 781cbc27e8962d7f6552b3ec97593f7412175ae0 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 6 May 2024 17:31:41 -0300 Subject: [PATCH 066/242] refactor: unchanged file --- .../address_edit_or_create_page.dart | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/src/screens/subaddress/address_edit_or_create_page.dart b/lib/src/screens/subaddress/address_edit_or_create_page.dart index ed6369a736..750af846ed 100644 --- a/lib/src/screens/subaddress/address_edit_or_create_page.dart +++ b/lib/src/screens/subaddress/address_edit_or_create_page.dart @@ -13,7 +13,8 @@ class AddressEditOrCreatePage extends BasePage { : _formKey = GlobalKey(), _labelController = TextEditingController(), super() { - _labelController.addListener(() => addressEditOrCreateViewModel.label = _labelController.text); + _labelController.addListener( + () => addressEditOrCreateViewModel.label = _labelController.text); _labelController.text = addressEditOrCreateViewModel.label; } @@ -54,8 +55,10 @@ class AddressEditOrCreatePage extends BasePage { : S.of(context).new_subaddress_create, color: Theme.of(context).primaryColor, textColor: Colors.white, - isLoading: addressEditOrCreateViewModel.state is AddressIsSaving, - isDisabled: addressEditOrCreateViewModel.label?.isEmpty ?? true, + isLoading: + addressEditOrCreateViewModel.state is AddressIsSaving, + isDisabled: + addressEditOrCreateViewModel.label?.isEmpty ?? true, ), ) ], @@ -67,13 +70,14 @@ class AddressEditOrCreatePage extends BasePage { if (_isEffectsInstalled) { return; } - reaction((_) => addressEditOrCreateViewModel.state, (AddressEditOrCreateState state) { - if (state is AddressSavedSuccessfully) { - WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.of(context).pop()); - } - }); + reaction((_) => addressEditOrCreateViewModel.state, + (AddressEditOrCreateState state) { + if (state is AddressSavedSuccessfully) { + WidgetsBinding.instance + .addPostFrameCallback((_) => Navigator.of(context).pop()); + } + }); _isEffectsInstalled = true; } } - From bd02c1febfc53aa757964f937a586c08fbeab393 Mon Sep 17 00:00:00 2001 From: m Date: Mon, 6 May 2024 21:41:40 +0100 Subject: [PATCH 067/242] Added build guides for iOS and macOS. Replaced nproc call on macOS. Added macOS configuration for configure_cake_wallet.sh script. --- configure_cake_wallet.sh | 8 ++- howto-build-ios.md | 101 +++++++++++++++++++++++++++++ howto-build-macos.md | 103 ++++++++++++++++++++++++++++++ ios/Podfile.lock | 27 -------- macos/Podfile.lock | 21 ------ scripts/ios/build_monero_all.sh | 2 +- scripts/macos/build_monero_all.sh | 2 +- 7 files changed, 213 insertions(+), 51 deletions(-) create mode 100644 howto-build-ios.md create mode 100644 howto-build-macos.md diff --git a/configure_cake_wallet.sh b/configure_cake_wallet.sh index d18f6c22a8..2eb6d8f25a 100755 --- a/configure_cake_wallet.sh +++ b/configure_cake_wallet.sh @@ -2,8 +2,9 @@ IOS="ios" ANDROID="android" +MACOS="macos" -PLATFORMS=($IOS $ANDROID) +PLATFORMS=($IOS $ANDROID $MACOS) PLATFORM=$1 if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then @@ -16,6 +17,11 @@ if [ "$PLATFORM" == "$IOS" ]; then cd scripts/ios fi +if [ "$PLATFORM" == "$MACOS" ]; then + echo "Configuring for macOS" + cd scripts/macos +fi + if [ "$PLATFORM" == "$ANDROID" ]; then echo "Configuring for Android" cd scripts/android diff --git a/howto-build-ios.md b/howto-build-ios.md new file mode 100644 index 0000000000..3abf3e9154 --- /dev/null +++ b/howto-build-ios.md @@ -0,0 +1,101 @@ +# Building CakeWallet for iOS + +## Requirements and Setup + +The following are the system requirements to build CakeWallet for your iOS device. + +``` +macOS >= 14.0 +Xcode 15.3 +Flutter 3.10.x +``` + +## Building CakeWallet on iOS + +These steps will help you configure and execute a build of CakeWallet from its source code. + +### 1. Installing Package Dependencies + +CakeWallet cannot be built without the following packages installed on your build system. + +For installing dependency tools you can use brew [Install brew](https://brew.sh). + +You may easily install them on your build system with the following command: + +`$ brew install cmake xz cocoapods` + +### 2. Installing Xcode + +You may download and install the latest version of [Xcode](https://developer.apple.com/xcode/) from macOS App Store. + +### 3. Installing Flutter + +Need to install flutter with version `3.10.x`. For this please check section [Install Flutter](https://docs.flutter.dev/get-started/install/macos/mobile-ios?tab=download). + +### 4. Verify Installations + +Verify that the Flutter and Xcode have been correctly installed on your system with the following command: + +`$ flutter doctor` + +The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding. +``` +Doctor summary (to see all details, run flutter doctor -v): +[✓] Flutter (Channel stable, 3.10.x, on macOS 14.x.x) +[✓] Xcode - develop for iOS and macOS (Xcode 15.3) +``` + +### 5. Acquiring the CakeWallet source code + +Download the source code. + +`$ git clone https://github.com/cake-tech/cake_wallet.git --branch main` + +Proceed into the source code before proceeding with the next steps: + +`$ cd cake_wallet/scripts/ios/` + +### 6. Execute Build & Setup Commands for CakeWallet + +We need to generate project settings like app name, app icon, package name, etc. For this need to setup environment variables and configure project files. + +Please pick what app you want to build: cakewallet or monero.com. + +`$ source ./app_env.sh ` +(it should be like `$ source ./app_env.sh cakewallet` or `$ source ./app_env.sh monero.com`) + +Then run configuration script for setup app name, app icon and etc: + +`$ ./app_config.sh` + +Build the Monero libraries and their dependencies: + +`$ ./build_monero_all.sh` + +It is now time to change back to the base directory of the CakeWallet source code: + +`$ cd ../../` + +Install Flutter package dependencies with this command: + +`$ flutter pub get` + +Your CakeWallet binary will be built with cryptographic salts, which are used for secure encryption of your data. You may generate these secret salts with the following command: + +`$ flutter packages pub run tool/generate_new_secrets.dart` + +Then we need to generate localization files and mobx models. + +`$ ./configure_cake_wallet.sh ios` + +### 7. Build! + +`$ flutter build ios --release` + +Then you can open `ios/Runner.xcworkspace` with Xcode and you can to archive the application. + +Or if you want to run to connected device: + +`$ flutter run --release` + +Copyright (c) 2024 Cake Technologies LLC. diff --git a/howto-build-macos.md b/howto-build-macos.md new file mode 100644 index 0000000000..c8cc5bf52d --- /dev/null +++ b/howto-build-macos.md @@ -0,0 +1,103 @@ +# Building CakeWallet for macOS + +## Requirements and Setup + +The following are the system requirements to build CakeWallet for your macOS device. + +``` +macOS >= 14.0 +Xcode 15.3 +Flutter 3.10.x +``` + +## Building CakeWallet on macOS + +These steps will help you configure and execute a build of CakeWallet from its source code. + +### 1. Installing Package Dependencies + +CakeWallet cannot be built without the following packages installed on your build system. + +For installing dependency tools you can use brew [Install brew](https://brew.sh). + +You may easily install them on your build system with the following command: + +`$ brew install cmake xz unbound boost@1.76 zmq cocoapods` + +`$ brew link boost@1.76` + +### 2. Installing Xcode + +You may download and install the latest version of [Xcode](https://developer.apple.com/xcode/) from macOS App Store. + +### 3. Installing Flutter + +Need to install flutter with version `3.10.x`. For this please check section [Install Flutter](https://docs.flutter.dev/get-started/install/macos/desktop?tab=download). + +### 4. Verify Installations + +Verify that Flutter and Xcode have been correctly installed on your system with the following command: + +`$ flutter doctor` + +The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding. +``` +Doctor summary (to see all details, run flutter doctor -v): +[✓] Flutter (Channel stable, 3.10.x, on macOS 14.x.x) +[✓] Xcode - develop for iOS and macOS (Xcode 15.3) +``` + +### 5. Acquiring the CakeWallet source code + +Download the source code. + +`$ git clone https://github.com/cake-tech/cake_wallet.git --branch main` + +Proceed into the source code before proceeding with the next steps: + +`$ cd cake_wallet/scripts/macos/` + +### 6. Execute Build & Setup Commands for CakeWallet + +We need to generate project settings like app name, app icon, package name, etc. For this need to setup environment variables and configure project files. + +Please pick what app you want to build: cakewallet or monero.com. + +`$ source ./app_env.sh ` +(it should be like `$ source ./app_env.sh cakewallet` or `$ source ./app_env.sh monero.com`) + +Then run configuration script for setup app name, app icon and etc: + +`$ ./app_config.sh` + +Build the Monero libraries and their dependencies: + +`$ ./build_monero_all.sh` + +It is now time to change back to the base directory of the CakeWallet source code: + +`$ cd ../../` + +Install Flutter package dependencies with this command: + +`$ flutter pub get` + +Your CakeWallet binary will be built with cryptographic salts, which are used for secure encryption of your data. You may generate these secret salts with the following command: + +`$ flutter packages pub run tool/generate_new_secrets.dart` + +Then we need to generate localization files and mobx models. + +`$ ./configure_cake_wallet.sh macos` + +### 7. Build! + +`$ flutter build macos --release` + +Then you can open `macos/Runner.xcworkspace` with Xcode and you can to archive the application. + +Or if you want to run to connected device: + +`$ flutter run --release` + +Copyright (c) 2024 Cake Technologies LLC. diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 67c0c9ee82..68003b8fe9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -27,29 +27,6 @@ PODS: - cw_haven/Sodium (0.0.1): - cw_shared_external - Flutter - - cw_monero (0.0.2): - - cw_monero/Boost (= 0.0.2) - - cw_monero/Monero (= 0.0.2) - - cw_monero/OpenSSL (= 0.0.2) - - cw_monero/Sodium (= 0.0.2) - - cw_monero/Unbound (= 0.0.2) - - cw_shared_external - - Flutter - - cw_monero/Boost (0.0.2): - - cw_shared_external - - Flutter - - cw_monero/Monero (0.0.2): - - cw_shared_external - - Flutter - - cw_monero/OpenSSL (0.0.2): - - cw_shared_external - - Flutter - - cw_monero/Sodium (0.0.2): - - cw_shared_external - - Flutter - - cw_monero/Unbound (0.0.2): - - cw_shared_external - - Flutter - cw_shared_external (0.0.1): - cw_shared_external/Boost (= 0.0.1) - cw_shared_external/OpenSSL (= 0.0.1) @@ -162,7 +139,6 @@ DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - CryptoSwift - cw_haven (from `.symlinks/plugins/cw_haven/ios`) - - cw_monero (from `.symlinks/plugins/cw_monero/ios`) - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`) - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) @@ -210,8 +186,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/connectivity_plus/ios" cw_haven: :path: ".symlinks/plugins/cw_haven/ios" - cw_monero: - :path: ".symlinks/plugins/cw_monero/ios" cw_shared_external: :path: ".symlinks/plugins/cw_shared_external/ios" device_display_brightness: @@ -265,7 +239,6 @@ SPEC CHECKSUMS: connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d CryptoSwift: b9c701d6f5011df23794dbf7f2e480a77835d83d cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a - cw_monero: 4cf3b96f2da8e95e2ef7d6703dd4d2c509127b7d cw_shared_external: 2972d872b8917603478117c9957dfca611845a92 device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 diff --git a/macos/Podfile.lock b/macos/Podfile.lock index f83e18c409..261f76f063 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -2,23 +2,6 @@ PODS: - connectivity_plus (0.0.1): - FlutterMacOS - ReachabilitySwift - - cw_monero (0.0.1): - - cw_monero/Boost (= 0.0.1) - - cw_monero/Monero (= 0.0.1) - - cw_monero/OpenSSL (= 0.0.1) - - cw_monero/Sodium (= 0.0.1) - - cw_monero/Unbound (= 0.0.1) - - FlutterMacOS - - cw_monero/Boost (0.0.1): - - FlutterMacOS - - cw_monero/Monero (0.0.1): - - FlutterMacOS - - cw_monero/OpenSSL (0.0.1): - - FlutterMacOS - - cw_monero/Sodium (0.0.1): - - FlutterMacOS - - cw_monero/Unbound (0.0.1): - - FlutterMacOS - device_info_plus (0.0.1): - FlutterMacOS - devicelocale (0.0.1): @@ -52,7 +35,6 @@ PODS: DEPENDENCIES: - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) - - cw_monero (from `Flutter/ephemeral/.symlinks/plugins/cw_monero/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`) - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) @@ -75,8 +57,6 @@ SPEC REPOS: EXTERNAL SOURCES: connectivity_plus: :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos - cw_monero: - :path: Flutter/ephemeral/.symlinks/plugins/cw_monero/macos device_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos devicelocale: @@ -106,7 +86,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 - cw_monero: f8b7f104508efba2591548e76b5c058d05cba3f0 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d diff --git a/scripts/ios/build_monero_all.sh b/scripts/ios/build_monero_all.sh index 89ba7e8042..261ebd710b 100755 --- a/scripts/ios/build_monero_all.sh +++ b/scripts/ios/build_monero_all.sh @@ -12,7 +12,7 @@ set -x -e cd "$(dirname "$0")" -NPROC="-j$(nproc)" +NPROC="-j$(sysctl -n hw.logicalcpu)" ../prepare_moneroc.sh diff --git a/scripts/macos/build_monero_all.sh b/scripts/macos/build_monero_all.sh index 53083f1caf..ae9c8889b6 100755 --- a/scripts/macos/build_monero_all.sh +++ b/scripts/macos/build_monero_all.sh @@ -3,7 +3,7 @@ set -x -e cd "$(dirname "$0")" -NPROC="-j$(nproc)" +NPROC="-j$(sysctl -n hw.logicalcpu)" ../prepare_moneroc.sh From eef25149c9bbf221535740de5584a17d2cedab27 Mon Sep 17 00:00:00 2001 From: m Date: Tue, 7 May 2024 15:43:43 +0100 Subject: [PATCH 068/242] Update monero.dart and monero_c versions. --- cw_monero/pubspec.yaml | 2 +- scripts/prepare_moneroc.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index ce04159091..a033028e3d 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: monero: git: url: https://git.mrcyjanek.net/mrcyjanek/monero.dart - ref: 57e075ee67d16aa0f3f75fba67d79529fbc73a6c + ref: 6a17a405a1a260fa228b2f4fc94044088a4335ac dev_dependencies: flutter_test: diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh index 0ae2705886..87cf41d7fe 100755 --- a/scripts/prepare_moneroc.sh +++ b/scripts/prepare_moneroc.sh @@ -10,7 +10,7 @@ if [[ ! -d "monero_c" ]]; then git clone https://github.com/mrcyjanek/monero_c --branch rewrite-wip cd monero_c - git checkout cd90f3bcd0349759030751ec7ce84eec6ee80c43 + git checkout 07d3a5d7da8c1ad3603756a7621fcfc5399110a4 git reset --hard git submodule update --init --force --recursive ./apply_patches.sh monero From 950cc84346c589c27442e48900bf4516f75ff493 Mon Sep 17 00:00:00 2001 From: m Date: Tue, 7 May 2024 15:49:23 +0100 Subject: [PATCH 069/242] Add missed windows build scripts --- scripts/windows/build_all.sh | 37 +++++++++++++++++++++++++++++++++++ scripts/windows/cakewallet.sh | 7 +++++++ 2 files changed, 44 insertions(+) create mode 100755 scripts/windows/build_all.sh create mode 100755 scripts/windows/cakewallet.sh diff --git a/scripts/windows/build_all.sh b/scripts/windows/build_all.sh new file mode 100755 index 0000000000..e77f6edb57 --- /dev/null +++ b/scripts/windows/build_all.sh @@ -0,0 +1,37 @@ +set -x -e + +cd "$(dirname "$0")" + +if [[ ! "x$(uname)" == "xLinux" ]]; +then + echo "Only Linux hosts can build windows (yes, i know)"; + exit 1 +fi + +../prepare_moneroc.sh + +# export USE_DOCKER="ON" + +pushd ../monero_c + set +e + command -v sudo && export SUDO=sudo + set -e + NPROC="-j$(nproc)" + if [[ ! "x$USE_DOCKER" == "x" ]]; + then + for COIN in monero wownero; + do + $SUDO docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 gperf libtinfo5; ./build_single.sh ${COIN} x86_64-w64-mingw32 $NPROC" + # $SUDO docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc-mingw-w64-i686 g++-mingw-w64-i686 gperf libtinfo5; ./build_single.sh ${COIN} i686-w64-mingw32 $NPROC" + done + else + for COIN in monero wownero; + do + $SUDO ./build_single.sh ${COIN} x86_64-w64-mingw32 $NPROC + # $SUDO ./build_single.sh ${COIN} i686-w64-mingw32 $NPROC + done + fi +popd + +$SUDO unxz -f ../monero_c/release/monero/*.dll.xz +$SUDO unxz -f ../monero_c/release/wownero/*.dll.xz diff --git a/scripts/windows/cakewallet.sh b/scripts/windows/cakewallet.sh new file mode 100755 index 0000000000..61e526a739 --- /dev/null +++ b/scripts/windows/cakewallet.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# This (wrapper) script should be run in wsl with installed (windows "side") flutter +# and available cmd.exe in PATH +# Assume that we are in scripts/windows dir +CW_ROOT=`pwd`/../.. +cd $CW_ROOT +cmd.exe /c cakewallet.bat $1 \ No newline at end of file From 1cb71895f62cbe8f81e8a9beb03b6bd3057b1bce Mon Sep 17 00:00:00 2001 From: m Date: Tue, 7 May 2024 15:58:23 +0100 Subject: [PATCH 070/242] Update the application configuration for windows build script. --- cakewallet.bat | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cakewallet.bat b/cakewallet.bat index dba666eed7..e429d109b1 100644 --- a/cakewallet.bat +++ b/cakewallet.bat @@ -1,5 +1,5 @@ @echo off -set cw_win_app_config=--monero --bitcoin --ethereum +set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana set cw_root=%cd% set cw_archive_name=Cake Wallet.zip set cw_archive_path=%cw_root%\%cw_archive_name% @@ -20,7 +20,7 @@ IF NOT EXIST "%secrets_file_path%" ( ) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===) echo === Generating mobx models === -for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm .) do ( +for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana .) do ( cd %%i call flutter pub get > nul call dart run build_runner build --delete-conflicting-outputs > nul From eccc3393efb1d41116ca228572c23052a4b7a8aa Mon Sep 17 00:00:00 2001 From: m Date: Tue, 7 May 2024 16:13:08 +0100 Subject: [PATCH 071/242] Update cw_monero pubspec lock file for monero.dart --- cw_monero/pubspec.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 7503cd96da..6d39bed6eb 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -414,8 +414,8 @@ packages: dependency: "direct main" description: path: "." - ref: "57e075ee67d16aa0f3f75fba67d79529fbc73a6c" - resolved-ref: "57e075ee67d16aa0f3f75fba67d79529fbc73a6c" + ref: "6a17a405a1a260fa228b2f4fc94044088a4335ac" + resolved-ref: "6a17a405a1a260fa228b2f4fc94044088a4335ac" url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart" source: git version: "0.0.0" From 69dd7a5321bec4fb824f1387e94804ee96150009 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Wed, 8 May 2024 05:04:47 +0300 Subject: [PATCH 072/242] Update pr_test_build.yml --- .github/workflows/pr_test_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 23902f110c..aa14573d15 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -42,7 +42,7 @@ jobs: - name: Flutter action uses: subosito/flutter-action@v1 with: - flutter-version: "3.19.5" + flutter-version: "3.19.6" channel: stable - name: Install package dependencies From 15ca7d6219f0ac2754f82d0428fc45b5f051dc5d Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Wed, 8 May 2024 08:40:57 -0300 Subject: [PATCH 073/242] chore: upgrade --- cw_nano/pubspec.lock | 124 ++++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 50 deletions(-) diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index 51e43145ea..10d254698a 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" asn1lib: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" url: "https://pub.dev" source: hosted - version: "7.2.7" + version: "7.2.10" built_collection: dependency: transitive description: @@ -228,10 +228,10 @@ packages: dependency: transitive description: name: encrypt - sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.3" fake_async: dependency: transitive description: @@ -244,10 +244,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" file: dependency: transitive description: @@ -281,10 +281,10 @@ packages: dependency: transitive description: name: flutter_mobx - sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" + sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e" url: "https://pub.dev" source: hosted - version: "2.0.6+5" + version: "2.2.1+1" flutter_test: dependency: "direct dev" description: flutter @@ -299,10 +299,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -347,10 +347,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -387,18 +387,18 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -467,10 +467,10 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mobx: dependency: "direct main" description: @@ -504,6 +504,14 @@ packages: url: "https://github.com/perishllc/nanoutil.git" source: git version: "1.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -524,26 +532,26 @@ packages: dependency: transitive description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -556,10 +564,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -580,10 +588,10 @@ packages: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: @@ -596,10 +604,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.9.1" pool: dependency: transitive description: @@ -608,6 +616,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" pub_semver: dependency: transitive description: @@ -636,26 +652,26 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -676,10 +692,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -713,10 +729,10 @@ packages: dependency: transitive description: name: socks5_proxy - sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a" + sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5+dev.2" source_gen: dependency: transitive description: @@ -829,22 +845,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.5" win32: dependency: transitive description: name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "5.5.0" xdg_directories: dependency: transitive description: @@ -862,5 +886,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" From 1ebd19fc738d08d8b164b0db7f59a100dab2885f Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Wed, 8 May 2024 10:28:25 -0300 Subject: [PATCH 074/242] chore: merge changes --- cw_bitcoin/lib/bitcoin_wallet.dart | 10 +- cw_bitcoin/lib/electrum.dart | 4 +- cw_bitcoin/lib/electrum_wallet.dart | 20 +- cw_bitcoin/pubspec.lock | 86 ++++---- cw_core/pubspec.lock | 112 +++++----- cw_haven/pubspec.lock | 184 +++++++++-------- cw_monero/pubspec.lock | 192 ++++++++++-------- cw_shared_external/pubspec.lock | 147 -------------- lib/di.dart | 90 ++++---- .../screens/dashboard/pages/balance_page.dart | 94 ++++++++- .../dashboard/pages/cake_features_page.dart | 89 +------- .../dashboard/pages/market_place_page.dart | 119 ----------- .../wallet_address_list_item.dart | 2 +- 13 files changed, 462 insertions(+), 687 deletions(-) delete mode 100644 cw_shared_external/pubspec.lock delete mode 100644 lib/src/screens/dashboard/pages/market_place_page.dart diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index f27a8bf1af..3091cacf54 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -58,6 +58,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { currency: networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc, ) { + // in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here) + // the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here) + // String derivationPath = walletInfo.derivationInfo!.derivationPath!; + // String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1"; + // final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType); walletAddresses = BitcoinWalletAddresses( walletInfo, initialAddresses: initialAddresses, @@ -68,7 +73,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { mainHd: hd, sideHd: accountHD.derive(1), network: networkParam ?? network, - masterHd: hd, + masterHd: + seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null, ); autorun((_) { @@ -171,7 +177,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialSilentAddresses: snp.silentAddresses, initialSilentAddressIndex: snp.silentAddressIndex, initialBalance: snp.balance, - seedBytes: seedBytes!, + seedBytes: seedBytes, initialRegularAddressIndex: snp.regularAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex, addressPageType: snp.addressPageType, diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index a76e9b7bab..89c5fb785b 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -36,8 +36,8 @@ class ElectrumClient { _errors = {}, unterminatedString = ''; - static const connectionTimeout = Duration(seconds: 10); - static const aliveTimerDuration = Duration(seconds: 10); + static const connectionTimeout = Duration(seconds: 5); + static const aliveTimerDuration = Duration(seconds: 4); bool get isConnected => _isConnected; Socket? socket; diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 58e807ed3e..2d17222131 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -376,19 +376,11 @@ abstract class ElectrumWalletBase await _subscribeForUpdates(); - int finished = 0; - void checkFinishedAllUpdates(void _) { - finished++; - if (finished == 2) syncStatus = SyncedSyncStatus(); - } - - // Always await first as it discovers new addresses in the process await updateTransactions(); + await updateAllUnspents(); + await updateBalance(); - updateAllUnspents().then(checkFinishedAllUpdates); - updateBalance().then(checkFinishedAllUpdates); - - updateFeeRates(); + await updateFeeRates(); } catch (e, stacktrace) { print(stacktrace); print(e.toString()); @@ -631,8 +623,8 @@ abstract class ElectrumWalletBase isSendAll: true, hasChange: false, memo: memo, - spendsSilentPayment: utxoDetails.spendsSilentPayment, spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX, + spendsSilentPayment: utxoDetails.spendsSilentPayment, ); } @@ -790,8 +782,8 @@ abstract class ElectrumWalletBase hasChange: true, isSendAll: false, memo: memo, - spendsSilentPayment: utxoDetails.spendsSilentPayment, spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX, + spendsSilentPayment: utxoDetails.spendsSilentPayment, ); } @@ -986,6 +978,8 @@ abstract class ElectrumWalletBase ? SegwitAddresType.p2wpkh.toString() : walletInfo.addressPageType.toString(), 'balance': balance[currency]?.toJSON(), + 'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index, + 'derivationPath': walletInfo.derivationInfo?.derivationPath, 'silent_addresses': walletAddresses.silentAddresses.map((addr) => addr.toJSON()).toList(), 'silent_address_index': walletAddresses.currentSilentAddressIndex.toString(), }); diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 65648d2a05..85c7bd7f7f 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -80,10 +80,10 @@ packages: description: path: "." ref: cake-update-v3 - resolved-ref: "2a18ab92a9f7136b76fcd1bf8480eaaa90e0a6b2" + resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149 url: "https://github.com/cake-tech/bitcoin_base" source: git - version: "4.0.0" + version: "4.2.1" bitcoin_flutter: dependency: "direct main" description: @@ -98,10 +98,10 @@ packages: description: path: "." ref: cake-update-v1 - resolved-ref: "7864de88e9a0b598a61b1e50d26f6f4477a6411c" + resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3 url: "https://github.com/cake-tech/blockchain_utils" source: git - version: "1.6.0" + version: "2.1.2" boolean_selector: dependency: transitive description: @@ -250,10 +250,10 @@ packages: dependency: "direct main" description: name: cryptography - sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35 + sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" cw_core: dependency: "direct main" description: @@ -297,10 +297,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" ffigen: dependency: transitive description: @@ -363,10 +363,10 @@ packages: dependency: transitive description: name: functional_data - sha256: aefdec4365452283b2a7cf420a3169654d51d3e9553069a22d76680d7a9d7c3d + sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" glob: dependency: transitive description: @@ -411,10 +411,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -459,10 +459,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -548,10 +548,10 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mobx: dependency: "direct main" description: @@ -596,26 +596,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -660,10 +660,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" url: "https://pub.dev" source: hosted - version: "3.8.0" + version: "3.9.1" pool: dependency: transitive description: @@ -761,10 +761,10 @@ packages: dependency: transitive description: name: socks5_proxy - sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a" + sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5+dev.2" source_gen: dependency: transitive description: @@ -794,7 +794,7 @@ packages: description: path: "." ref: master - resolved-ref: "0ac9108db2f475c5b685af9eb9df393dcc978820" + resolved-ref: a6b14bcc37ec16f56931e48afa8a8f8e6939431d url: "https://github.com/rafael-xmr/sp_scanner" source: git version: "0.0.1" @@ -870,14 +870,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" - uuid: - dependency: transitive - description: - name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" - url: "https://pub.dev" - source: hosted - version: "3.0.7" vector_math: dependency: transitive description: @@ -902,22 +894,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.5" win32: dependency: transitive description: name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "5.5.0" xdg_directories: dependency: transitive description: @@ -938,10 +938,10 @@ packages: dependency: transitive description: name: yaml_edit - sha256: c566f4f804215d84a7a2c377667f546c6033d5b34b4f9e60dfb09d17c4e97826 + sha256: e9c1a3543d2da0db3e90270dbb1e4eebc985ee5e3ffe468d83224472b2194a5f url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.16.6" diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index abfdbfc586..43c1ffb359 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -5,34 +5,34 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.4.1" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" asn1lib: dependency: transitive description: name: asn1lib - sha256: "21afe4333076c02877d14f4a89df111e658a6d466cbfc802eb705eb91bd5adfd" + sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6 url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.5.2" async: dependency: transitive description: @@ -85,18 +85,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.9" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.11" + version: "7.3.0" built_collection: dependency: transitive description: @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: built_value - sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309 + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.8.1" + version: "8.9.2" characters: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.6" encrypt: dependency: "direct main" description: @@ -197,10 +197,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" file: dependency: "direct main" description: @@ -226,10 +226,10 @@ packages: dependency: "direct main" description: name: flutter_mobx - sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4" + sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e" url: "https://pub.dev" source: hosted - version: "2.2.0+2" + version: "2.2.1+1" flutter_test: dependency: "direct dev" description: flutter @@ -239,10 +239,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -279,10 +279,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -319,18 +319,18 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -391,26 +391,26 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mobx: dependency: "direct main" description: name: mobx - sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78" + sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e" url: "https://pub.dev" source: hosted - version: "2.3.0+1" + version: "2.3.3+2" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: b26c7f9c20b38f0ea572c1ed3f29d8e027cb265538bbd1aed3ec198642cfca42 + sha256: "8e0d8653a0c720ad933cd8358f6f89f740ce89203657c13f25bea772ef1fff7c" url: "https://pub.dev" source: hosted - version: "2.6.0+1" + version: "2.6.1" nested: dependency: transitive description: @@ -439,26 +439,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -503,10 +503,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" url: "https://pub.dev" source: hosted - version: "3.7.4" + version: "3.9.1" pool: dependency: transitive description: @@ -519,10 +519,10 @@ packages: dependency: transitive description: name: provider - sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "6.1.2" pub_semver: dependency: transitive description: @@ -564,10 +564,10 @@ packages: dependency: "direct main" description: name: socks5_proxy - sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a" + sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5+dev.2" source_gen: dependency: transitive description: @@ -680,22 +680,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.5" win32: dependency: transitive description: name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "5.5.0" xdg_directories: dependency: transitive description: @@ -713,5 +721,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.16.6" diff --git a/cw_haven/pubspec.lock b/cw_haven/pubspec.lock index 8aeb70a972..63c0d345fe 100644 --- a/cw_haven/pubspec.lock +++ b/cw_haven/pubspec.lock @@ -21,18 +21,18 @@ packages: dependency: transitive description: name: args - sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.5.0" asn1lib: dependency: transitive description: name: asn1lib - sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 + sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.2" async: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" build_resolvers: dependency: "direct dev" description: @@ -93,10 +93,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" url: "https://pub.dev" source: hosted - version: "7.2.7" + version: "7.2.10" built_collection: dependency: transitive description: @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: built_value - sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.4.3" + version: "8.9.2" characters: dependency: transitive description: @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" clock: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.10.0" collection: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" cw_core: dependency: "direct main" description: @@ -188,10 +188,10 @@ packages: dependency: transitive description: name: encrypt - sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.3" fake_async: dependency: transitive description: @@ -204,10 +204,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.2" file: dependency: transitive description: @@ -233,10 +233,10 @@ packages: dependency: "direct main" description: name: flutter_mobx - sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" + sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e" url: "https://pub.dev" source: hosted - version: "2.0.6+5" + version: "2.2.1+1" flutter_test: dependency: "direct dev" description: flutter @@ -246,26 +246,26 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: name: graphs - sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" hive: dependency: transitive description: @@ -286,10 +286,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -326,18 +326,18 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: transitive description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -366,10 +366,10 @@ packages: dependency: transitive description: name: logging - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" matcher: dependency: transitive description: @@ -398,26 +398,34 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mobx: dependency: "direct main" description: name: mobx - sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a + sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e" url: "https://pub.dev" source: hosted - version: "2.1.3+1" + version: "2.3.3+2" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181" + sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.3.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -438,26 +446,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -470,10 +478,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -486,26 +494,26 @@ packages: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.9.1" pool: dependency: transitive description: @@ -514,46 +522,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: + provider: dependency: transitive description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "6.1.2" pub_semver: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.3" shelf: dependency: transitive description: name: shelf - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -563,10 +571,10 @@ packages: dependency: transitive description: name: socks5_proxy - sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a" + sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5+dev.2" source_gen: dependency: transitive description: @@ -651,10 +659,10 @@ packages: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" vector_math: dependency: transitive description: @@ -679,38 +687,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.5" win32: dependency: transitive description: name: win32 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "5.5.0" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.7.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.16.6" diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index adb50bd027..623ad119d3 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -21,18 +21,18 @@ packages: dependency: transitive description: name: args - sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.5.0" asn1lib: dependency: transitive description: name: asn1lib - sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 + sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.2" async: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" build_resolvers: dependency: "direct dev" description: @@ -93,10 +93,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" url: "https://pub.dev" source: hosted - version: "7.2.7" + version: "7.2.10" built_collection: dependency: transitive description: @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: built_value - sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.4.3" + version: "8.9.2" characters: dependency: transitive description: @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" clock: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.10.0" collection: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" cw_core: dependency: "direct main" description: @@ -188,10 +188,10 @@ packages: dependency: "direct main" description: name: encrypt - sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.3" fake_async: dependency: transitive description: @@ -204,10 +204,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.2" file: dependency: transitive description: @@ -233,10 +233,10 @@ packages: dependency: "direct main" description: name: flutter_mobx - sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" + sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e" url: "https://pub.dev" source: hosted - version: "2.0.6+5" + version: "2.2.1+1" flutter_test: dependency: "direct dev" description: flutter @@ -246,34 +246,34 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: name: graphs - sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" hashlib: dependency: transitive description: name: hashlib - sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96" + sha256: b9e2528c341c8e060f410bf049b18aea06218e77f857562bd18e8cc2e003e467 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.14.0" hashlib_codecs: dependency: transitive description: @@ -302,10 +302,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -342,18 +342,18 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: transitive description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -382,10 +382,10 @@ packages: dependency: transitive description: name: logging - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" matcher: dependency: transitive description: @@ -414,26 +414,34 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mobx: dependency: "direct main" description: name: mobx - sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a + sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e" url: "https://pub.dev" source: hosted - version: "2.1.3+1" + version: "2.3.3+2" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181" + sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.3.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -454,26 +462,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -486,10 +494,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -502,34 +510,34 @@ packages: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.9.1" polyseed: dependency: "direct main" description: name: polyseed - sha256: "9b48ec535b10863f78f6354ec983b4cc0c88ca69ff48fee469d0fd1954b01d4f" + sha256: a340962242d7917b0f3e6bd02c4acc3f90eae8ff766f1244f793ae7a6414dd68 url: "https://pub.dev" source: hosted - version: "0.0.2" + version: "0.0.4" pool: dependency: transitive description: @@ -538,46 +546,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: + provider: dependency: transitive description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "6.1.2" pub_semver: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.3" shelf: dependency: transitive description: name: shelf - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -587,10 +595,10 @@ packages: dependency: transitive description: name: socks5_proxy - sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a" + sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5+dev.2" source_gen: dependency: transitive description: @@ -675,10 +683,10 @@ packages: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" vector_math: dependency: transitive description: @@ -703,38 +711,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.5" win32: dependency: transitive description: name: win32 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "5.5.0" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.7.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.16.6" diff --git a/cw_shared_external/pubspec.lock b/cw_shared_external/pubspec.lock deleted file mode 100644 index ef01c9f9ab..0000000000 --- a/cw_shared_external/pubspec.lock +++ /dev/null @@ -1,147 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.5.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.10" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.19" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" -sdks: - dart: ">=2.12.0-0.0 <3.0.0" - flutter: ">=1.20.0" diff --git a/lib/di.dart b/lib/di.dart index 15a72e80c7..610d148317 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -26,9 +26,9 @@ import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; -import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cw_core/receive_page_option.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/entities/template.dart'; @@ -123,54 +123,15 @@ import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.d import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart'; import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart'; -import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart'; -import 'package:cake_wallet/themes/theme_list.dart'; -import 'package:cake_wallet/utils/device_info.dart'; -import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart'; -import 'package:cake_wallet/utils/payment_request.dart'; -import 'package:cake_wallet/utils/responsive_layout_util.dart'; -import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart'; -import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart'; -import 'package:cake_wallet/view_model/anonpay_details_view_model.dart'; -import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart'; -import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart'; -import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; -import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_view_model.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_custom_redeem_view_model.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; -import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart'; -import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart'; -import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart'; -import 'package:cake_wallet/view_model/seed_type_view_model.dart'; -import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; -import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; -import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; -import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; -import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart'; -import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart'; -import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; -import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart'; -import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; -import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart'; -import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; -import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart'; -import 'package:cw_core/nano_account.dart'; -import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_core/wallet_service.dart'; -import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/node.dart'; import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; +import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart'; +import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; @@ -185,6 +146,13 @@ import 'package:cake_wallet/store/templates/exchange_template_store.dart'; import 'package:cake_wallet/store/templates/send_template_store.dart'; import 'package:cake_wallet/store/wallet_list_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; +import 'package:cake_wallet/themes/theme_list.dart'; +import 'package:cake_wallet/utils/device_info.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart'; +import 'package:cake_wallet/view_model/anonpay_details_view_model.dart'; import 'package:cake_wallet/view_model/auth_view_model.dart'; import 'package:cake_wallet/view_model/backup_view_model.dart'; import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart'; @@ -193,22 +161,45 @@ import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_custom_redeem_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart'; +import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart'; +import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart'; +import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart'; +import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart'; import 'package:cake_wallet/view_model/order_details_view_model.dart'; import 'package:cake_wallet/view_model/rescan_view_model.dart'; +import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; import 'package:cake_wallet/view_model/send/send_view_model.dart'; +import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; +import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; +import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; +import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart'; +import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart'; import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart'; import 'package:cake_wallet/view_model/support_view_model.dart'; import 'package:cake_wallet/view_model/trade_details_view_model.dart'; @@ -217,15 +208,24 @@ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart'; import 'package:cake_wallet/view_model/wallet_keys_view_model.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_new_vm.dart'; +import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cake_wallet/view_model/wallet_seed_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/nano_account.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -689,10 +689,6 @@ Future setup({ getIt.registerFactory(() { final wallet = getIt.get().wallet!; - // if ((wallet.type == WalletType.bitcoin && - // wallet.walletAddresses.addressPageType == btc.AddressType.p2sp) || - // wallet.type == WalletType.monero || - // wallet.type == WalletType.haven) { if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) { return MoneroAccountListViewModel(wallet); } diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index 0ed8795a7c..f6c0450e69 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -1,11 +1,13 @@ import 'dart:math'; import 'package:auto_size_text/auto_size_text.dart'; +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart'; import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; @@ -22,6 +24,7 @@ import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:url_launcher/url_launcher.dart'; class BalancePage extends StatelessWidget { BalancePage({ @@ -243,12 +246,101 @@ class CryptoBalanceWidget extends StatelessWidget { }, ); }, - ) + ), + if (dashboardViewModel.hasSilentPayments) ...[ + SizedBox(height: 10), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: DashBoardRoundedCardWidget( + title: S.of(context).silent_payments, + subTitle: S.of(context).enable_silent_payments_scanning, + hint: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => launchUrl( + // TODO: Update URL + Uri.https("guides.cakewallet.com"), + mode: LaunchMode.externalApplication, + ), + child: Row( + children: [ + Text( + S.of(context).what_is_silent_payments, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .labelTextColor, + height: 1, + ), + softWrap: true, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Icon(Icons.help_outline, + size: 16, + color: Theme.of(context) + .extension()! + .labelTextColor), + ) + ], + ), + ), + Observer( + builder: (_) => StandardSwitch( + value: dashboardViewModel.silentPaymentsScanningActive, + onTaped: () => _toggleSilentPaymentsScanning(context), + ), + ) + ], + ), + ], + ), + onTap: () => _toggleSilentPaymentsScanning(context), + icon: Icon( + Icons.lock, + color: Theme.of(context).extension()!.pageTitleTextColor, + size: 50, + ), + ), + ), + ] ], ), ), ); } + + Future _toggleSilentPaymentsScanning(BuildContext context) async { + final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive; + final newValue = !isSilentPaymentsScanningActive; + + final needsToSwitch = bitcoin!.getNodeIsCakeElectrs(dashboardViewModel.wallet) == false; + + if (needsToSwitch) { + return showPopUp( + context: context, + builder: (BuildContext context) => AlertWithTwoActions( + alertTitle: S.of(context).change_current_node_title, + alertContent: S.of(context).confirm_silent_payments_switch_node, + rightButtonText: S.of(context).ok, + leftButtonText: S.of(context).cancel, + actionRightButton: () { + dashboardViewModel.setSilentPaymentsScanning(newValue); + Navigator.of(context).pop(); + }, + actionLeftButton: () => Navigator.of(context).pop(), + )); + } + + return dashboardViewModel.setSilentPaymentsScanning(newValue); + } } class BalanceRowWidget extends StatelessWidget { diff --git a/lib/src/screens/dashboard/pages/cake_features_page.dart b/lib/src/screens/dashboard/pages/cake_features_page.dart index 89c0435e15..c85ef08f82 100644 --- a/lib/src/screens/dashboard/pages/cake_features_page.dart +++ b/lib/src/screens/dashboard/pages/cake_features_page.dart @@ -78,7 +78,7 @@ class CakeFeaturesPage extends StatelessWidget { fit: BoxFit.cover, ), ), - const SizedBox(height: 20), + SizedBox(height: 10), DashBoardRoundedCardWidget( title: "NanoGPT", subTitle: S.of(context).nanogpt_subtitle, @@ -87,68 +87,6 @@ class CakeFeaturesPage extends StatelessWidget { mode: LaunchMode.externalApplication, ), ), - if (dashboardViewModel.hasSilentPayments) ...[ - SizedBox(height: 10), - DashBoardRoundedCardWidget( - title: S.of(context).silent_payments, - subTitle: S.of(context).enable_silent_payments_scanning, - hint: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => launchUrl( - // TODO: Update URL - Uri.https("guides.cakewallet.com"), - mode: LaunchMode.externalApplication, - ), - child: Row( - children: [ - Text( - S.of(context).what_is_silent_payments, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .labelTextColor, - height: 1, - ), - softWrap: true, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Icon(Icons.help_outline, - size: 16, - color: Theme.of(context) - .extension()! - .labelTextColor), - ) - ], - ), - ), - Observer( - builder: (_) => StandardSwitch( - value: dashboardViewModel.silentPaymentsScanningActive, - onTaped: () => _toggleSilentPaymentsScanning(context), - ), - ) - ], - ), - ], - ), - onTap: () => _toggleSilentPaymentsScanning(context), - icon: Icon( - Icons.lock, - color: - Theme.of(context).extension()!.pageTitleTextColor, - size: 50, - ), - ), - ] ], ), ), @@ -185,29 +123,4 @@ class CakeFeaturesPage extends StatelessWidget { }); } } - - Future _toggleSilentPaymentsScanning(BuildContext context) async { - final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive; - final newValue = !isSilentPaymentsScanningActive; - - final needsToSwitch = bitcoin!.getNodeIsCakeElectrs(dashboardViewModel.wallet) == false; - - if (needsToSwitch) { - return showPopUp( - context: context, - builder: (BuildContext context) => AlertWithTwoActions( - alertTitle: S.of(context).change_current_node_title, - alertContent: S.of(context).confirm_silent_payments_switch_node, - rightButtonText: S.of(context).ok, - leftButtonText: S.of(context).cancel, - actionRightButton: () { - dashboardViewModel.setSilentPaymentsScanning(newValue); - Navigator.of(context).pop(); - }, - actionLeftButton: () => Navigator.of(context).pop(), - )); - } - - return dashboardViewModel.setSilentPaymentsScanning(newValue); - } } diff --git a/lib/src/screens/dashboard/pages/market_place_page.dart b/lib/src/screens/dashboard/pages/market_place_page.dart deleted file mode 100644 index d280488446..0000000000 --- a/lib/src/screens/dashboard/pages/market_place_page.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/material.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; - -class MarketPlacePage extends StatelessWidget { - MarketPlacePage({ - required this.dashboardViewModel, - required this.marketPlaceViewModel, - }); - - final DashboardViewModel dashboardViewModel; - final MarketPlaceViewModel marketPlaceViewModel; - final _scrollController = ScrollController(); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: RawScrollbar( - thumbColor: Colors.white.withOpacity(0.15), - radius: Radius.circular(20), - thumbVisibility: true, - thickness: 2, - controller: _scrollController, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 50), - Text( - S.of(context).market_place, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.pageTitleTextColor, - ), - ), - Expanded( - child: ListView( - controller: _scrollController, - children: [ - // SizedBox(height: 20), - // DashBoardRoundedCardWidget( - // onTap: () => launchUrl( - // Uri.parse("https://cakelabs.com/news/cake-pay-mobile-to-shut-down/"), - // mode: LaunchMode.externalApplication, - // ), - // title: S.of(context).cake_pay_title, - // subTitle: S.of(context).cake_pay_subtitle, - // ), - SizedBox(height: 20), - DashBoardRoundedCardWidget( - title: S.of(context).cake_pay_web_cards_title, - subTitle: S.of(context).cake_pay_web_cards_subtitle, - onTap: () => _launchMarketPlaceUrl("buy.cakepay.com"), - ), - const SizedBox(height: 20), - DashBoardRoundedCardWidget( - title: "NanoGPT", - subTitle: S.of(context).nanogpt_subtitle, - onTap: () => _launchMarketPlaceUrl("cake.nano-gpt.com"), - ), - ], - ), - ), - ], - ), - ), - ), - ); - } - - void _launchMarketPlaceUrl(String url) async { - try { - launchUrl( - Uri.https(url), - mode: LaunchMode.externalApplication, - ); - } catch (e) { - print(e); - } - } - - // TODO: Remove ionia flow/files if we will discard it - void _navigatorToGiftCardsPage(BuildContext context) { - final walletType = dashboardViewModel.type; - - switch (walletType) { - case WalletType.haven: - showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).error, - alertContent: S.of(context).gift_cards_unavailable, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); - break; - default: - marketPlaceViewModel.isIoniaUserAuthenticated().then((value) { - if (value) { - Navigator.pushNamed(context, Routes.ioniaManageCardsPage); - return; - } - Navigator.of(context).pushNamed(Routes.ioniaWelcomePage); - }); - } - } -} diff --git a/lib/view_model/wallet_address_list/wallet_address_list_item.dart b/lib/view_model/wallet_address_list/wallet_address_list_item.dart index ebcf541073..6a6e341137 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_item.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_item.dart @@ -9,6 +9,7 @@ class WalletAddressListItem extends ListItem { this.txCount, this.balance, this.isChange = false, + // Address that is only ever used once, shouldn't be used to receive funds, copy and paste, share etc this.isOneTimeReceiveAddress = false, }) : super(); @@ -24,4 +25,3 @@ class WalletAddressListItem extends ListItem { @override String toString() => name ?? address; } - From f090d09a651ad0a2a5ca8a958a4edf6095e6a980 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Wed, 8 May 2024 10:32:45 -0300 Subject: [PATCH 075/242] refactor: unchanged files [skip ci] --- cw_bitcoin/lib/bitcoin_wallet.dart | 33 +++++++++++++++--------------- lib/di.dart | 2 +- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 3091cacf54..02a9125e11 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -41,23 +41,22 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { List? initialSilentAddresses, int initialSilentAddressIndex = 0, }) : super( - mnemonic: mnemonic, - passphrase: passphrase, - xpub: xpub, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - networkType: networkParam == null - ? bitcoin.bitcoin - : networkParam == BitcoinNetwork.mainnet - ? bitcoin.bitcoin - : bitcoin.testnet, - initialAddresses: initialAddresses, - initialBalance: initialBalance, - seedBytes: seedBytes, - currency: - networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc, - ) { + mnemonic: mnemonic, + passphrase: passphrase, + xpub: xpub, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + networkType: networkParam == null + ? bitcoin.bitcoin + : networkParam == BitcoinNetwork.mainnet + ? bitcoin.bitcoin + : bitcoin.testnet, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: + networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc) { // in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here) // the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here) // String derivationPath = walletInfo.derivationInfo!.derivationPath!; diff --git a/lib/di.dart b/lib/di.dart index 610d148317..338c599b08 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -26,9 +26,9 @@ import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; -import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cw_core/receive_page_option.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/entities/template.dart'; From a745c59af4b7383d174def616918ebcf97dfa839 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Wed, 8 May 2024 17:36:01 +0300 Subject: [PATCH 076/242] Fix conflicts with main --- scripts/android/pubspec_gen.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index bc7985506e..e691e4e763 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --polygon --nano --bitcoinCash --solana --tron" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron" #TODO: fix and add back --haven ;; $HAVEN) CONFIG_ARGS="--haven" From 0e902b6f6318713859149e5358a1e82d71148c0a Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Wed, 8 May 2024 17:36:39 +0200 Subject: [PATCH 077/242] fix for multiple wallets --- cw_monero/lib/api/wallet_manager.dart | 79 ++++++++++++++++----------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index a3e9ff33f5..3d5d81c6d5 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -1,4 +1,3 @@ - import 'dart:ffi'; import 'package:cw_monero/api/account_list.dart'; @@ -7,12 +6,16 @@ import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart'; import 'package:cw_monero/api/wallet.dart'; +import 'package:flutter/foundation.dart'; import 'package:monero/monero.dart' as monero; monero.WalletManager? _wmPtr; final monero.WalletManager wmPtr = Pointer.fromAddress((() { try { - monero.printStarts = true; + // Problems with the wallet? Crashes? Lags? this will print all calls to xmr + // codebase, so it will be easier to debug what happens. At least easier + // than plugging gdb in. Especially on windows/android. + monero.printStarts = kDebugMode; _wmPtr ??= monero.WalletManagerFactory_getWalletManager(); print("ptr: $_wmPtr"); } catch (e) { @@ -23,16 +26,18 @@ final monero.WalletManager wmPtr = Pointer.fromAddress((() { void createWalletSync( {required String path, - required String password, - required String language, - int nettype = 0}) { - wptr = monero.WalletManager_createWallet(wmPtr, path: path, password: password, language: language, networkType: 0); + required String password, + required String language, + int nettype = 0}) { + wptr = monero.WalletManager_createWallet(wmPtr, + path: path, password: password, language: language, networkType: 0); final status = monero.Wallet_status(wptr!); if (status != 0) { throw WalletCreationException(message: monero.Wallet_errorString(wptr!)); } monero.Wallet_store(wptr!, path: path); + openedWalletsByPath[path] = wptr!; // is the line below needed? // setupNodeSync(address: "node.moneroworld.com:18089"); @@ -48,7 +53,6 @@ void restoreWalletFromSeedSync( required String seed, int nettype = 0, int restoreHeight = 0}) { - wptr = monero.WalletManager_recoveryWallet( wmPtr, path: path, @@ -65,6 +69,8 @@ void restoreWalletFromSeedSync( final error = monero.Wallet_errorString(wptr!); throw WalletRestoreFromSeedException(message: error); } + + openedWalletsByPath[path] = wptr!; } void restoreWalletFromKeysSync( @@ -76,7 +82,6 @@ void restoreWalletFromKeysSync( required String spendKey, int nettype = 0, int restoreHeight = 0}) { - wptr = monero.WalletManager_createWalletFromKeys( wmPtr, path: path, @@ -90,19 +95,21 @@ void restoreWalletFromKeysSync( final status = monero.Wallet_status(wptr!); if (status != 0) { - throw WalletRestoreFromKeysException(message: monero.Wallet_errorString(wptr!)); + throw WalletRestoreFromKeysException( + message: monero.Wallet_errorString(wptr!)); } + + openedWalletsByPath[path] = wptr!; } void restoreWalletFromSpendKeySync( {required String path, - required String password, - required String seed, - required String language, - required String spendKey, - int nettype = 0, - int restoreHeight = 0}) { - + required String password, + required String seed, + required String language, + required String spendKey, + int nettype = 0, + int restoreHeight = 0}) { // wptr = monero.WalletManager_createWalletFromKeys( // wmPtr, // path: path, @@ -135,6 +142,8 @@ void restoreWalletFromSpendKeySync( monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); storeSync(); + + openedWalletsByPath[path] = wptr!; } String _lastOpenedWallet = ""; @@ -170,17 +179,22 @@ String _lastOpenedWallet = ""; // } // } +Map openedWalletsByPath = {}; -void loadWallet({ - required String path, - required String password, - int nettype = 0}) { +void loadWallet( + {required String path, required String password, int nettype = 0}) { + if (openedWalletsByPath[path] != null) { + wptr = openedWalletsByPath[path]!; + return; + } try { if (wptr == null || path != _lastOpenedWallet) { if (wptr != null) { monero.Wallet_store(wptr!); } - wptr = monero.WalletManager_openWallet(wmPtr, path: path, password: password); + wptr = monero.WalletManager_openWallet(wmPtr, + path: path, password: password); + openedWalletsByPath[path] = wptr!; _lastOpenedWallet = path; } } catch (e) { @@ -248,12 +262,15 @@ void _restoreFromSpendKey(Map args) { spendKey: spendKey); } -Future _openWallet(Map args) async => - loadWallet(path: args['path'] as String, password: args['password'] as String); +Future _openWallet(Map args) async => loadWallet( + path: args['path'] as String, password: args['password'] as String); Future _isWalletExist(String path) async => isWalletExistSync(path: path); -void openWallet({required String path, required String password, int nettype = 0}) async => +void openWallet( + {required String path, + required String password, + int nettype = 0}) async => loadWallet(path: path, password: password, nettype: nettype); Future openWalletAsync(Map args) async => @@ -306,13 +323,13 @@ Future restoreFromKeys( }); Future restoreFromSpendKey( - {required String path, - required String password, - required String seed, - required String language, - required String spendKey, - int nettype = 0, - int restoreHeight = 0}) async => + {required String path, + required String password, + required String seed, + required String language, + required String spendKey, + int nettype = 0, + int restoreHeight = 0}) async => _restoreFromSpendKey({ 'path': path, 'password': password, From bf14a14db95cca5317b982c93f326ef162f04a17 Mon Sep 17 00:00:00 2001 From: m Date: Wed, 8 May 2024 19:30:43 +0100 Subject: [PATCH 078/242] Add tron to windows application configuration. --- cakewallet.bat | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cakewallet.bat b/cakewallet.bat index e429d109b1..2639b39b8b 100644 --- a/cakewallet.bat +++ b/cakewallet.bat @@ -1,5 +1,5 @@ @echo off -set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana +set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron set cw_root=%cd% set cw_archive_name=Cake Wallet.zip set cw_archive_path=%cw_root%\%cw_archive_name% @@ -20,7 +20,7 @@ IF NOT EXIST "%secrets_file_path%" ( ) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===) echo === Generating mobx models === -for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana .) do ( +for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron .) do ( cd %%i call flutter pub get > nul call dart run build_runner build --delete-conflicting-outputs > nul From 1fe447000f07412e59256746c2b85529e6142130 Mon Sep 17 00:00:00 2001 From: m Date: Wed, 8 May 2024 19:34:24 +0100 Subject: [PATCH 079/242] Add macOS option for description message in configure_cake_wallet.sh --- configure_cake_wallet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure_cake_wallet.sh b/configure_cake_wallet.sh index 2eb6d8f25a..16c7c16539 100755 --- a/configure_cake_wallet.sh +++ b/configure_cake_wallet.sh @@ -8,7 +8,7 @@ PLATFORMS=($IOS $ANDROID $MACOS) PLATFORM=$1 if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then - echo "specify platform: ./configure_cake_wallet.sh ios|android" + echo "specify platform: ./configure_cake_wallet.sh ios|android|macos" exit 1 fi From 75f3cb78f841c3a72933b3d2ac66f7307c2924d9 Mon Sep 17 00:00:00 2001 From: m Date: Wed, 8 May 2024 21:40:45 +0100 Subject: [PATCH 080/242] Include missed monero dll for windows. --- windows/CMakeLists.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 1af4450847..fcb54ad0a6 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -84,6 +84,21 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x86_64-w64-mingw32_libwallet2_api_c.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "monero_libwallet2_api_c.dll" COMPONENT Runtime) +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x86_64-w64-mingw32_libgcc_s_seh-1.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "libgcc_s_seh-1.dll" + COMPONENT Runtime) + +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x86_64-w64-mingw32_libpolyseed.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "libpolyseed.dll" + COMPONENT Runtime) + +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x86_64-w64-mingw32_libssp-0.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "libssp-0.dll" + COMPONENT Runtime) + +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x86_64-w64-mingw32_libstdc++-6.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "libstdc++-6.dll" + COMPONENT Runtime) + +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x86_64-w64-mingw32_libwinpthread-1.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "libwinpthread-1.dll" + COMPONENT Runtime) + if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" From fa53913153c965effbbc502c2460d624f9eb5b9c Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Thu, 9 May 2024 01:01:03 +0300 Subject: [PATCH 081/242] fix conflicts with main --- lib/main.dart | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 423b233980..8cb845f88f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -39,34 +39,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:cw_core/root_dir.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:cake_wallet/themes/theme_base.dart'; -import 'package:cake_wallet/router.dart' as Router; -import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/reactions/bootstrap.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/store/authentication_store.dart'; -import 'package:cake_wallet/entities/transaction_description.dart'; -import 'package:cake_wallet/entities/get_encryption_key.dart'; -import 'package:cake_wallet/entities/contact.dart'; -import 'package:cw_core/node.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cake_wallet/entities/default_settings_migration.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/entities/template.dart'; -import 'package:cake_wallet/exchange/trade.dart'; -import 'package:cake_wallet/exchange/exchange_template.dart'; -import 'package:cake_wallet/src/screens/root/root.dart'; import 'package:uni_links/uni_links.dart'; -import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cake_wallet/monero/monero.dart'; -import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/window_size.dart'; final navigatorKey = GlobalKey(); From 112c092bde01de5e9c978a8779e38afffd5ff29a Mon Sep 17 00:00:00 2001 From: m Date: Thu, 9 May 2024 21:04:21 +0100 Subject: [PATCH 082/242] Disable haven configuration for iOS as default. Add ability to configure cakewallet for iOS with for configuration script. Remove cw_shared configuration for cw_monero. --- scripts/ios/app_config.sh | 5 ++++- tool/configure.dart | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh index ab7fbd4228..5482868bf2 100755 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -28,7 +28,10 @@ case $APP_IOS_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --polygon --nano --bitcoinCash --solana --tron" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron" + if [ "$CW_WITH_HAVEN" = true ];then + CONFIG_ARGS="$CONFIG_ARGS --haven" + fi ;; $HAVEN) diff --git a/tool/configure.dart b/tool/configure.dart index 22013aa574..b549805c1a 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1223,7 +1223,7 @@ Future generatePubspec( var output = cwCore; if (hasMonero) { - output += '\n$cwMonero\n$cwSharedExternal'; + output += '\n$cwMonero'; } if (hasBitcoin) { @@ -1258,10 +1258,8 @@ Future generatePubspec( output += '\n$cwTron'; } - if (hasHaven && !hasMonero) { + if (hasHaven) { output += '\n$cwSharedExternal\n$cwHaven'; - } else if (hasHaven) { - output += '\n$cwHaven'; } if (hasFlutterSecureStorage) { From 8e5d997562c66075c96d3636a508e9f02d43ac76 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Thu, 9 May 2024 17:06:39 -0300 Subject: [PATCH 083/242] fix: scan fixes, add date, allow sending while scanning --- cw_bitcoin/lib/electrum_wallet.dart | 312 ++++++++---------- cw_bitcoin/pubspec.lock | 2 +- cw_core/lib/get_height_by_date.dart | 11 +- cw_core/lib/wallet_info.dart | 35 +- lib/entities/default_settings_migration.dart | 1 - .../on_wallet_sync_status_change.dart | 35 +- .../screens/dashboard/pages/balance_page.dart | 1 + lib/src/widgets/dashboard_card_widget.dart | 4 +- lib/view_model/send/send_view_model.dart | 5 +- 9 files changed, 191 insertions(+), 215 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 2d17222131..8f924eacc9 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -37,6 +37,7 @@ import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:cw_core/get_height_by_date.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:http/http.dart' as http; @@ -359,7 +360,7 @@ abstract class ElectrumWalletBase } syncStatus = message.syncStatus; - walletInfo.restoreHeight = message.height; + await walletInfo.updateRestoreHeight(message.height); } } } @@ -381,6 +382,8 @@ abstract class ElectrumWalletBase await updateBalance(); await updateFeeRates(); + + syncStatus = SyncedSyncStatus(); } catch (e, stacktrace) { print(stacktrace); print(e.toString()); @@ -1141,7 +1144,8 @@ abstract class ElectrumWalletBase coin.isFrozen = coinInfo.isFrozen; coin.isSending = coinInfo.isSending; coin.note = coinInfo.note; - coin.bitcoinAddressRecord.balance += coinInfo.value; + if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord) + coin.bitcoinAddressRecord.balance += coinInfo.value; } else { _addCoinInfo(coin); } @@ -1172,7 +1176,8 @@ abstract class ElectrumWalletBase coin.isFrozen = coinInfo.isFrozen; coin.isSending = coinInfo.isSending; coin.note = coinInfo.note; - coin.bitcoinAddressRecord.balance += coinInfo.value; + if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord) + coin.bitcoinAddressRecord.balance += coinInfo.value; } else { _addCoinInfo(coin); } @@ -1588,7 +1593,6 @@ abstract class ElectrumWalletBase Future updateTransactions() async { try { if (_isTransactionUpdating) { - _isTransactionUpdating = false; return; } @@ -1734,7 +1738,7 @@ abstract class ElectrumWalletBase _currentChainTip = height; if (_currentChainTip != null && _currentChainTip! > 0 && walletInfo.restoreHeight == 0) { - walletInfo.restoreHeight = _currentChainTip!; + await walletInfo.updateRestoreHeight(_currentChainTip!); } }); } @@ -1818,202 +1822,156 @@ class SyncResponse { } Future startRefresh(ScanData scanData) async { - Future getElectrumConnection() async { - final electrumClient = scanData.electrumClient; - if (!electrumClient.isConnected) { - final node = scanData.node; - await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); - } - return electrumClient; - } - - var lastKnownBlockHeight = 0; - var initialSyncHeight = 0; - - var syncHeight = scanData.height; - var currentChainTip = scanData.chainTip; - - if (syncHeight <= 0) { - syncHeight = currentChainTip; - } - - if (initialSyncHeight <= 0) { - initialSyncHeight = syncHeight; - } - - if (lastKnownBlockHeight == syncHeight) { - scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); - return; - } + int syncHeight = scanData.height; + int initialSyncHeight = syncHeight; BehaviorSubject? tweaksSubscription = null; - lastKnownBlockHeight = syncHeight; - - SyncingSyncStatus syncingStatus; - if (scanData.isSingleScan) { - syncingStatus = SyncingSyncStatus(1, 0); - } else { - syncingStatus = - SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight); - } + final syncingStatus = scanData.isSingleScan + ? SyncingSyncStatus(1, 0) + : SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight); + // Initial status UI update, send how many blocks left to scan scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); - if (syncingStatus.blocksLeft <= 0 || (scanData.isSingleScan && scanData.height != syncHeight)) { - scanData.sendPort.send(SyncResponse(scanData.chainTip, SyncedSyncStatus())); - return; - } + final electrumClient = scanData.electrumClient; + await electrumClient.connectToUri(scanData.node.uri, useSSL: scanData.node.useSSL); + + if (tweaksSubscription == null) { + final count = scanData.isSingleScan ? 1 : TWEAKS_COUNT; + final receiver = Receiver( + scanData.silentAddress.b_scan.toHex(), + scanData.silentAddress.B_spend.toHex(), + scanData.network == BitcoinNetwork.testnet, + scanData.labelIndexes, + scanData.labelIndexes.length, + ); + + tweaksSubscription = await electrumClient.tweaksSubscribe(height: syncHeight, count: count); + tweaksSubscription?.listen((t) async { + final tweaks = t as Map; - try { - final electrumClient = await getElectrumConnection(); + if (tweaks["message"] != null) { + // re-subscribe to continue receiving messages + electrumClient.tweaksSubscribe(height: syncHeight, count: count); + return; + } - if (tweaksSubscription == null) { - final count = scanData.isSingleScan ? 1 : TWEAKS_COUNT; + final blockHeight = tweaks.keys.first; + final tweakHeight = int.parse(blockHeight); try { - tweaksSubscription = await electrumClient.tweaksSubscribe(height: syncHeight, count: count); + final blockTweaks = tweaks[blockHeight] as Map; - tweaksSubscription?.listen((t) async { - final tweaks = t as Map; + for (var j = 0; j < blockTweaks.keys.length; j++) { + final txid = blockTweaks.keys.elementAt(j); + final details = blockTweaks[txid] as Map; + final outputPubkeys = (details["output_pubkeys"] as Map); + final tweak = details["tweak"].toString(); - if (tweaks["message"] != null && !scanData.isSingleScan) { - // re-subscribe to continue receiving messages - electrumClient.tweaksSubscribe(height: syncHeight, count: count); - return; - } + try { + // scanOutputs called from rust here + final addToWallet = scanOutputs( + outputPubkeys.values.toList(), + tweak, + receiver, + ); + + if (addToWallet.isEmpty) { + // no results tx, continue to next tx + continue; + } - final blockHeight = tweaks.keys.first; - final tweakHeight = int.parse(blockHeight); + // placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s) + final txInfo = ElectrumTransactionInfo( + WalletType.bitcoin, + id: txid, + height: tweakHeight, + amount: 0, + fee: 0, + direction: TransactionDirection.incoming, + isPending: false, + date: scanData.network == BitcoinNetwork.mainnet + ? getDateByBitcoinHeight(tweakHeight) + : DateTime.now(), + confirmations: scanData.chainTip - tweakHeight + 1, + unspents: [], + ); + + addToWallet.forEach((label, value) { + (value as Map).forEach((output, tweak) { + final t_k = tweak.toString(); + + final receivingOutputAddress = ECPublic.fromHex(output) + .toTaprootAddress(tweak: false) + .toAddress(scanData.network); + + int? amount; + int? pos; + outputPubkeys.entries.firstWhere((k) { + final isMatchingOutput = k.value[0] == output; + if (isMatchingOutput) { + amount = int.parse(k.value[1].toString()); + pos = int.parse(k.key.toString()); + return true; + } + return false; + }); - try { - final blockTweaks = tweaks[blockHeight] as Map; - - for (var j = 0; j < blockTweaks.keys.length; j++) { - final txid = blockTweaks.keys.elementAt(j); - final details = blockTweaks[txid] as Map; - final outputPubkeys = (details["output_pubkeys"] as Map); - final tweak = details["tweak"].toString(); - - try { - // scanOutputs called from rust here - final addToWallet = scanOutputs( - outputPubkeys.values.map((o) => o[0].toString()).toList(), - tweak, - Receiver( - scanData.silentAddress.b_scan.toHex(), - scanData.silentAddress.B_spend.toHex(), - scanData.network == BitcoinNetwork.testnet, - scanData.labelIndexes, - scanData.labelIndexes.length, - ), + final receivedAddressRecord = BitcoinSilentPaymentAddressRecord( + receivingOutputAddress, + index: 0, + isHidden: false, + isUsed: true, + network: scanData.network, + silentPaymentTweak: t_k, + type: SegwitAddresType.p2tr, + txCount: 1, + balance: amount!, ); - if (addToWallet.isEmpty) { - // no results tx, continue to next tx - continue; - } - - // placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s) - final txInfo = ElectrumTransactionInfo( - WalletType.bitcoin, - id: txid, - height: tweakHeight, - amount: 0, - fee: 0, - direction: TransactionDirection.incoming, - isPending: false, - date: DateTime.now(), - confirmations: scanData.chainTip - tweakHeight + 1, - unspents: [], + final unspent = BitcoinSilentPaymentsUnspent( + receivedAddressRecord, + txid, + amount!, + pos!, + silentPaymentTweak: t_k, + silentPaymentLabel: label == "None" ? null : label, ); - addToWallet.forEach((label, value) { - (value as Map).forEach((output, tweak) { - final t_k = tweak.toString(); - - final receivingOutputAddress = ECPublic.fromHex(output) - .toTaprootAddress(tweak: false) - .toAddress(scanData.network); - - final receivedAddressRecord = BitcoinSilentPaymentAddressRecord( - receivingOutputAddress, - index: 0, - isHidden: false, - isUsed: true, - network: scanData.network, - silentPaymentTweak: t_k, - type: SegwitAddresType.p2tr, - txCount: 1, - ); - - int? amount; - int? pos; - outputPubkeys.entries.firstWhere((k) { - final isMatchingOutput = k.value[0] == output; - if (isMatchingOutput) { - amount = int.parse(k.value[1].toString()); - pos = int.parse(k.key.toString()); - return true; - } - return false; - }); - - final unspent = BitcoinSilentPaymentsUnspent( - receivedAddressRecord, - txid, - amount!, - pos!, - silentPaymentTweak: t_k, - silentPaymentLabel: label == "None" ? null : label, - ); - - txInfo.unspents!.add(unspent); - txInfo.amount += unspent.value; - }); - }); + txInfo.unspents!.add(unspent); + txInfo.amount += unspent.value; + }); + }); - scanData.sendPort.send({txInfo.id: txInfo}); - } catch (_) {} - } + scanData.sendPort.send({txInfo.id: txInfo}); } catch (_) {} - - syncHeight = tweakHeight; - scanData.sendPort.send( - SyncResponse( - syncHeight, - SyncingSyncStatus.fromHeightValues( - currentChainTip, - initialSyncHeight, - syncHeight, - ), - ), - ); - - if (int.parse(blockHeight) >= scanData.chainTip || scanData.isSingleScan) { - scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus())); - await tweaksSubscription!.close(); - } - }); - } catch (e) { - if (e is RequestFailedTimeoutException) { - return scanData.sendPort.send( - SyncResponse(syncHeight, TimedOutSyncStatus()), - ); } - } - } + } catch (_) {} - if (tweaksSubscription == null) { - return scanData.sendPort.send( - SyncResponse(syncHeight, UnsupportedSyncStatus()), + syncHeight = tweakHeight; + scanData.sendPort.send( + SyncResponse( + syncHeight, + SyncingSyncStatus.fromHeightValues( + scanData.chainTip, + initialSyncHeight, + syncHeight, + ), + ), ); - } - } catch (e, stacktrace) { - print(stacktrace); - print(e.toString()); - scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus())); + if (tweakHeight >= scanData.chainTip || scanData.isSingleScan) { + scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus())); + await tweaksSubscription!.close(); + } + }); + } + + if (tweaksSubscription == null) { + return scanData.sendPort.send( + SyncResponse(syncHeight, UnsupportedSyncStatus()), + ); } } diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 85c7bd7f7f..fe2d0c2af1 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -794,7 +794,7 @@ packages: description: path: "." ref: master - resolved-ref: a6b14bcc37ec16f56931e48afa8a8f8e6939431d + resolved-ref: "4977a0d31fc8614d27193b07d92c5992d163131e" url: "https://github.com/rafael-xmr/sp_scanner" source: git version: "0.0.1" diff --git a/cw_core/lib/get_height_by_date.dart b/cw_core/lib/get_height_by_date.dart index 11bc370c5b..3d23d24af7 100644 --- a/cw_core/lib/get_height_by_date.dart +++ b/cw_core/lib/get_height_by_date.dart @@ -245,6 +245,7 @@ Future getHavenCurrentHeight() async { // Data taken from https://timechaincalendar.com/ const bitcoinDates = { + "2024-05": 841590, "2024-04": 837182, "2024-03": 832623, "2024-02": 828319, @@ -265,7 +266,9 @@ const bitcoinDates = { int getBitcoinHeightByDate({required DateTime date}) { String dateKey = '${date.year}-${date.month.toString().padLeft(2, '0')}'; - int startBlock = bitcoinDates[dateKey] ?? bitcoinDates.values.last; + final closestKey = bitcoinDates.keys + .firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => bitcoinDates.keys.last); + int startBlock = bitcoinDates[dateKey] ?? bitcoinDates[closestKey]!; DateTime startOfMonth = DateTime(date.year, date.month); int daysDifference = date.difference(startOfMonth).inDays; @@ -275,3 +278,9 @@ int getBitcoinHeightByDate({required DateTime date}) { return startBlock + estimatedBlocksSinceStartOfMonth; } + +DateTime getDateByBitcoinHeight(int height) { + final date = bitcoinDates.entries + .lastWhere((entry) => entry.value >= height, orElse: () => bitcoinDates.entries.last); + return formatMapKey(date.key); +} diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart index 57cdad81b0..ff0c011bbe 100644 --- a/cw_core/lib/wallet_info.dart +++ b/cw_core/lib/wallet_info.dart @@ -66,21 +66,21 @@ class DerivationInfo extends HiveObject { @HiveType(typeId: WalletInfo.typeId) class WalletInfo extends HiveObject { WalletInfo( - this.id, - this.name, - this.type, - this.isRecovery, - this.restoreHeight, - this.timestamp, - this.dirPath, - this.path, - this.address, - this.yatEid, - this.yatLastUsedAddressRaw, - this.showIntroCakePayCard, - this.derivationInfo, - this.hardwareWalletType, - ): _yatLastUsedAddressController = StreamController.broadcast(); + this.id, + this.name, + this.type, + this.isRecovery, + this.restoreHeight, + this.timestamp, + this.dirPath, + this.path, + this.address, + this.yatEid, + this.yatLastUsedAddressRaw, + this.showIntroCakePayCard, + this.derivationInfo, + this.hardwareWalletType, + ) : _yatLastUsedAddressController = StreamController.broadcast(); factory WalletInfo.external({ required String id, @@ -207,4 +207,9 @@ class WalletInfo extends HiveObject { Stream get yatLastUsedAddressStream => _yatLastUsedAddressController.stream; StreamController _yatLastUsedAddressController; + + Future updateRestoreHeight(int height) async { + restoreHeight = height; + await save(); + } } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 44a1fdd468..3c9670f11b 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -36,7 +36,6 @@ const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002'; const nanoDefaultNodeUri = 'rpc.nano.to'; const nanoDefaultPowNodeUri = 'rpc.nano.to'; const solanaDefaultNodeUri = 'rpc.ankr.com'; -const tronDefaultNodeUri = 'api.trongrid.io'; const tronDefaultNodeUri = 'tron-rpc.publicnode.com:443'; const newCakeWalletBitcoinUri = '198.58.111.154:50001'; diff --git a/lib/reactions/on_wallet_sync_status_change.dart b/lib/reactions/on_wallet_sync_status_change.dart index 9a13db5979..a52d4edf57 100644 --- a/lib/reactions/on_wallet_sync_status_change.dart +++ b/lib/reactions/on_wallet_sync_status_change.dart @@ -12,28 +12,27 @@ import 'package:wakelock_plus/wakelock_plus.dart'; ReactionDisposer? _onWalletSyncStatusChangeReaction; void startWalletSyncStatusChangeReaction( - WalletBase, - TransactionInfo> wallet, + WalletBase, TransactionInfo> wallet, FiatConversionStore fiatConversionStore) { _onWalletSyncStatusChangeReaction?.reaction.dispose(); - _onWalletSyncStatusChangeReaction = - reaction((_) => wallet.syncStatus, (SyncStatus status) async { - try { - if (status is ConnectedSyncStatus) { - await wallet.startSync(); + _onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async { + if (!(status is SyncingSyncStatus) || wallet.type != WalletType.bitcoin) + try { + if (status is ConnectedSyncStatus) { + await wallet.startSync(); - if (wallet.type == WalletType.haven) { - await updateHavenRate(fiatConversionStore); + if (wallet.type == WalletType.haven) { + await updateHavenRate(fiatConversionStore); + } } + if (status is SyncingSyncStatus) { + await WakelockPlus.enable(); + } + if (status is SyncedSyncStatus || status is FailedSyncStatus) { + await WakelockPlus.disable(); + } + } catch (e) { + print(e.toString()); } - if (status is SyncingSyncStatus) { - await WakelockPlus.enable(); - } - if (status is SyncedSyncStatus || status is FailedSyncStatus) { - await WakelockPlus.disable(); - } - } catch(e) { - print(e.toString()); - } }); } diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index f6c0450e69..5e96effab6 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -252,6 +252,7 @@ class CryptoBalanceWidget extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), child: DashBoardRoundedCardWidget( + customBorder: 30, title: S.of(context).silent_payments, subTitle: S.of(context).enable_silent_payments_scanning, hint: Column( diff --git a/lib/src/widgets/dashboard_card_widget.dart b/lib/src/widgets/dashboard_card_widget.dart index b0831c7cf9..5a8ca14a49 100644 --- a/lib/src/widgets/dashboard_card_widget.dart +++ b/lib/src/widgets/dashboard_card_widget.dart @@ -13,6 +13,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget { this.svgPicture, this.icon, this.onClose, + this.customBorder, }); final VoidCallback onTap; @@ -22,6 +23,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget { final Widget? hint; final SvgPicture? svgPicture; final Icon? icon; + final double? customBorder; @override Widget build(BuildContext context) { @@ -37,7 +39,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget { width: double.infinity, decoration: BoxDecoration( color: Theme.of(context).extension()!.syncedBackgroundColor, - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(customBorder ?? 20), border: Border.all( color: Theme.of(context).extension()!.cardBorderColor, ), diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 635d43d53e..f9c3dd9228 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -218,7 +218,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title; @computed - bool get isReadyForSend => wallet.syncStatus is SyncedSyncStatus; + bool get isReadyForSend => + wallet.syncStatus is SyncedSyncStatus || + // If silent payments scanning, can still send payments + (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus); @computed List