diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index bd015107a..8df7f3fc7 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: "11.x" + java-version: "17.x" - name: Configure placeholder git details run: | git config --global user.email "CI@cakewallet.com" diff --git a/assets/litecoin_electrum_server_list.yml b/assets/litecoin_electrum_server_list.yml index 991762885..550b900e1 100644 --- a/assets/litecoin_electrum_server_list.yml +++ b/assets/litecoin_electrum_server_list.yml @@ -1,4 +1,19 @@ - uri: ltc-electrum.cakewallet.com:50002 useSSL: true - isDefault: true \ No newline at end of file + isDefault: true +- + uri: litecoin.stackwallet.com:20063 + useSSL: true +- + uri: electrum-ltc.bysh.me:50002 + useSSL: true +- + uri: lightweight.fiatfaucet.com:50002 + useSSL: true +- + uri: electrum.ltc.xurious.com:50002 + useSSL: true +- + uri: backup.electrum-ltc.org:443 + useSSL: true diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index 2a6c07abe..c90d54524 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,3 +1,4 @@ -Monero enhancements -Synchronization improvements +Monero synchronization improvements +Enhance error handling +UI enhancements Bug fixes \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index d17a22c84..34bca2e5e 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,5 +1,6 @@ -Monero and Ethereum enhancements -Synchronization improvements -Exchange flow enhancements -Ledger improvements +Wallets enhancements +Monero synchronization improvements +Improve wallet backups +Enhance error handling +UI enhancements Bug fixes \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart index 345d645d1..de339175d 100644 --- a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart'; @@ -25,7 +25,8 @@ class BitcoinHardwareWalletService { for (final i in indexRange) { final derivationPath = "m/84'/0'/$i'"; final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath); - HDWallet hd = HDWallet.fromBase58(xpub).derive(0); + Bip32Slip10Secp256k1 hd = + Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0)); final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet); diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index d061480ed..7b8250541 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -2,19 +2,19 @@ import 'dart:convert'; import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:convert/convert.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; -import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_bitcoin/psbt_transaction_builder.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart'; @@ -50,11 +50,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - networkType: networkParam == null - ? bitcoin.bitcoin + network: networkParam == null + ? BitcoinNetwork.mainnet : networkParam == BitcoinNetwork.mainnet - ? bitcoin.bitcoin - : bitcoin.testnet, + ? BitcoinNetwork.mainnet + : BitcoinNetwork.testnet, initialAddresses: initialAddresses, initialBalance: initialBalance, seedBytes: seedBytes, @@ -75,10 +75,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialSilentAddresses: initialSilentAddresses, initialSilentAddressIndex: initialSilentAddressIndex, mainHd: hd, - sideHd: accountHD.derive(1), + sideHd: accountHD.childKey(Bip32KeyIndex(1)), network: networkParam ?? network, - masterHd: - seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null, + masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, ); autorun((_) { @@ -143,49 +142,66 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final network = walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : BitcoinNetwork.mainnet; - final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network); - walletInfo.derivationInfo ??= DerivationInfo( - derivationType: snp.derivationType ?? DerivationType.electrum, - derivationPath: snp.derivationPath, - ); + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); + + ElectrumWalletSnapshot? snp = null; + + try { + snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network); + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + keysData = + WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + + walletInfo.derivationInfo ??= DerivationInfo(); // set the default if not present: - walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? electrum_path; - walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum; + walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path; + walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum; Uint8List? seedBytes = null; + final mnemonic = keysData.mnemonic; + final passphrase = keysData.passphrase; - if (snp.mnemonic != null) { + if (mnemonic != null) { switch (walletInfo.derivationInfo!.derivationType) { case DerivationType.electrum: - seedBytes = await mnemonicToSeedBytes(snp.mnemonic!); + seedBytes = await mnemonicToSeedBytes(mnemonic); break; case DerivationType.bip39: default: seedBytes = await bip39.mnemonicToSeed( - snp.mnemonic!, - passphrase: snp.passphrase ?? '', + mnemonic, + passphrase: passphrase ?? '', ); break; } } return BitcoinWallet( - mnemonic: snp.mnemonic, - xpub: snp.xpub, + mnemonic: mnemonic, + xpub: keysData.xPub, password: password, - passphrase: snp.passphrase, + passphrase: passphrase, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp.addresses, - initialSilentAddresses: snp.silentAddresses, - initialSilentAddressIndex: snp.silentAddressIndex, - initialBalance: snp.balance, + initialAddresses: snp?.addresses, + initialSilentAddresses: snp?.silentAddresses, + initialSilentAddressIndex: snp?.silentAddressIndex ?? 0, + initialBalance: snp?.balance, seedBytes: seedBytes, - initialRegularAddressIndex: snp.regularAddressIndex, - initialChangeAddressIndex: snp.changeAddressIndex, - addressPageType: snp.addressPageType, + initialRegularAddressIndex: snp?.regularAddressIndex, + initialChangeAddressIndex: snp?.changeAddressIndex, + addressPageType: snp?.addressPageType, networkParam: network, alwaysScan: alwaysScan, ); @@ -235,7 +251,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt); - return BtcTransaction.fromRaw(hex.encode(rawHex)); + return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex)); } @override @@ -249,8 +265,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final accountPath = walletInfo.derivationInfo?.derivationPath; final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null; - final signature = await _bitcoinLedgerApp! - .signMessage(_ledgerDevice!, message: ascii.encode(message), signDerivationPath: derivationPath); + final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!, + message: ascii.encode(message), signDerivationPath: derivationPath); return base64Encode(signature); } diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index 486e69b11..697719894 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -1,5 +1,5 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart'; +import 'package:blockchain_utils/bip/bip/bip32/bip32.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/wallet_info.dart'; @@ -24,7 +24,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S }) : super(walletInfo); @override - String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) { + String getAddress( + {required int index, required Bip32Slip10Secp256k1 hd, BitcoinAddressType? addressType}) { if (addressType == P2pkhAddressType.p2pkh) return generateP2PKHAddress(hd: hd, index: index, network: network); diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index a9a6d96db..cf93aa29d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -41,8 +41,10 @@ class BitcoinWalletService extends WalletService< unspentCoinsInfo: unspentCoinsInfoSource, network: network, ); + await wallet.save(); await wallet.init(); + return wallet; } diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index b02e1d29a..d58e31e45 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'; +enum ConnectionStatus { connected, disconnected, connecting, failed } + String jsonrpcparams(List params) { final _params = params.map((val) => '"${val.toString()}"').join(','); return '[$_params]'; @@ -41,7 +43,7 @@ class ElectrumClient { bool get isConnected => _isConnected; Socket? socket; - void Function(bool?)? onConnectionStatusChange; + void Function(ConnectionStatus)? onConnectionStatusChange; int _id; final Map _tasks; Map get tasks => _tasks; @@ -60,17 +62,33 @@ class ElectrumClient { } Future connect({required String host, required int port, bool? useSSL}) async { + _setConnectionStatus(ConnectionStatus.connecting); + try { await socket?.close(); } catch (_) {} - if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) { - socket = await Socket.connect(host, port, timeout: connectionTimeout); - } else { - socket = await SecureSocket.connect(host, port, - timeout: connectionTimeout, onBadCertificate: (_) => true); + try { + if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) { + socket = await Socket.connect(host, port, timeout: connectionTimeout); + } else { + socket = await SecureSocket.connect( + host, + port, + timeout: connectionTimeout, + onBadCertificate: (_) => true, + ); + } + } catch (_) { + _setConnectionStatus(ConnectionStatus.failed); + return; } - _setIsConnected(true); + + if (socket == null) { + _setConnectionStatus(ConnectionStatus.failed); + return; + } + _setConnectionStatus(ConnectionStatus.connected); socket!.listen((Uint8List event) { try { @@ -86,13 +104,20 @@ class ElectrumClient { print(e.toString()); } }, onError: (Object error) { - print(error.toString()); + final errorMsg = error.toString(); + print(errorMsg); unterminatedString = ''; - _setIsConnected(false); + + final currentHost = socket?.address.host; + final isErrorForCurrentHost = errorMsg.contains(" ${currentHost} "); + + if (currentHost != null && isErrorForCurrentHost) + _setConnectionStatus(ConnectionStatus.failed); }, onDone: () { unterminatedString = ''; - _setIsConnected(null); + if (host == socket?.address.host) _setConnectionStatus(ConnectionStatus.disconnected); }); + keepAlive(); } @@ -144,9 +169,9 @@ class ElectrumClient { Future ping() async { try { await callWithTimeout(method: 'server.ping'); - _setIsConnected(true); + _setConnectionStatus(ConnectionStatus.connected); } on RequestFailedTimeoutException catch (_) { - _setIsConnected(null); + _setConnectionStatus(ConnectionStatus.disconnected); } } @@ -236,9 +261,24 @@ class ElectrumClient { return []; }); - Future> getTransactionRaw({required String hash}) async => - callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000) - .then((dynamic result) { + Future getTransaction({required String hash, required bool verbose}) async { + try { + final result = await callWithTimeout( + method: 'blockchain.transaction.get', params: [hash, verbose], timeout: 10000); + if (result is Map) { + return result; + } + } on RequestFailedTimeoutException catch (_) { + return {}; + } catch (e) { + print("getTransaction: ${e.toString()}"); + return {}; + } + return {}; + } + + Future> getTransactionVerbose({required String hash}) => + getTransaction(hash: hash, verbose: true).then((dynamic result) { if (result is Map) { return result; } @@ -246,9 +286,8 @@ class ElectrumClient { return {}; }); - Future getTransactionHex({required String hash}) async => - callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000) - .then((dynamic result) { + Future getTransactionHex({required String hash}) => + getTransaction(hash: hash, verbose: false).then((dynamic result) { if (result is String) { return result; } @@ -336,7 +375,7 @@ class ElectrumClient { try { final topDoubleString = await estimatefee(p: 1); final middleDoubleString = await estimatefee(p: 5); - final bottomDoubleString = await estimatefee(p: 100); + final bottomDoubleString = await estimatefee(p: 10); final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round(); final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round(); final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round(); @@ -353,20 +392,18 @@ class ElectrumClient { // "height": 520481, // "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4" // } - BehaviorSubject>? tipListener; - int? currentTip; + Future getCurrentBlockChainTip() async { try { - final method = 'blockchain.headers.subscribe'; - final cb = (result) => currentTip = result['height'] as int; - if (tipListener == null) { - tipListener = subscribe(id: method, method: method); - tipListener?.listen(cb); - callWithTimeout(method: method).then(cb); + final result = await callWithTimeout(method: 'blockchain.headers.subscribe'); + if (result is Map) { + return result["height"] as int; } - return currentTip; - } catch (_) { - // our websocket connection was likely terminated ungracefully :/ + return null; + } on RequestFailedTimeoutException catch (_) { + return null; + } catch (e) { + print("getCurrentBlockChainTip: ${e.toString()}"); return null; } } @@ -388,6 +425,10 @@ class ElectrumClient { BehaviorSubject? subscribe( {required String id, required String method, List params = const []}) { try { + if (socket == null) { + _setConnectionStatus(ConnectionStatus.failed); + return null; + } final subscription = BehaviorSubject(); _regisrySubscription(id, subscription); socket!.write(jsonrpc(method: method, id: _id, params: params)); @@ -401,6 +442,10 @@ class ElectrumClient { Future call( {required String method, List params = const [], Function(int)? idCallback}) async { + if (socket == null) { + _setConnectionStatus(ConnectionStatus.failed); + return null; + } final completer = Completer(); _id += 1; final id = _id; @@ -414,6 +459,10 @@ class ElectrumClient { Future callWithTimeout( {required String method, List params = const [], int timeout = 4000}) async { try { + if (socket == null) { + _setConnectionStatus(ConnectionStatus.failed); + return null; + } final completer = Completer(); _id += 1; final id = _id; @@ -435,6 +484,7 @@ class ElectrumClient { _aliveTimer?.cancel(); try { await socket?.close(); + socket = null; } catch (_) {} onConnectionStatusChange = null; } @@ -489,12 +539,9 @@ class ElectrumClient { } } - void _setIsConnected(bool? isConnected) { - if (_isConnected != isConnected) { - onConnectionStatusChange?.call(isConnected); - } - - _isConnected = isConnected ?? false; + void _setConnectionStatus(ConnectionStatus status) { + onConnectionStatusChange?.call(status); + _isConnected = status == ConnectionStatus.connected; } void _handleResponse(Map response) { diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index d06cfe9de..ea4a3de33 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -22,7 +22,7 @@ class ElectrumTransactionInfo extends TransactionInfo { ElectrumTransactionInfo(this.type, {required String id, - required int height, + int? height, required int amount, int? fee, List? inputAddresses, @@ -99,7 +99,7 @@ class ElectrumTransactionInfo extends TransactionInfo { factory ElectrumTransactionInfo.fromElectrumBundle( ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network, - {required Set addresses, required int height}) { + {required Set addresses, int? height}) { final date = bundle.time != null ? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000) : DateTime.now(); diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 84be047bc..45e7b24ab 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -5,7 +5,6 @@ import 'dart:isolate'; import 'dart:math'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:collection/collection.dart'; import 'package:cw_bitcoin/address_from_output.dart'; @@ -22,8 +21,6 @@ import 'package:cw_bitcoin/electrum_transaction_history.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/exceptions.dart'; -import 'package:cw_bitcoin/litecoin_network.dart'; -import 'package:cw_bitcoin/litecoin_wallet.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/utils.dart'; @@ -38,11 +35,11 @@ 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_keys_file.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; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:sp_scanner/sp_scanner.dart'; @@ -55,12 +52,12 @@ const int TWEAKS_COUNT = 25; abstract class ElectrumWalletBase extends WalletBase - with Store { + with Store, WalletKeysFile { ElectrumWalletBase({ required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, - required this.networkType, + required this.network, String? xpub, String? mnemonic, Uint8List? seedBytes, @@ -71,7 +68,7 @@ abstract class ElectrumWalletBase CryptoCurrency? currency, this.alwaysScan, }) : accountHD = - getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo), + getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], @@ -90,8 +87,7 @@ abstract class ElectrumWalletBase } : {}), this.unspentCoinsInfo = unspentCoinsInfo, - this.network = _getNetwork(networkType, currency), - this.isTestnet = networkType == bitcoin.testnet, + this.isTestnet = network == BitcoinNetwork.testnet, this._mnemonic = mnemonic, super(walletInfo) { this.electrumClient = electrumClient ?? ElectrumClient(); @@ -101,12 +97,8 @@ abstract class ElectrumWalletBase reaction((_) => syncStatus, _syncStatusReaction); } - static bitcoin.HDWallet getAccountHDWallet( - CryptoCurrency? currency, - bitcoin.NetworkType networkType, - Uint8List? seedBytes, - String? xpub, - DerivationInfo? derivationInfo) { + static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network, + Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) { if (seedBytes == null && xpub == null) { throw Exception( "To create a Wallet you need either a seed or an xpub. This should not happen"); @@ -115,25 +107,26 @@ abstract class ElectrumWalletBase if (seedBytes != null) { return currency == CryptoCurrency.bch ? bitcoinCashHDWallet(seedBytes) - : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) - .derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)); + : Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath( + _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)) + as Bip32Slip10Secp256k1; } - return bitcoin.HDWallet.fromBase58(xpub!); + return Bip32Slip10Secp256k1.fromExtendedKey(xpub!); } - static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) => - bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'"); + static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => + Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1; static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 68 + outputsCounts * 34 + 10; bool? alwaysScan; - final bitcoin.HDWallet accountHD; + final Bip32Slip10Secp256k1 accountHD; final String? _mnemonic; - bitcoin.HDWallet get hd => accountHD.derive(0); + Bip32Slip10Secp256k1 get hd => accountHD.childKey(Bip32KeyIndex(0)); final String? passphrase; @override @@ -167,12 +160,15 @@ abstract class ElectrumWalletBase .map((addr) => scriptHash(addr.address, network: network)) .toList(); - String get xpub => accountHD.base58!; + String get xpub => accountHD.publicKey.toExtended; @override String? get seed => _mnemonic; - bitcoin.NetworkType networkType; + @override + WalletKeysData get walletKeysData => + WalletKeysData(mnemonic: _mnemonic, xPub: xpub, passphrase: passphrase); + BasedUtxoNetwork network; @override @@ -188,24 +184,21 @@ abstract class ElectrumWalletBase bool _isTryingToConnect = false; @action - Future setSilentPaymentsScanning(bool active, bool usingElectrs) async { + Future setSilentPaymentsScanning(bool active) async { silentPaymentsScanningActive = active; if (active) { - syncStatus = AttemptingSyncStatus(); + syncStatus = StartingScanSyncStatus(); final tip = await getUpdatedChainTip(); if (tip == walletInfo.restoreHeight) { syncStatus = SyncedTipSyncStatus(tip); + return; } if (tip > walletInfo.restoreHeight) { - _setListeners( - walletInfo.restoreHeight, - chainTipParam: _currentChainTip, - usingElectrs: usingElectrs, - ); + _setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip); } } else { alwaysScan = false; @@ -243,8 +236,11 @@ abstract class ElectrumWalletBase } @override - BitcoinWalletKeys get keys => - BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); + BitcoinWalletKeys get keys => BitcoinWalletKeys( + wif: WifEncoder.encode(hd.privateKey.raw, netVer: network.wifNetVer), + privateKey: hd.privateKey.toHex(), + publicKey: hd.publicKey.toHex(), + ); String _password; List unspentCoins; @@ -277,7 +273,7 @@ abstract class ElectrumWalletBase int height, { int? chainTipParam, bool? doSingleScan, - bool? usingElectrs, + bool? usingSupportedNode, }) async { final chainTip = chainTipParam ?? await getUpdatedChainTip(); @@ -286,7 +282,7 @@ abstract class ElectrumWalletBase return; } - syncStatus = AttemptingSyncStatus(); + syncStatus = StartingScanSyncStatus(); if (_isolate != null) { final runningIsolate = await _isolate!; @@ -304,7 +300,9 @@ abstract class ElectrumWalletBase chainTip: chainTip, electrumClient: ElectrumClient(), transactionHistoryIds: transactionHistory.transactions.keys.toList(), - node: usingElectrs == true ? ScanNode(node!.uri, node!.useSSL) : null, + node: (await getNodeSupportsSilentPayments()) == true + ? ScanNode(node!.uri, node!.useSSL) + : null, labels: walletAddresses.labels, labelIndexes: walletAddresses.silentAddresses .where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1) @@ -393,7 +391,7 @@ abstract class ElectrumWalletBase BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)), ) : silentAddress.B_spend, - hrp: silentAddress.hrp, + network: network, ); final addressRecord = walletAddresses.silentAddresses @@ -424,9 +422,6 @@ abstract class ElectrumWalletBase await updateAllUnspents(); await updateBalance(); - await updateFeeRates(); - Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates()); - if (alwaysScan == true) { _setListeners(walletInfo.restoreHeight); } else { @@ -449,6 +444,58 @@ abstract class ElectrumWalletBase Node? node; + Future getNodeIsElectrs() async { + if (node == null) { + return false; + } + + final version = await electrumClient.version(); + + if (version.isNotEmpty) { + final server = version[0]; + + if (server.toLowerCase().contains('electrs')) { + node!.isElectrs = true; + node!.save(); + return node!.isElectrs!; + } + } + + + node!.isElectrs = false; + node!.save(); + return node!.isElectrs!; + } + + Future getNodeSupportsSilentPayments() async { + // As of today (august 2024), only ElectrumRS supports silent payments + if (!(await getNodeIsElectrs())) { + return false; + } + + if (node == null) { + return false; + } + + try { + final tweaksResponse = await electrumClient.getTweaks(height: 0); + + if (tweaksResponse != null) { + node!.supportsSilentPayments = true; + node!.save(); + return node!.supportsSilentPayments!; + } + } on RequestFailedTimeoutException catch (_) { + node!.supportsSilentPayments = false; + node!.save(); + return node!.supportsSilentPayments!; + } catch (_) {} + + node!.supportsSilentPayments = false; + node!.save(); + return node!.supportsSilentPayments!; + } + @action @override Future connectToNode({required Node node}) async { @@ -511,13 +558,6 @@ abstract class ElectrumWalletBase final hd = utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd; - final derivationPath = - "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" - "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" - "/${utx.bitcoinAddressRecord.index}"; - final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!; - - publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; @@ -534,6 +574,7 @@ abstract class ElectrumWalletBase } vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); + String pubKeyHex; if (privkey != null) { inputPrivKeyInfos.add(ECPrivateInfo( @@ -541,8 +582,18 @@ abstract class ElectrumWalletBase address.type == SegwitAddresType.p2tr, tweak: !isSilentPayment, )); + + pubKeyHex = privkey.getPublic().toHex(); + } else { + pubKeyHex = hd.childKey(Bip32KeyIndex(utx.bitcoinAddressRecord.index)).publicKey.toHex(); } + final derivationPath = + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" + "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" + "/${utx.bitcoinAddressRecord.index}"; + publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); + utxos.add( UtxoWithAddress( utxo: BitcoinUtxo( @@ -1094,6 +1145,11 @@ abstract class ElectrumWalletBase @override Future save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password); + saveKeysFile(_password, true); + } + final path = await makePath(); await write(path: path, password: _password, data: toJSON()); await transactionHistory.save(); @@ -1135,10 +1191,9 @@ abstract class ElectrumWalletBase int? chainTip, ScanData? scanData, bool? doSingleScan, - bool? usingElectrs, }) async { silentPaymentsScanningActive = true; - _setListeners(height, doSingleScan: doSingleScan, usingElectrs: usingElectrs); + _setListeners(height, doSingleScan: doSingleScan); } @override @@ -1150,8 +1205,6 @@ abstract class ElectrumWalletBase _autoSaveTimer?.cancel(); } - Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - @action Future updateAllUnspents() async { List updatedUnspentCoins = []; @@ -1250,7 +1303,7 @@ abstract class ElectrumWalletBase await Future.wait(unspents.map((unspent) async { try { final coin = BitcoinUnspent.fromJSON(address, unspent); - final tx = await fetchTransactionInfo(hash: coin.hash, height: 0); + final tx = await fetchTransactionInfo(hash: coin.hash); coin.isChange = address.isHidden; coin.confirmations = tx?.confirmations; @@ -1305,9 +1358,17 @@ abstract class ElectrumWalletBase } Future canReplaceByFee(String hash) async { - final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); - final confirmations = verboseTransaction['confirmations'] as int? ?? 0; - final transactionHex = verboseTransaction['hex'] as String?; + final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash); + + final String? transactionHex; + int confirmations = 0; + + if (verboseTransaction.isEmpty) { + transactionHex = await electrumClient.getTransactionHex(hash: hash); + } else { + confirmations = verboseTransaction['confirmations'] as int? ?? 0; + transactionHex = verboseTransaction['hex'] as String?; + } if (confirmations > 0) return false; @@ -1315,10 +1376,7 @@ abstract class ElectrumWalletBase return false; } - final original = bitcoin.Transaction.fromHex(transactionHex); - - return original.ins - .any((element) => element.sequence != null && element.sequence! < 4294967293); + return BtcTransaction.fromRaw(transactionHex).canReplaceByFee; } Future isChangeSufficientForFee(String txId, int newFee) async { @@ -1477,50 +1535,73 @@ abstract class ElectrumWalletBase } } - Future getTransactionExpanded({required String hash}) async { + Future getTransactionExpanded( + {required String hash, int? height}) async { String transactionHex; + // TODO: time is not always available, and calculating it from height is not always accurate. + // Add settings to choose API provider and use and http server instead of electrum for this. int? time; - int confirmations = 0; - if (network == BitcoinNetwork.testnet) { - // Testnet public electrum server does not support verbose transaction fetching + int? confirmations; + + final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash); + + if (verboseTransaction.isEmpty) { transactionHex = await electrumClient.getTransactionHex(hash: hash); - - final status = json.decode( - (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body); - - time = status["block_time"] as int?; - final height = status["block_height"] as int? ?? 0; - final tip = await getUpdatedChainTip(); - if (tip > 0) confirmations = height > 0 ? tip - height + 1 : 0; } else { - final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); - transactionHex = verboseTransaction['hex'] as String; time = verboseTransaction['time'] as int?; - confirmations = verboseTransaction['confirmations'] as int? ?? 0; + confirmations = verboseTransaction['confirmations'] as int?; + } + + if (height != null) { + if (time == null) { + time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round(); + } + + if (confirmations == null) { + final tip = await getUpdatedChainTip(); + if (tip > 0 && height > 0) { + // Add one because the block itself is the first confirmation + confirmations = tip - height + 1; + } + } } final original = BtcTransaction.fromRaw(transactionHex); final ins = []; for (final vin in original.inputs) { - ins.add(BtcTransaction.fromRaw(await electrumClient.getTransactionHex(hash: vin.txId))); + final verboseTransaction = await electrumClient.getTransactionVerbose(hash: vin.txId); + + final String inputTransactionHex; + + if (verboseTransaction.isEmpty) { + inputTransactionHex = await electrumClient.getTransactionHex(hash: hash); + } else { + inputTransactionHex = verboseTransaction['hex'] as String; + } + + ins.add(BtcTransaction.fromRaw(inputTransactionHex)); } return ElectrumTransactionBundle( original, ins: ins, time: time, - confirmations: confirmations, + confirmations: confirmations ?? 0, ); } Future fetchTransactionInfo( - {required String hash, required int height, bool? retryOnFailure}) async { + {required String hash, int? height, bool? retryOnFailure}) async { try { return ElectrumTransactionInfo.fromElectrumBundle( - await getTransactionExpanded(hash: hash), walletInfo.type, network, - addresses: addressesSet, height: height); + await getTransactionExpanded(hash: hash, height: height), + walletInfo.type, + network, + addresses: addressesSet, + height: height, + ); } catch (e) { if (e is FormatException && retryOnFailure == true) { await Future.delayed(const Duration(seconds: 2)); @@ -1671,8 +1752,8 @@ abstract class ElectrumWalletBase await getCurrentChainTip(); transactionHistory.transactions.values.forEach((tx) async { - if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) { - tx.confirmations = await getCurrentChainTip() - tx.height + 1; + if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height != null && tx.height! > 0) { + tx.confirmations = await getCurrentChainTip() - tx.height! + 1; } }); @@ -1779,8 +1860,12 @@ abstract class ElectrumWalletBase final index = address != null ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index : null; - final HD = index == null ? hd : hd.derive(index); - return base64Encode(HD.signMessage(message)); + final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); + final priv = ECPrivate.fromWif( + WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer), + netVersion: network.wifNetVer, + ); + return priv.signMessage(StringUtils.encode(message)); } Future _setInitialHeight() async { @@ -1806,43 +1891,42 @@ abstract class ElectrumWalletBase }); } - static BasedUtxoNetwork _getNetwork(bitcoin.NetworkType networkType, CryptoCurrency? currency) { - if (networkType == bitcoin.bitcoin && currency == CryptoCurrency.bch) { - return BitcoinCashNetwork.mainnet; - } - - if (networkType == litecoinNetwork) { - return LitecoinNetwork.mainnet; - } - - if (networkType == bitcoin.testnet) { - return BitcoinNetwork.testnet; - } - - return BitcoinNetwork.mainnet; - } - static String _hardenedDerivationPath(String derivationPath) => derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1); @action - void _onConnectionStatusChange(bool? isConnected) { - if (syncStatus is SyncingSyncStatus) return; + void _onConnectionStatusChange(ConnectionStatus status) { + switch (status) { + case ConnectionStatus.connected: + if (syncStatus is NotConnectedSyncStatus || + syncStatus is LostConnectionSyncStatus || + syncStatus is ConnectingSyncStatus) { + syncStatus = AttemptingSyncStatus(); + startSync(); + } - if (isConnected == true && syncStatus is! SyncedSyncStatus) { - syncStatus = ConnectedSyncStatus(); - } else if (isConnected == false) { - syncStatus = LostConnectionSyncStatus(); - } else if (isConnected != true && syncStatus is! ConnectingSyncStatus) { - syncStatus = NotConnectedSyncStatus(); + break; + case ConnectionStatus.disconnected: + syncStatus = NotConnectedSyncStatus(); + break; + case ConnectionStatus.failed: + syncStatus = LostConnectionSyncStatus(); + // wait for 5 seconds and then try to reconnect: + Future.delayed(Duration(seconds: 5), () { + electrumClient.connectToUri( + node!.uri, + useSSL: node!.useSSL ?? false, + ); + }); + break; + case ConnectionStatus.connecting: + syncStatus = ConnectingSyncStatus(); + break; + default: } } void _syncStatusReaction(SyncStatus syncStatus) async { - if (syncStatus is! AttemptingSyncStatus && syncStatus is! SyncedTipSyncStatus) { - silentPaymentsScanningActive = syncStatus is SyncingSyncStatus; - } - if (syncStatus is NotConnectedSyncStatus) { // Needs to re-subscribe to all scripthashes when reconnected _scripthashesUpdateSubject = {}; @@ -1963,8 +2047,8 @@ Future startRefresh(ScanData scanData) async { final tweaks = t as Map; if (tweaks["message"] != null) { - // re-subscribe to continue receiving messages - electrumClient.tweaksSubscribe(height: syncHeight, count: count); + // re-subscribe to continue receiving messages, starting from the next unscanned height + electrumClient.tweaksSubscribe(height: syncHeight + 1, count: count); return; } @@ -2197,3 +2281,4 @@ class UtxoDetails { required this.spendsUnconfirmedTX, }); } + diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 3742d8d48..166ec9bb3 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -1,5 +1,4 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_core/wallet_addresses.dart'; @@ -31,7 +30,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { Map? initialChangeAddressIndex, List? initialSilentAddresses, int initialSilentAddressIndex = 0, - bitcoin.HDWallet? masterHd, + Bip32Slip10Secp256k1? masterHd, BitcoinAddressType? initialAddressPageType, }) : _addresses = ObservableList.of((initialAddresses ?? []).toSet()), addressesByReceiveType = @@ -54,9 +53,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { 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'); + b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privateKey.toHex()), + b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privateKey.toHex()), + network: network, + ); if (silentAddresses.length == 0) { silentAddresses.add(BitcoinSilentPaymentAddressRecord( @@ -93,8 +93,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final ObservableList changeAddresses; final ObservableList silentAddresses; final BasedUtxoNetwork network; - final bitcoin.HDWallet mainHd; - final bitcoin.HDWallet sideHd; + final Bip32Slip10Secp256k1 mainHd; + final Bip32Slip10Secp256k1 sideHd; @observable SilentPaymentOwner? silentAddress; @@ -320,7 +320,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } String getAddress( - {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) => + {required int index, + required Bip32Slip10Secp256k1 hd, + BitcoinAddressType? addressType}) => ''; Future getAddressAsync( @@ -575,8 +577,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { void _validateAddresses() { _addresses.forEach((element) { - if (!element.isHidden && element.address != - getAddress(index: element.index, hd: mainHd, addressType: element.type)) { + if (!element.isHidden && + element.address != + getAddress(index: element.index, hd: mainHd, addressType: element.type)) { element.isHidden = true; } else if (element.isHidden && element.address != @@ -598,7 +601,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return _isAddressByType(addressRecord, addressPageType); } - bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd; + Bip32Slip10Secp256k1 _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/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 15ad1cf63..082460f72 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -32,15 +32,21 @@ class ElectrumWalletSnapshot { final WalletType type; final String? addressPageType; + @deprecated String? mnemonic; + + @deprecated String? xpub; + + @deprecated + String? passphrase; + List addresses; List silentAddresses; ElectrumBalance balance; Map regularAddressIndex; Map changeAddressIndex; int silentAddressIndex; - String? passphrase; DerivationType? derivationType; String? derivationPath; diff --git a/cw_bitcoin/lib/litecoin_network.dart b/cw_bitcoin/lib/litecoin_network.dart deleted file mode 100644 index d7ad2f837..000000000 --- a/cw_bitcoin/lib/litecoin_network.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:bitcoin_flutter/bitcoin_flutter.dart'; - -final litecoinNetwork = NetworkType( - messagePrefix: '\x19Litecoin Signed Message:\n', - bech32: 'ltc', - bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), - pubKeyHash: 0x30, - scriptHash: 0x32, - wif: 0xb0); diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index c526bb0c0..49401f6fd 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,13 +1,17 @@ import 'dart:async'; import 'dart:math'; +import 'package:blockchain_utils/bip/bip/bip32/base/bip32_base.dart'; import 'package:collection/collection.dart'; -import 'package:convert/convert.dart'; +import 'package:convert/convert.dart' as convert; import 'package:crypto/crypto.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/mweb_utxo.dart'; import 'package:cw_mweb/mwebd.pbgrpc.dart'; import 'package:fixnum/fixnum.dart'; +import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; @@ -19,21 +23,19 @@ 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/unspent_coins_info.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; -import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; -import 'package:cw_bitcoin/electrum_wallet.dart'; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/electrum_balance.dart'; -import 'package:cw_bitcoin/litecoin_network.dart'; import 'package:cw_mweb/cw_mweb.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:bip39/bip39.dart' as bip39; part 'litecoin_wallet.g.dart'; @@ -53,19 +55,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { Map? initialChangeAddressIndex, int? initialMwebHeight, bool? alwaysScan, - }) : mwebHd = - bitcoin.HDWallet.fromSeed(seedBytes, network: litecoinNetwork).derivePath("m/1000'"), - super( + }) : super( mnemonic: mnemonic, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - networkType: litecoinNetwork, + network: LitecoinNetwork.mainnet, initialAddresses: initialAddresses, initialBalance: initialBalance, seedBytes: seedBytes, currency: CryptoCurrency.ltc, ) { + mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000") as Bip32Slip10Secp256k1; mwebEnabled = alwaysScan ?? false; walletAddresses = LitecoinWalletAddresses( walletInfo, @@ -73,7 +74,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, mainHd: hd, - sideHd: accountHD.derive(1), + sideHd: accountHD.childKey(Bip32KeyIndex(1)), network: network, mwebHd: mwebHd, ); @@ -85,7 +86,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { }); } - final bitcoin.HDWallet mwebHd; + late final Bip32Slip10Secp256k1 mwebHd; + late final bitcoin.HDWallet oldMwebHd; late final Box mwebUtxosBox; Timer? _syncTimer; StreamSubscription? _utxoStream; @@ -93,6 +95,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { late RpcClient _stub; late bool mwebEnabled; + List get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw; + List get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw; + static Future create( {required String mnemonic, required String password, @@ -139,20 +144,37 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { required String password, required bool alwaysScan, }) async { - final snp = - await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet); + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); + + ElectrumWalletSnapshot? snp = null; + + try { + snp = await ElectrumWalletSnapshot.load( + name, walletInfo.type, password, LitecoinNetwork.mainnet); + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + keysData = + WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + return LitecoinWallet( - mnemonic: snp.mnemonic!, + mnemonic: keysData.mnemonic!, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp.addresses, - initialBalance: snp.balance, - seedBytes: await mnemonicToSeedBytes(snp.mnemonic!), - initialRegularAddressIndex: snp.regularAddressIndex, - initialChangeAddressIndex: snp.changeAddressIndex, - addressPageType: snp.addressPageType, - alwaysScan: alwaysScan, + initialAddresses: snp?.addresses, + initialBalance: snp?.balance, + seedBytes: await mnemonicToSeedBytes(keysData.mnemonic!), + initialRegularAddressIndex: snp?.regularAddressIndex, + initialChangeAddressIndex: snp?.changeAddressIndex, + addressPageType: snp?.addressPageType, ); } @@ -199,7 +221,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { // update the confirmations for each transaction: for (final transaction in transactionHistory.transactions.values) { if (transaction.isPending) continue; - final confirmations = mwebUtxosHeight - transaction.height + 1; + int txHeight = transaction.height ?? mwebUtxosHeight; + final confirmations = (mwebUtxosHeight - txHeight) + 1; if (transaction.confirmations == confirmations) continue; transaction.confirmations = confirmations; transactionHistory.addOne(transaction); @@ -348,10 +371,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } Future processMwebUtxos() async { - final scanSecret = mwebHd.derive(0x80000000).privKey!; + // final scanSecret = oldMwebHd.derive(0x80000000).privKey!; int restoreHeight = walletInfo.restoreHeight; print("SCANNING FROM HEIGHT: $restoreHeight"); - final req = UtxosRequest(scanSecret: hex.decode(scanSecret), fromHeight: restoreHeight); + final req = UtxosRequest(scanSecret: scanSecret, fromHeight: restoreHeight); // process old utxos: for (final utxo in mwebUtxosBox.values) { @@ -425,7 +448,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { if (status.mwebUtxosHeight != height) return; int amount = 0; Set inputAddresses = {}; - var output = AccumulatorSink(); + var output = convert.AccumulatorSink(); var input = sha256.startChunkedConversion(output); for (final outputId in spent) { final utxo = mwebUtxosBox.get(outputId); @@ -541,7 +564,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { updatedUnspentCoins.add(unspent); }); } - + unspentCoins = updatedUnspentCoins; } @@ -665,8 +688,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { BitcoinTransactionBuilder(utxos: utxos, outputs: outputs, fee: fee, network: network); final resp = await _stub.create(CreateRequest( rawTx: txb.buildTransaction((a, b, c, d) => '').toBytes(), - scanSecret: hex.decode(mwebHd.derive(0x80000000).privKey!), - spendSecret: hex.decode(mwebHd.derive(0x80000001).privKey!), + scanSecret: scanSecret, + spendSecret: spendSecret, feeRatePerKb: Int64(feeRate * 1000), dryRun: true)); final tx = BtcTransaction.fromRaw(hex.encode(resp.rawTx)); @@ -705,8 +728,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { final resp = await _stub.create(CreateRequest( rawTx: hex.decode(tx.hex), - scanSecret: hex.decode(mwebHd.derive(0x80000000).privKey!), - spendSecret: hex.decode(mwebHd.derive(0x80000001).privKey!), + scanSecret: scanSecret, + spendSecret: spendSecret, feeRatePerKb: Int64.parseInt(tx.feeRate) * 1000, )); final tx2 = BtcTransaction.fromRaw(hex.encode(resp.rawTx)); diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index 36bb569c6..7fc013211 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -1,6 +1,6 @@ import 'package:convert/convert.dart'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; @@ -26,20 +26,22 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with topUpMweb(0); } - final HDWallet mwebHd; + final Bip32Slip10Secp256k1 mwebHd; + List get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw; + // TODO: I'm not 100% sure if it's supposed to be the compressed or uncompressed public key! + List get spendPubkey => mwebHd.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed; + List mwebAddrs = []; Future topUpMweb(int index) async { while (mwebAddrs.length - index < 1000) { final length = mwebAddrs.length; - final scanSecret = mwebHd.derive(0x80000000).privKey!; - final spendPubkey = mwebHd.derive(0x80000001).pubKey!; final stub = await CwMweb.stub(); final resp = await stub.addresses(AddressRequest( fromIndex: length, toIndex: index + 1000, - scanSecret: hex.decode(scanSecret), - spendPubkey: hex.decode(spendPubkey), + scanSecret: scanSecret, + spendPubkey: spendPubkey, )); if (mwebAddrs.length == length) { mwebAddrs.addAll(resp.address); @@ -48,7 +50,11 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with } @override - String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) { + String getAddress({ + required int index, + required Bip32Slip10Secp256k1 hd, + BitcoinAddressType? addressType, + }) { if (addressType == SegwitAddresType.mweb) { topUpMweb(index); return hd == sideHd ? mwebAddrs[0] : mwebAddrs[index + 1]; @@ -57,8 +63,11 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with } @override - Future getAddressAsync( - {required int index, required HDWallet hd, BitcoinAddressType? addressType}) async { + Future getAddressAsync({ + required int index, + required Bip32Slip10Secp256k1 hd, + BitcoinAddressType? addressType, + }) async { if (addressType == SegwitAddresType.mweb) { await topUpMweb(index); } diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index 12013fb63..f66b537e1 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -37,6 +37,7 @@ class LitecoinWalletService extends WalletService< walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, ); + await wallet.save(); await wallet.init(); diff --git a/cw_bitcoin/lib/utils.dart b/cw_bitcoin/lib/utils.dart index e3ebc00db..29d7a9bf3 100644 --- a/cw_bitcoin/lib/utils.dart +++ b/cw_bitcoin/lib/utils.dart @@ -1,68 +1,54 @@ -import 'dart:typed_data'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:flutter/foundation.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; -import 'package:hex/hex.dart'; - -bitcoin.PaymentData generatePaymentData({ - required bitcoin.HDWallet hd, - required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey))); -} +import 'package:blockchain_utils/blockchain_utils.dart'; ECPrivate generateECPrivate({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final wif = hd.derive(index).wif!; - return ECPrivate.fromWif(wif, netVersion: network.wifNetVer); -} +}) => + ECPrivate(hd.childKey(Bip32KeyIndex(index)).privateKey); String generateP2WPKHAddress({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network); -} +}) => + ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) + .toP2wpkhAddress() + .toAddress(network); String generateP2SHAddress({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network); -} +}) => + ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) + .toP2wshInP2sh() + .toAddress(network); String generateP2WSHAddress({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network); -} +}) => + ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) + .toP2wshAddress() + .toAddress(network); String generateP2PKHAddress({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network); -} +}) => + ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) + .toP2pkhAddress() + .toAddress(network); String generateP2TRAddress({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network); -} +}) => + ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) + .toTaprootAddress() + .toAddress(network); diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index e46d9fef8..99ad737b1 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -49,15 +49,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" - bech32: - dependency: transitive - description: - path: "." - ref: "cake-0.2.2" - resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192" - url: "https://github.com/cake-tech/bech32.git" - source: git - version: "0.2.2" bip32: dependency: transitive description: @@ -87,29 +78,20 @@ packages: dependency: "direct main" description: path: "." - ref: cake-update-v3 - resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149 + ref: cake-update-v4 + resolved-ref: "574486bfcdbbaf978dcd006b46fc8716f880da29" url: "https://github.com/cake-tech/bitcoin_base" source: git - version: "4.2.1" - bitcoin_flutter: - dependency: "direct main" - description: - path: "." - ref: cake-update-v4 - resolved-ref: e19ffb7e7977278a75b27e0479b3c6f4034223b3 - url: "https://github.com/cake-tech/bitcoin_flutter.git" - source: git - version: "2.1.0" + version: "4.7.0" blockchain_utils: dependency: "direct main" description: path: "." - ref: cake-update-v1 - resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3 + ref: cake-update-v2 + resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57" url: "https://github.com/cake-tech/blockchain_utils" source: git - version: "2.1.2" + version: "3.3.0" boolean_selector: dependency: transitive description: @@ -442,10 +424,10 @@ packages: dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -530,11 +512,12 @@ packages: ledger_flutter: dependency: "direct main" description: - name: ledger_flutter - sha256: f1680060ed6ff78f275837e0024ccaf667715a59ba7aa29fa7354bc7752e71c8 - url: "https://pub.dev" - source: hosted - version: "1.0.1" + path: "." + ref: cake-v3 + resolved-ref: "66469ff9dffe2417c70ae7287c9d76d2fe7157a4" + url: "https://github.com/cake-tech/ledger-flutter.git" + source: git + version: "1.0.2" ledger_usb: dependency: transitive description: @@ -627,10 +610,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: @@ -667,18 +650,18 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" platform: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -731,10 +714,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" quiver: dependency: transitive description: @@ -792,10 +775,10 @@ packages: dependency: transitive description: name: socks5_proxy - sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83" + sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" url: "https://pub.dev" source: hosted - version: "1.0.5+dev.2" + version: "1.0.6" source_gen: dependency: transitive description: @@ -824,9 +807,9 @@ packages: dependency: "direct main" description: path: "." - ref: "sp_v2.0.0" - resolved-ref: "62c152b9086cd968019128845371072f7e1168de" - url: "https://github.com/cake-tech/sp_scanner" + ref: "sp_v4.0.0" + resolved-ref: "3b8ae38592c0584f53560071dc18bc570758fe13" + url: "https://github.com/rafael-xmr/sp_scanner" source: git version: "0.0.1" stack_trace: @@ -941,14 +924,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.5" - win32: - dependency: transitive - description: - name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" - url: "https://pub.dev" - source: hosted - version: "5.5.0" xdg_directories: dependency: transitive description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 82a5c84ab..fc20cf2f9 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -19,10 +19,6 @@ dependencies: intl: ^0.18.0 cw_core: path: ../cw_core - bitcoin_flutter: - git: - url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v4 bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git @@ -32,11 +28,11 @@ dependencies: bitcoin_base: git: url: https://github.com/cake-tech/bitcoin_base - ref: cake-mweb + ref: cake-update-v5 blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils - ref: cake-update-v1 + ref: cake-update-v2 ledger_flutter: ^1.0.1 ledger_bitcoin: git: @@ -46,8 +42,8 @@ dependencies: grpc: ^3.2.4 sp_scanner: git: - url: https://github.com/cake-tech/sp_scanner - ref: sp_v2.0.0 + url: https://github.com/rafael-xmr/sp_scanner + ref: sp_v4.0.0 dev_dependencies: @@ -59,12 +55,16 @@ dev_dependencies: hive_generator: ^1.1.3 dependency_overrides: + ledger_flutter: + git: + url: https://github.com/cake-tech/ledger-flutter.git + ref: cake-v3 watcher: ^1.1.0 protobuf: ^3.1.0 bitcoin_base: git: url: https://github.com/cake-tech/bitcoin_base - ref: cake-mweb + ref: cake-update-v5 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 51bd3612d..8323c01a8 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -1,8 +1,6 @@ -import 'dart:convert'; - import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; @@ -12,6 +10,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -39,7 +38,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - networkType: bitcoin.bitcoin, + network: BitcoinCashNetwork.mainnet, initialAddresses: initialAddresses, initialBalance: initialBalance, seedBytes: seedBytes, @@ -50,7 +49,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, mainHd: hd, - sideHd: accountHD.derive(1), + sideHd: accountHD.childKey(Bip32KeyIndex(1)), network: network, initialAddressPageType: addressPageType, ); @@ -76,7 +75,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, initialBalance: initialBalance, - seedBytes: await Mnemonic.toSeed(mnemonic), + seedBytes: await MnemonicBip39.toSeed(mnemonic), initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, addressPageType: P2pkhAddressType.p2pkh, @@ -89,14 +88,32 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { required Box unspentCoinsInfo, required String password, }) async { - final snp = await ElectrumWalletSnapshot.load( - name, walletInfo.type, password, BitcoinCashNetwork.mainnet); + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); + + ElectrumWalletSnapshot? snp = null; + + try { + snp = await ElectrumWalletSnapshot.load( + name, walletInfo.type, password, BitcoinCashNetwork.mainnet); + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + keysData = + WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + return BitcoinCashWallet( - mnemonic: snp.mnemonic!, + mnemonic: keysData.mnemonic!, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp.addresses.map((addr) { + initialAddresses: snp?.addresses.map((addr) { try { BitcoinCashAddress(addr.address); return BitcoinAddressRecord( @@ -116,16 +133,18 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { ); } }).toList(), - initialBalance: snp.balance, - seedBytes: await Mnemonic.toSeed(snp.mnemonic!), - initialRegularAddressIndex: snp.regularAddressIndex, - initialChangeAddressIndex: snp.changeAddressIndex, + initialBalance: snp?.balance, + seedBytes: await MnemonicBip39.toSeed(keysData.mnemonic!), + initialRegularAddressIndex: snp?.regularAddressIndex, + initialChangeAddressIndex: snp?.changeAddressIndex, addressPageType: P2pkhAddressType.p2pkh, ); } - bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) => - bitbox.ECPair.fromWIF(hd.derive(index).wif!); + bitbox.ECPair generateKeyPair({required Bip32Slip10Secp256k1 hd, required int index}) => + bitbox.ECPair.fromPrivateKey( + Uint8List.fromList(hd.childKey(Bip32KeyIndex(index)).privateKey.raw), + ); int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) { int inputsCount = 0; @@ -171,7 +190,11 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address)) .index : null; - final HD = index == null ? hd : hd.derive(index); - return base64Encode(HD.signMessage(message)); + final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); + final priv = ECPrivate.fromWif( + WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer), + netVersion: network.wifNetVer, + ); + return priv.signMessage(StringUtils.encode(message)); } } 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 d543e944c..7342dc7f5 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart @@ -1,5 +1,5 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/wallet_info.dart'; @@ -23,6 +23,8 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi @override String getAddress( - {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) => + {required int index, + required Bip32Slip10Secp256k1 hd, + BitcoinAddressType? addressType}) => generateP2PKHAddress(hd: hd, index: index, network: network); } diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart index e6c0cad07..002e52c4f 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart @@ -11,8 +11,11 @@ import 'package:cw_core/wallet_type.dart'; import 'package:collection/collection.dart'; import 'package:hive/hive.dart'; -class BitcoinCashWalletService extends WalletService { +class BitcoinCashWalletService extends WalletService< + BitcoinCashNewWalletCredentials, + BitcoinCashRestoreWalletFromSeedCredentials, + BitcoinCashRestoreWalletFromWIFCredentials, + BitcoinCashNewWalletCredentials> { BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); final Box walletInfoSource; @@ -30,12 +33,14 @@ class BitcoinCashWalletService extends WalletService restoreFromHardwareWallet(BitcoinCashNewWalletCredentials credentials) { - throw UnimplementedError("Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!"); + throw UnimplementedError( + "Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!"); } @override diff --git a/cw_bitcoin_cash/lib/src/mnemonic.dart b/cw_bitcoin_cash/lib/src/mnemonic.dart index b1f1ee984..7aac1d5c4 100644 --- a/cw_bitcoin_cash/lib/src/mnemonic.dart +++ b/cw_bitcoin_cash/lib/src/mnemonic.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; import 'package:bip39/bip39.dart' as bip39; -class Mnemonic { +class MnemonicBip39 { /// Generate bip39 mnemonic static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength); diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 367a025cb..9c2561def 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -21,10 +21,6 @@ dependencies: path: ../cw_core cw_bitcoin: path: ../cw_bitcoin - bitcoin_flutter: - git: - url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v4 bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git @@ -36,7 +32,7 @@ dependencies: blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils - ref: cake-update-v1 + ref: cake-update-v2 dev_dependencies: flutter_test: diff --git a/cw_core/lib/get_height_by_date.dart b/cw_core/lib/get_height_by_date.dart index 6f1b4078b..204f03d62 100644 --- a/cw_core/lib/get_height_by_date.dart +++ b/cw_core/lib/get_height_by_date.dart @@ -245,6 +245,8 @@ Future getHavenCurrentHeight() async { // Data taken from https://timechaincalendar.com/ const bitcoinDates = { + "2024-08": 854889, + "2024-07": 850182, "2024-06": 846005, "2024-05": 841590, "2024-04": 837182, @@ -371,7 +373,8 @@ const wowDates = { int getWowneroHeightByDate({required DateTime date}) { String closestKey = - wowDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => ''); + wowDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => ''); return wowDates[closestKey] ?? 0; -} \ No newline at end of file +} + diff --git a/cw_core/lib/monero_wallet_utils.dart b/cw_core/lib/monero_wallet_utils.dart index 1b1988eb6..8a4990f78 100644 --- a/cw_core/lib/monero_wallet_utils.dart +++ b/cw_core/lib/monero_wallet_utils.dart @@ -79,6 +79,7 @@ Future backupWalletFilesExists(String name) async { backupAddressListFile.existsSync(); } +// WARNING: Transaction keys and your Polyseed CANNOT be recovered if this file is deleted Future removeCache(String name) async { final path = await pathForWallet(name: name, type: WalletType.monero); final cacheFile = File(path); @@ -92,8 +93,8 @@ Future restoreOrResetWalletFiles(String name) async { final backupsExists = await backupWalletFilesExists(name); if (backupsExists) { + await removeCache(name); + await restoreWalletFiles(name); } - - removeCache(name); } diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index f35ea589f..85c61de15 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -11,7 +11,8 @@ import 'package:http/io_client.dart' as ioc; part 'node.g.dart'; -Uri createUriFromElectrumAddress(String address, String path) => Uri.tryParse('tcp://$address$path')!; +Uri createUriFromElectrumAddress(String address, String path) => + Uri.tryParse('tcp://$address$path')!; @HiveType(typeId: Node.typeId) class Node extends HiveObject with Keyable { @@ -72,6 +73,12 @@ class Node extends HiveObject with Keyable { @HiveField(7, defaultValue: '') String? path; + @HiveField(8) + bool? isElectrs; + + @HiveField(9) + bool? supportsSilentPayments; + bool get isSSL => useSSL ?? false; bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty; diff --git a/cw_core/lib/sync_status.dart b/cw_core/lib/sync_status.dart index 55c31877f..ea015340c 100644 --- a/cw_core/lib/sync_status.dart +++ b/cw_core/lib/sync_status.dart @@ -3,6 +3,11 @@ abstract class SyncStatus { double progress(); } +class StartingScanSyncStatus extends SyncStatus { + @override + double progress() => 0.0; +} + class SyncingSyncStatus extends SyncStatus { SyncingSyncStatus(this.blocksLeft, this.ptc); diff --git a/cw_core/lib/transaction_info.dart b/cw_core/lib/transaction_info.dart index e363d88db..971e4ecdb 100644 --- a/cw_core/lib/transaction_info.dart +++ b/cw_core/lib/transaction_info.dart @@ -9,7 +9,7 @@ abstract class TransactionInfo extends Object with Keyable { late TransactionDirection direction; late bool isPending; late DateTime date; - late int height; + int? height; late int confirmations; String amountFormatted(); String fiatAmount(); @@ -25,4 +25,5 @@ abstract class TransactionInfo extends Object with Keyable { dynamic get keyIndex => id; late Map additionalInfo; -} \ No newline at end of file +} + diff --git a/cw_core/lib/wallet_keys_file.dart b/cw_core/lib/wallet_keys_file.dart new file mode 100644 index 000000000..45539e09d --- /dev/null +++ b/cw_core/lib/wallet_keys_file.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; +import 'dart:developer' as dev; +import 'dart:io'; + +import 'package:cw_core/balance.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/utils/file.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; + +mixin WalletKeysFile + on WalletBase { + Future makePath() => pathForWallet(name: walletInfo.name, type: walletInfo.type); + + // this needs to be overridden + WalletKeysData get walletKeysData; + + Future makeKeysFilePath() async => "${await makePath()}.keys"; + + Future saveKeysFile(String password, [bool isBackup = false]) async { + try { + final rootPath = await makeKeysFilePath(); + final path = "$rootPath${isBackup ? ".backup" : ""}"; + dev.log("Saving .keys file '$path'"); + await write(path: path, password: password, data: walletKeysData.toJSON()); + } catch (_) {} + } + + static Future createKeysFile( + String name, WalletType type, String password, WalletKeysData walletKeysData, + [bool withBackup = true]) async { + try { + final rootPath = await pathForWallet(name: name, type: type); + final path = "$rootPath.keys"; + + dev.log("Saving .keys file '$path'"); + await write(path: path, password: password, data: walletKeysData.toJSON()); + + if (withBackup) { + dev.log("Saving .keys.backup file '$path.backup'"); + await write(path: "$path.backup", password: password, data: walletKeysData.toJSON()); + } + } catch (_) {} + } + + static Future hasKeysFile(String name, WalletType type) async { + try { + final path = await pathForWallet(name: name, type: type); + return File("$path.keys").existsSync() || File("$path.keys.backup").existsSync(); + } catch (_) { + return false; + } + } + + static Future readKeysFile(String name, WalletType type, String password) async { + final path = await pathForWallet(name: name, type: type); + + var readPath = "$path.keys"; + try { + if (!File(readPath).existsSync()) throw Exception("No .keys file found for $name $type"); + + final jsonSource = await read(path: readPath, password: password); + final data = json.decode(jsonSource) as Map; + return WalletKeysData.fromJSON(data); + } catch (e) { + dev.log("Failed to read .keys file. Trying .keys.backup file..."); + + readPath = "$readPath.backup"; + if (!File(readPath).existsSync()) + throw Exception("No .keys nor a .keys.backup file found for $name $type"); + + final jsonSource = await read(path: readPath, password: password); + final data = json.decode(jsonSource) as Map; + final keysData = WalletKeysData.fromJSON(data); + + dev.log("Restoring .keys from .keys.backup"); + createKeysFile(name, type, password, keysData, false); + return keysData; + } + } +} + +class WalletKeysData { + final String? privateKey; + final String? mnemonic; + final String? altMnemonic; + final String? passphrase; + final String? xPub; + + WalletKeysData({this.privateKey, this.mnemonic, this.altMnemonic, this.passphrase, this.xPub}); + + String toJSON() => jsonEncode({ + "privateKey": privateKey, + "mnemonic": mnemonic, + if (altMnemonic != null) "altMnemonic": altMnemonic, + if (passphrase != null) "passphrase": passphrase, + if (xPub != null) "xPub": xPub + }); + + static WalletKeysData fromJSON(Map json) => WalletKeysData( + privateKey: json["privateKey"] as String?, + mnemonic: json["mnemonic"] as String?, + altMnemonic: json["altMnemonic"] as String?, + passphrase: json["passphrase"] as String?, + xPub: json["xPub"] as String?, + ); +} diff --git a/cw_core/lib/wallet_service.dart b/cw_core/lib/wallet_service.dart index fcbd59ff3..d90ae30bc 100644 --- a/cw_core/lib/wallet_service.dart +++ b/cw_core/lib/wallet_service.dart @@ -1,6 +1,8 @@ +import 'dart:convert'; import 'dart:io'; import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_type.dart'; @@ -42,4 +44,21 @@ abstract class WalletService getSeeds(String name, String password, WalletType type) async { + try { + final path = await pathForWallet(name: name, type: type); + final jsonSource = await read(path: path, password: password); + try { + final data = json.decode(jsonSource) as Map; + return data['mnemonic'] as String? ?? ''; + } catch (_) { + // if not a valid json + return jsonSource.substring(0, 200); + } + } catch (_) { + // if the file couldn't be opened or read + return ''; + } + } } diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index 2c58cd31d..7bcd55cf4 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -6,6 +6,7 @@ import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart'; import 'package:cw_ethereum/ethereum_client.dart'; import 'package:cw_ethereum/ethereum_transaction_history.dart'; @@ -122,19 +123,37 @@ class EthereumWallet extends EVMChainWallet { static Future open( {required String name, required String password, required WalletInfo walletInfo}) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String?; - final privateKey = data['private_key'] as String?; - final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ?? + + Map? data; + try { + final jsonSource = await read(path: path, password: password); + + data = json.decode(jsonSource) as Map; + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ?? EVMChainERC20Balance(BigInt.zero); + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + return EthereumWallet( walletInfo: walletInfo, password: password, - mnemonic: mnemonic, - privateKey: privateKey, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, initialBalance: balance, client: EthereumClient(), ); diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 760c50a04..55dcea959 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -16,6 +16,7 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_evm/evm_chain_client.dart'; import 'package:cw_evm/evm_chain_exceptions.dart'; @@ -58,7 +59,7 @@ abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet; abstract class EVMChainWalletBase extends WalletBase - with Store { + with Store, WalletKeysFile { EVMChainWalletBase({ required WalletInfo walletInfo, required EVMChainClient client, @@ -508,6 +509,11 @@ abstract class EVMChainWalletBase @override Future save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password); + saveKeysFile(_password, true); + } + await walletAddresses.updateAddressesInBox(); final path = await makePath(); await write(path: path, password: _password, data: toJSON()); @@ -522,7 +528,8 @@ abstract class EVMChainWalletBase ? HEX.encode((evmChainPrivateKey as EthPrivateKey).privateKey) : null; - Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); + @override + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey); String toJSON() => json.encode({ 'mnemonic': _mnemonic, diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml index e4b29b676..c3f4347c2 100644 --- a/cw_evm/pubspec.yaml +++ b/cw_evm/pubspec.yaml @@ -35,7 +35,7 @@ dependency_overrides: ledger_flutter: git: url: https://github.com/cake-tech/ledger-flutter.git - ref: cake + ref: cake-v3 watcher: ^1.1.0 dev_dependencies: diff --git a/cw_haven/pubspec.lock b/cw_haven/pubspec.lock index b8583d219..c34e164f4 100644 --- a/cw_haven/pubspec.lock +++ b/cw_haven/pubspec.lock @@ -254,10 +254,10 @@ packages: 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: @@ -514,14 +514,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" pub_semver: dependency: transitive description: @@ -707,10 +699,10 @@ packages: 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: diff --git a/cw_monero/lib/api/exceptions/connection_to_node_exception.dart b/cw_monero/lib/api/exceptions/connection_to_node_exception.dart deleted file mode 100644 index 483b0a174..000000000 --- a/cw_monero/lib/api/exceptions/connection_to_node_exception.dart +++ /dev/null @@ -1,5 +0,0 @@ -class ConnectionToNodeException implements Exception { - ConnectionToNodeException({required this.message}); - - final String message; -} \ No newline at end of file diff --git a/cw_monero/lib/api/structs/account_row.dart b/cw_monero/lib/api/structs/account_row.dart deleted file mode 100644 index aa492ee0f..000000000 --- a/cw_monero/lib/api/structs/account_row.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class AccountRow extends Struct { - @Int64() - external int id; - - external Pointer label; - - String getLabel() => label.toDartString(); - int getId() => id; -} diff --git a/cw_monero/lib/api/structs/coins_info_row.dart b/cw_monero/lib/api/structs/coins_info_row.dart deleted file mode 100644 index ff6f6ce73..000000000 --- a/cw_monero/lib/api/structs/coins_info_row.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class CoinsInfoRow extends Struct { - @Int64() - external int blockHeight; - - external Pointer hash; - - @Uint64() - external int internalOutputIndex; - - @Uint64() - external int globalOutputIndex; - - @Int8() - external int spent; - - @Int8() - external int frozen; - - @Uint64() - external int spentHeight; - - @Uint64() - external int amount; - - @Int8() - external int rct; - - @Int8() - external int keyImageKnown; - - @Uint64() - external int pkIndex; - - @Uint32() - external int subaddrIndex; - - @Uint32() - external int subaddrAccount; - - external Pointer address; - - external Pointer addressLabel; - - external Pointer keyImage; - - @Uint64() - external int unlockTime; - - @Int8() - external int unlocked; - - external Pointer pubKey; - - @Int8() - external int coinbase; - - external Pointer description; - - String getHash() => hash.toDartString(); - - String getAddress() => address.toDartString(); - - String getAddressLabel() => addressLabel.toDartString(); - - String getKeyImage() => keyImage.toDartString(); - - String getPubKey() => pubKey.toDartString(); - - String getDescription() => description.toDartString(); -} diff --git a/cw_monero/lib/api/structs/subaddress_row.dart b/cw_monero/lib/api/structs/subaddress_row.dart deleted file mode 100644 index d593a793d..000000000 --- a/cw_monero/lib/api/structs/subaddress_row.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class SubaddressRow extends Struct { - @Int64() - external int id; - - external Pointer address; - - external Pointer label; - - String getLabel() => label.toDartString(); - String getAddress() => address.toDartString(); - int getId() => id; -} \ No newline at end of file diff --git a/cw_monero/lib/api/structs/transaction_info_row.dart b/cw_monero/lib/api/structs/transaction_info_row.dart deleted file mode 100644 index bdcc64d3f..000000000 --- a/cw_monero/lib/api/structs/transaction_info_row.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class TransactionInfoRow extends Struct { - @Uint64() - external int amount; - - @Uint64() - external int fee; - - @Uint64() - external int blockHeight; - - @Uint64() - external int confirmations; - - @Uint32() - external int subaddrAccount; - - @Int8() - external int direction; - - @Int8() - external int isPending; - - @Uint32() - external int subaddrIndex; - - external Pointer hash; - - external Pointer paymentId; - - @Int64() - external int datetime; - - int getDatetime() => datetime; - int getAmount() => amount >= 0 ? amount : amount * -1; - bool getIsPending() => isPending != 0; - String getHash() => hash.toDartString(); - String getPaymentId() => paymentId.toDartString(); -} diff --git a/cw_monero/lib/api/structs/ut8_box.dart b/cw_monero/lib/api/structs/ut8_box.dart deleted file mode 100644 index 53e678c88..000000000 --- a/cw_monero/lib/api/structs/ut8_box.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class Utf8Box extends Struct { - external Pointer value; - - String getValue() => value.toDartString(); -} diff --git a/cw_monero/lib/api/subaddress_list.dart b/cw_monero/lib/api/subaddress_list.dart index 57edea76e..e5145692d 100644 --- a/cw_monero/lib/api/subaddress_list.dart +++ b/cw_monero/lib/api/subaddress_list.dart @@ -42,12 +42,16 @@ class Subaddress { List getAllSubaddresses() { final size = monero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex); - return List.generate(size, (index) { + final list = List.generate(size, (index) { return Subaddress( accountIndex: subaddress!.accountIndex, addressIndex: index, ); }).reversed.toList(); + if (list.length == 0) { + list.add(Subaddress(addressIndex: subaddress!.accountIndex, accountIndex: 0)); + } + return list; } 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 5e33c6c56..b416e1b4e 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -1,4 +1,3 @@ - import 'dart:ffi'; import 'dart:isolate'; @@ -110,7 +109,10 @@ Future createTransactionSync( })(); if (error != null) { - final message = error; + String message = error; + if (message.contains("RPC error")) { + message = "Invalid node response, please try again or switch node\n\ntrace: $message"; + } throw CreationTransactionException(message: message); } @@ -285,7 +287,7 @@ class Transaction { }; } - // S finalubAddress? subAddress; + // final SubAddress? subAddress; // List transfers = []; // final int txIndex; final monero.TransactionInfo txInfo; @@ -321,4 +323,4 @@ class Transaction { required this.key, required this.txInfo }); -} \ No newline at end of file +} diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 6ca9cd1bb..0f6e59c4e 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -131,7 +131,7 @@ void storeSync() async { return monero.Wallet_synchronized(Pointer.fromAddress(addr)); }); if (lastStorePointer == wptr!.address && - lastStoreHeight + 5000 < monero.Wallet_blockChainHeight(wptr!) && + lastStoreHeight + 5000 > monero.Wallet_blockChainHeight(wptr!) && !synchronized) { return; } diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 02ce2b7d6..26c83b06e 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -1,4 +1,5 @@ import 'dart:ffi'; +import 'dart:io'; import 'dart:isolate'; import 'package:cw_monero/api/account_list.dart'; @@ -8,8 +9,42 @@ 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/transaction_history.dart'; import 'package:cw_monero/api/wallet.dart'; +import 'package:flutter/foundation.dart'; import 'package:monero/monero.dart' as monero; +class MoneroCException implements Exception { + final String message; + + MoneroCException(this.message); + + @override + String toString() { + return message; + } +} + +void checkIfMoneroCIsFine() { + final cppCsCpp = monero.MONERO_checksum_wallet2_api_c_cpp(); + final cppCsH = monero.MONERO_checksum_wallet2_api_c_h(); + final cppCsExp = monero.MONERO_checksum_wallet2_api_c_exp(); + + final dartCsCpp = monero.wallet2_api_c_cpp_sha256; + final dartCsH = monero.wallet2_api_c_h_sha256; + final dartCsExp = monero.wallet2_api_c_exp_sha256; + + if (cppCsCpp != dartCsCpp) { + throw MoneroCException("monero_c and monero.dart cpp wrapper code mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsCpp'\ndart: '$dartCsCpp'"); + } + + if (cppCsH != dartCsH) { + throw MoneroCException("monero_c and monero.dart cpp wrapper header mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsH'\ndart: '$dartCsH'"); + } + + if (cppCsExp != dartCsExp && (Platform.isIOS || Platform.isMacOS)) { + throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'"); + } +} + monero.WalletManager? _wmPtr; final monero.WalletManager wmPtr = Pointer.fromAddress((() { try { @@ -32,13 +67,14 @@ void createWalletSync( required String language, int nettype = 0}) { txhistory = null; - wptr = monero.WalletManager_createWallet(wmPtr, + final newWptr = monero.WalletManager_createWallet(wmPtr, path: path, password: password, language: language, networkType: 0); - final status = monero.Wallet_status(wptr!); + final status = monero.Wallet_status(newWptr); if (status != 0) { - throw WalletCreationException(message: monero.Wallet_errorString(wptr!)); + throw WalletCreationException(message: monero.Wallet_errorString(newWptr)); } + wptr = newWptr; monero.Wallet_store(wptr!, path: path); openedWalletsByPath[path] = wptr!; @@ -57,7 +93,7 @@ void restoreWalletFromSeedSync( int nettype = 0, int restoreHeight = 0}) { txhistory = null; - wptr = monero.WalletManager_recoveryWallet( + final newWptr = monero.WalletManager_recoveryWallet( wmPtr, path: path, password: password, @@ -67,12 +103,13 @@ void restoreWalletFromSeedSync( networkType: 0, ); - final status = monero.Wallet_status(wptr!); + final status = monero.Wallet_status(newWptr); if (status != 0) { - final error = monero.Wallet_errorString(wptr!); + final error = monero.Wallet_errorString(newWptr); throw WalletRestoreFromSeedException(message: error); } + wptr = newWptr; openedWalletsByPath[path] = wptr!; } @@ -87,7 +124,7 @@ void restoreWalletFromKeysSync( int nettype = 0, int restoreHeight = 0}) { txhistory = null; - wptr = monero.WalletManager_createWalletFromKeys( + final newWptr = monero.WalletManager_createWalletFromKeys( wmPtr, path: path, password: password, @@ -98,12 +135,14 @@ void restoreWalletFromKeysSync( nettype: 0, ); - final status = monero.Wallet_status(wptr!); + final status = monero.Wallet_status(newWptr); if (status != 0) { throw WalletRestoreFromKeysException( - message: monero.Wallet_errorString(wptr!)); + message: monero.Wallet_errorString(newWptr)); } + wptr = newWptr; + openedWalletsByPath[path] = wptr!; } @@ -128,7 +167,7 @@ void restoreWalletFromSpendKeySync( // ); txhistory = null; - wptr = monero.WalletManager_createDeterministicWalletFromSpendKey( + final newWptr = monero.WalletManager_createDeterministicWalletFromSpendKey( wmPtr, path: path, password: password, @@ -138,14 +177,16 @@ void restoreWalletFromSpendKeySync( restoreHeight: restoreHeight, ); - final status = monero.Wallet_status(wptr!); + final status = monero.Wallet_status(newWptr); if (status != 0) { - final err = monero.Wallet_errorString(wptr!); + final err = monero.Wallet_errorString(newWptr); print("err: $err"); throw WalletRestoreFromKeysException(message: err); } + wptr = newWptr; + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); storeSync(); @@ -203,15 +244,16 @@ void loadWallet( }); } txhistory = null; - wptr = monero.WalletManager_openWallet(wmPtr, + final newWptr = monero.WalletManager_openWallet(wmPtr, path: path, password: password); _lastOpenedWallet = path; - final status = monero.Wallet_status(wptr!); + final status = monero.Wallet_status(newWptr); if (status != 0) { - final err = monero.Wallet_errorString(wptr!); + final err = monero.Wallet_errorString(newWptr); print(err); throw WalletOpeningException(message: err); } + wptr = newWptr; openedWalletsByPath[path] = wptr!; } } diff --git a/cw_monero/lib/cw_monero.dart b/cw_monero/lib/cw_monero.dart deleted file mode 100644 index 7945a020e..000000000 --- a/cw_monero/lib/cw_monero.dart +++ /dev/null @@ -1,8 +0,0 @@ - -import 'cw_monero_platform_interface.dart'; - -class CwMonero { - Future getPlatformVersion() { - return CwMoneroPlatform.instance.getPlatformVersion(); - } -} diff --git a/cw_monero/lib/cw_monero_method_channel.dart b/cw_monero/lib/cw_monero_method_channel.dart deleted file mode 100644 index 1cbca9f2c..000000000 --- a/cw_monero/lib/cw_monero_method_channel.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; - -import 'cw_monero_platform_interface.dart'; - -/// An implementation of [CwMoneroPlatform] that uses method channels. -class MethodChannelCwMonero extends CwMoneroPlatform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel('cw_monero'); - - @override - Future getPlatformVersion() async { - final version = await methodChannel.invokeMethod('getPlatformVersion'); - return version; - } -} diff --git a/cw_monero/lib/cw_monero_platform_interface.dart b/cw_monero/lib/cw_monero_platform_interface.dart deleted file mode 100644 index 6c9b20a25..000000000 --- a/cw_monero/lib/cw_monero_platform_interface.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -import 'cw_monero_method_channel.dart'; - -abstract class CwMoneroPlatform extends PlatformInterface { - /// Constructs a CwMoneroPlatform. - CwMoneroPlatform() : super(token: _token); - - static final Object _token = Object(); - - static CwMoneroPlatform _instance = MethodChannelCwMonero(); - - /// The default instance of [CwMoneroPlatform] to use. - /// - /// Defaults to [MethodChannelCwMonero]. - static CwMoneroPlatform get instance => _instance; - - /// Platform-specific implementations should set this with their own - /// platform-specific class that extends [CwMoneroPlatform] when - /// they register themselves. - static set instance(CwMoneroPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - Future getPlatformVersion() { - throw UnimplementedError('platformVersion() has not been implemented.'); - } -} diff --git a/cw_monero/lib/monero_transaction_info.dart b/cw_monero/lib/monero_transaction_info.dart index 596b26812..76064ad11 100644 --- a/cw_monero/lib/monero_transaction_info.dart +++ b/cw_monero/lib/monero_transaction_info.dart @@ -1,8 +1,5 @@ -import 'dart:math'; - import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/monero_amount_format.dart'; -import 'package:cw_monero/api/structs/transaction_info_row.dart'; import 'package:cw_core/parseBoolFromString.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/format_amount.dart'; @@ -37,26 +34,6 @@ class MoneroTransactionInfo extends TransactionInfo { }; } - MoneroTransactionInfo.fromRow(TransactionInfoRow row) - : id = "${row.getHash()}_${row.getAmount()}_${row.subaddrAccount}_${row.subaddrIndex}", - txHash = row.getHash(), - height = row.blockHeight, - direction = parseTransactionDirectionFromInt(row.direction), - date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000), - isPending = row.isPending != 0, - amount = row.getAmount(), - accountIndex = row.subaddrAccount, - addressIndex = row.subaddrIndex, - confirmations = row.confirmations, - key = getTxKey(row.getHash()), - fee = row.fee { - additionalInfo = { - 'key': key, - 'accountIndex': accountIndex, - 'addressIndex': addressIndex - }; - } - final String id; final String txHash; final int height; diff --git a/cw_monero/lib/monero_unspent.dart b/cw_monero/lib/monero_unspent.dart index 65b5c595d..87d8f0b39 100644 --- a/cw_monero/lib/monero_unspent.dart +++ b/cw_monero/lib/monero_unspent.dart @@ -1,5 +1,4 @@ import 'package:cw_core/unspent_transaction_output.dart'; -import 'package:cw_monero/api/structs/coins_info_row.dart'; class MoneroUnspent extends Unspent { MoneroUnspent( @@ -8,13 +7,5 @@ class MoneroUnspent extends Unspent { this.isFrozen = isFrozen; } - factory MoneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow) => MoneroUnspent( - coinsInfoRow.getAddress(), - coinsInfoRow.getHash(), - coinsInfoRow.getKeyImage(), - coinsInfoRow.amount, - coinsInfoRow.frozen == 1, - coinsInfoRow.unlocked == 1); - final bool isUnlocked; } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 4b596648e..9298f8a49 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -19,7 +19,6 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/coins_info.dart'; import 'package:cw_monero/api/monero_output.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; @@ -110,9 +109,7 @@ abstract class MoneroWalletBase extends WalletBase monero_wallet.getSeed(); - String seedLegacy(String? language) { - return monero_wallet.getSeedLegacy(language); - } + String seedLegacy(String? language) => monero_wallet.getSeedLegacy(language); @override MoneroWalletKeys get keys => MoneroWalletKeys( @@ -191,12 +188,12 @@ abstract class MoneroWalletBase extends WalletBase startSync() async { try { - _setInitialHeight(); + _assertInitialHeight(); } catch (_) { // our restore height wasn't correct, so lets see if using the backup works: try { - await resetCache(name); - _setInitialHeight(); + await resetCache(name); // Resetting the cache removes the TX Keys and Polyseed + _assertInitialHeight(); } catch (e) { // we still couldn't get a valid height from the backup?!: // try to use the date instead: @@ -636,18 +633,14 @@ abstract class MoneroWalletBase extends WalletBase MIN_RESTORE_HEIGHT) { - // the restore height is probably correct, so we do nothing: - return; - } + // the restore height is probably correct, so we do nothing: + if (height > MIN_RESTORE_HEIGHT) return; throw Exception("height isn't > $MIN_RESTORE_HEIGHT!"); } diff --git a/cw_monero/lib/monero_wallet_addresses.dart b/cw_monero/lib/monero_wallet_addresses.dart index f74e7dd5b..d4f22e46f 100644 --- a/cw_monero/lib/monero_wallet_addresses.dart +++ b/cw_monero/lib/monero_wallet_addresses.dart @@ -109,7 +109,7 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store { accountIndex: accountIndex, defaultLabel: defaultLabel, usedAddresses: usedAddresses.toList()); - subaddress = subaddressList.subaddresses.last; + subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel) : subaddressList.subaddresses.last; address = subaddress!.address; } diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index ea2f3b766..3588ebb78 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -57,8 +57,11 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials { final String spendKey; } -class MoneroWalletService extends WalletService { +class MoneroWalletService extends WalletService< + MoneroNewWalletCredentials, + MoneroRestoreWalletFromSeedCredentials, + MoneroRestoreWalletFromKeysCredentials, + MoneroNewWalletCredentials> { MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); final Box walletInfoSource; @@ -183,11 +186,8 @@ class MoneroWalletService extends WalletService restoreFromHardwareWallet(MoneroNewWalletCredentials credentials) { - throw UnimplementedError("Restoring a Monero wallet from a hardware wallet is not yet supported!"); + throw UnimplementedError( + "Restoring a Monero wallet from a hardware wallet is not yet supported!"); } @override @@ -350,4 +351,24 @@ class MoneroWalletService extends WalletService getSeeds(String name, String password, WalletType type) async { + try { + final path = await pathForWallet(name: name, type: getType()); + + if (walletFilesExist(path)) { + await repairOldAndroidWallet(name); + } + + await monero_wallet_manager.openWalletAsync({'path': path, 'password': password}); + final walletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource); + return wallet.seed; + } catch (_) { + // if the file couldn't be opened or read + return ''; + } + } } diff --git a/cw_monero/lib/mymonero.dart b/cw_monero/lib/mymonero.dart deleted file mode 100644 index d50e48b64..000000000 --- a/cw_monero/lib/mymonero.dart +++ /dev/null @@ -1,1689 +0,0 @@ -const prefixLength = 3; - -String swapEndianBytes(String original) { - if (original.length != 8) { - return ''; - } - - return original[6] + - original[7] + - original[4] + - original[5] + - original[2] + - original[3] + - original[0] + - original[1]; -} - -List tructWords(List wordSet) { - final start = 0; - final end = prefixLength; - - return wordSet.map((word) => word.substring(start, end)).toList(); -} - -String mnemonicDecode(String seed) { - final n = englistWordSet.length; - var out = ''; - var wlist = seed.split(' '); - wlist.removeLast(); - - for (var i = 0; i < wlist.length; i += 3) { - final w1 = - tructWords(englistWordSet).indexOf(wlist[i].substring(0, prefixLength)); - final w2 = tructWords(englistWordSet) - .indexOf(wlist[i + 1].substring(0, prefixLength)); - final w3 = tructWords(englistWordSet) - .indexOf(wlist[i + 2].substring(0, prefixLength)); - - if (w1 == -1 || w2 == -1 || w3 == -1) { - print("invalid word in mnemonic"); - return ''; - } - - final x = w1 + n * (((n - w1) + w2) % n) + n * n * (((n - w2) + w3) % n); - - if (x % n != w1) { - print("Something went wrong when decoding your private key, please try again"); - return ''; - } - - final _res = '0000000' + x.toRadixString(16); - final start = _res.length - 8; - final end = _res.length; - final res = _res.substring(start, end); - - out += swapEndianBytes(res); - } - - return out; -} - -final englistWordSet = [ - "abbey", - "abducts", - "ability", - "ablaze", - "abnormal", - "abort", - "abrasive", - "absorb", - "abyss", - "academy", - "aces", - "aching", - "acidic", - "acoustic", - "acquire", - "across", - "actress", - "acumen", - "adapt", - "addicted", - "adept", - "adhesive", - "adjust", - "adopt", - "adrenalin", - "adult", - "adventure", - "aerial", - "afar", - "affair", - "afield", - "afloat", - "afoot", - "afraid", - "after", - "against", - "agenda", - "aggravate", - "agile", - "aglow", - "agnostic", - "agony", - "agreed", - "ahead", - "aided", - "ailments", - "aimless", - "airport", - "aisle", - "ajar", - "akin", - "alarms", - "album", - "alchemy", - "alerts", - "algebra", - "alkaline", - "alley", - "almost", - "aloof", - "alpine", - "already", - "also", - "altitude", - "alumni", - "always", - "amaze", - "ambush", - "amended", - "amidst", - "ammo", - "amnesty", - "among", - "amply", - "amused", - "anchor", - "android", - "anecdote", - "angled", - "ankle", - "annoyed", - "answers", - "antics", - "anvil", - "anxiety", - "anybody", - "apart", - "apex", - "aphid", - "aplomb", - "apology", - "apply", - "apricot", - "aptitude", - "aquarium", - "arbitrary", - "archer", - "ardent", - "arena", - "argue", - "arises", - "army", - "around", - "arrow", - "arsenic", - "artistic", - "ascend", - "ashtray", - "aside", - "asked", - "asleep", - "aspire", - "assorted", - "asylum", - "athlete", - "atlas", - "atom", - "atrium", - "attire", - "auburn", - "auctions", - "audio", - "august", - "aunt", - "austere", - "autumn", - "avatar", - "avidly", - "avoid", - "awakened", - "awesome", - "awful", - "awkward", - "awning", - "awoken", - "axes", - "axis", - "axle", - "aztec", - "azure", - "baby", - "bacon", - "badge", - "baffles", - "bagpipe", - "bailed", - "bakery", - "balding", - "bamboo", - "banjo", - "baptism", - "basin", - "batch", - "bawled", - "bays", - "because", - "beer", - "befit", - "begun", - "behind", - "being", - "below", - "bemused", - "benches", - "berries", - "bested", - "betting", - "bevel", - "beware", - "beyond", - "bias", - "bicycle", - "bids", - "bifocals", - "biggest", - "bikini", - "bimonthly", - "binocular", - "biology", - "biplane", - "birth", - "biscuit", - "bite", - "biweekly", - "blender", - "blip", - "bluntly", - "boat", - "bobsled", - "bodies", - "bogeys", - "boil", - "boldly", - "bomb", - "border", - "boss", - "both", - "bounced", - "bovine", - "bowling", - "boxes", - "boyfriend", - "broken", - "brunt", - "bubble", - "buckets", - "budget", - "buffet", - "bugs", - "building", - "bulb", - "bumper", - "bunch", - "business", - "butter", - "buying", - "buzzer", - "bygones", - "byline", - "bypass", - "cabin", - "cactus", - "cadets", - "cafe", - "cage", - "cajun", - "cake", - "calamity", - "camp", - "candy", - "casket", - "catch", - "cause", - "cavernous", - "cease", - "cedar", - "ceiling", - "cell", - "cement", - "cent", - "certain", - "chlorine", - "chrome", - "cider", - "cigar", - "cinema", - "circle", - "cistern", - "citadel", - "civilian", - "claim", - "click", - "clue", - "coal", - "cobra", - "cocoa", - "code", - "coexist", - "coffee", - "cogs", - "cohesive", - "coils", - "colony", - "comb", - "cool", - "copy", - "corrode", - "costume", - "cottage", - "cousin", - "cowl", - "criminal", - "cube", - "cucumber", - "cuddled", - "cuffs", - "cuisine", - "cunning", - "cupcake", - "custom", - "cycling", - "cylinder", - "cynical", - "dabbing", - "dads", - "daft", - "dagger", - "daily", - "damp", - "dangerous", - "dapper", - "darted", - "dash", - "dating", - "dauntless", - "dawn", - "daytime", - "dazed", - "debut", - "decay", - "dedicated", - "deepest", - "deftly", - "degrees", - "dehydrate", - "deity", - "dejected", - "delayed", - "demonstrate", - "dented", - "deodorant", - "depth", - "desk", - "devoid", - "dewdrop", - "dexterity", - "dialect", - "dice", - "diet", - "different", - "digit", - "dilute", - "dime", - "dinner", - "diode", - "diplomat", - "directed", - "distance", - "ditch", - "divers", - "dizzy", - "doctor", - "dodge", - "does", - "dogs", - "doing", - "dolphin", - "domestic", - "donuts", - "doorway", - "dormant", - "dosage", - "dotted", - "double", - "dove", - "down", - "dozen", - "dreams", - "drinks", - "drowning", - "drunk", - "drying", - "dual", - "dubbed", - "duckling", - "dude", - "duets", - "duke", - "dullness", - "dummy", - "dunes", - "duplex", - "duration", - "dusted", - "duties", - "dwarf", - "dwelt", - "dwindling", - "dying", - "dynamite", - "dyslexic", - "each", - "eagle", - "earth", - "easy", - "eating", - "eavesdrop", - "eccentric", - "echo", - "eclipse", - "economics", - "ecstatic", - "eden", - "edgy", - "edited", - "educated", - "eels", - "efficient", - "eggs", - "egotistic", - "eight", - "either", - "eject", - "elapse", - "elbow", - "eldest", - "eleven", - "elite", - "elope", - "else", - "eluded", - "emails", - "ember", - "emerge", - "emit", - "emotion", - "empty", - "emulate", - "energy", - "enforce", - "enhanced", - "enigma", - "enjoy", - "enlist", - "enmity", - "enough", - "enraged", - "ensign", - "entrance", - "envy", - "epoxy", - "equip", - "erase", - "erected", - "erosion", - "error", - "eskimos", - "espionage", - "essential", - "estate", - "etched", - "eternal", - "ethics", - "etiquette", - "evaluate", - "evenings", - "evicted", - "evolved", - "examine", - "excess", - "exhale", - "exit", - "exotic", - "exquisite", - "extra", - "exult", - "fabrics", - "factual", - "fading", - "fainted", - "faked", - "fall", - "family", - "fancy", - "farming", - "fatal", - "faulty", - "fawns", - "faxed", - "fazed", - "feast", - "february", - "federal", - "feel", - "feline", - "females", - "fences", - "ferry", - "festival", - "fetches", - "fever", - "fewest", - "fiat", - "fibula", - "fictional", - "fidget", - "fierce", - "fifteen", - "fight", - "films", - "firm", - "fishing", - "fitting", - "five", - "fixate", - "fizzle", - "fleet", - "flippant", - "flying", - "foamy", - "focus", - "foes", - "foggy", - "foiled", - "folding", - "fonts", - "foolish", - "fossil", - "fountain", - "fowls", - "foxes", - "foyer", - "framed", - "friendly", - "frown", - "fruit", - "frying", - "fudge", - "fuel", - "fugitive", - "fully", - "fuming", - "fungal", - "furnished", - "fuselage", - "future", - "fuzzy", - "gables", - "gadget", - "gags", - "gained", - "galaxy", - "gambit", - "gang", - "gasp", - "gather", - "gauze", - "gave", - "gawk", - "gaze", - "gearbox", - "gecko", - "geek", - "gels", - "gemstone", - "general", - "geometry", - "germs", - "gesture", - "getting", - "geyser", - "ghetto", - "ghost", - "giant", - "giddy", - "gifts", - "gigantic", - "gills", - "gimmick", - "ginger", - "girth", - "giving", - "glass", - "gleeful", - "glide", - "gnaw", - "gnome", - "goat", - "goblet", - "godfather", - "goes", - "goggles", - "going", - "goldfish", - "gone", - "goodbye", - "gopher", - "gorilla", - "gossip", - "gotten", - "gourmet", - "governing", - "gown", - "greater", - "grunt", - "guarded", - "guest", - "guide", - "gulp", - "gumball", - "guru", - "gusts", - "gutter", - "guys", - "gymnast", - "gypsy", - "gyrate", - "habitat", - "hacksaw", - "haggled", - "hairy", - "hamburger", - "happens", - "hashing", - "hatchet", - "haunted", - "having", - "hawk", - "haystack", - "hazard", - "hectare", - "hedgehog", - "heels", - "hefty", - "height", - "hemlock", - "hence", - "heron", - "hesitate", - "hexagon", - "hickory", - "hiding", - "highway", - "hijack", - "hiker", - "hills", - "himself", - "hinder", - "hippo", - "hire", - "history", - "hitched", - "hive", - "hoax", - "hobby", - "hockey", - "hoisting", - "hold", - "honked", - "hookup", - "hope", - "hornet", - "hospital", - "hotel", - "hounded", - "hover", - "howls", - "hubcaps", - "huddle", - "huge", - "hull", - "humid", - "hunter", - "hurried", - "husband", - "huts", - "hybrid", - "hydrogen", - "hyper", - "iceberg", - "icing", - "icon", - "identity", - "idiom", - "idled", - "idols", - "igloo", - "ignore", - "iguana", - "illness", - "imagine", - "imbalance", - "imitate", - "impel", - "inactive", - "inbound", - "incur", - "industrial", - "inexact", - "inflamed", - "ingested", - "initiate", - "injury", - "inkling", - "inline", - "inmate", - "innocent", - "inorganic", - "input", - "inquest", - "inroads", - "insult", - "intended", - "inundate", - "invoke", - "inwardly", - "ionic", - "irate", - "iris", - "irony", - "irritate", - "island", - "isolated", - "issued", - "italics", - "itches", - "items", - "itinerary", - "itself", - "ivory", - "jabbed", - "jackets", - "jaded", - "jagged", - "jailed", - "jamming", - "january", - "jargon", - "jaunt", - "javelin", - "jaws", - "jazz", - "jeans", - "jeers", - "jellyfish", - "jeopardy", - "jerseys", - "jester", - "jetting", - "jewels", - "jigsaw", - "jingle", - "jittery", - "jive", - "jobs", - "jockey", - "jogger", - "joining", - "joking", - "jolted", - "jostle", - "journal", - "joyous", - "jubilee", - "judge", - "juggled", - "juicy", - "jukebox", - "july", - "jump", - "junk", - "jury", - "justice", - "juvenile", - "kangaroo", - "karate", - "keep", - "kennel", - "kept", - "kernels", - "kettle", - "keyboard", - "kickoff", - "kidneys", - "king", - "kiosk", - "kisses", - "kitchens", - "kiwi", - "knapsack", - "knee", - "knife", - "knowledge", - "knuckle", - "koala", - "laboratory", - "ladder", - "lagoon", - "lair", - "lakes", - "lamb", - "language", - "laptop", - "large", - "last", - "later", - "launching", - "lava", - "lawsuit", - "layout", - "lazy", - "lectures", - "ledge", - "leech", - "left", - "legion", - "leisure", - "lemon", - "lending", - "leopard", - "lesson", - "lettuce", - "lexicon", - "liar", - "library", - "licks", - "lids", - "lied", - "lifestyle", - "light", - "likewise", - "lilac", - "limits", - "linen", - "lion", - "lipstick", - "liquid", - "listen", - "lively", - "loaded", - "lobster", - "locker", - "lodge", - "lofty", - "logic", - "loincloth", - "long", - "looking", - "lopped", - "lordship", - "losing", - "lottery", - "loudly", - "love", - "lower", - "loyal", - "lucky", - "luggage", - "lukewarm", - "lullaby", - "lumber", - "lunar", - "lurk", - "lush", - "luxury", - "lymph", - "lynx", - "lyrics", - "macro", - "madness", - "magically", - "mailed", - "major", - "makeup", - "malady", - "mammal", - "maps", - "masterful", - "match", - "maul", - "maverick", - "maximum", - "mayor", - "maze", - "meant", - "mechanic", - "medicate", - "meeting", - "megabyte", - "melting", - "memoir", - "menu", - "merger", - "mesh", - "metro", - "mews", - "mice", - "midst", - "mighty", - "mime", - "mirror", - "misery", - "mittens", - "mixture", - "moat", - "mobile", - "mocked", - "mohawk", - "moisture", - "molten", - "moment", - "money", - "moon", - "mops", - "morsel", - "mostly", - "motherly", - "mouth", - "movement", - "mowing", - "much", - "muddy", - "muffin", - "mugged", - "mullet", - "mumble", - "mundane", - "muppet", - "mural", - "musical", - "muzzle", - "myriad", - "mystery", - "myth", - "nabbing", - "nagged", - "nail", - "names", - "nanny", - "napkin", - "narrate", - "nasty", - "natural", - "nautical", - "navy", - "nearby", - "necklace", - "needed", - "negative", - "neither", - "neon", - "nephew", - "nerves", - "nestle", - "network", - "neutral", - "never", - "newt", - "nexus", - "nibs", - "niche", - "niece", - "nifty", - "nightly", - "nimbly", - "nineteen", - "nirvana", - "nitrogen", - "nobody", - "nocturnal", - "nodes", - "noises", - "nomad", - "noodles", - "northern", - "nostril", - "noted", - "nouns", - "novelty", - "nowhere", - "nozzle", - "nuance", - "nucleus", - "nudged", - "nugget", - "nuisance", - "null", - "number", - "nuns", - "nurse", - "nutshell", - "nylon", - "oaks", - "oars", - "oasis", - "oatmeal", - "obedient", - "object", - "obliged", - "obnoxious", - "observant", - "obtains", - "obvious", - "occur", - "ocean", - "october", - "odds", - "odometer", - "offend", - "often", - "oilfield", - "ointment", - "okay", - "older", - "olive", - "olympics", - "omega", - "omission", - "omnibus", - "onboard", - "oncoming", - "oneself", - "ongoing", - "onion", - "online", - "onslaught", - "onto", - "onward", - "oozed", - "opacity", - "opened", - "opposite", - "optical", - "opus", - "orange", - "orbit", - "orchid", - "orders", - "organs", - "origin", - "ornament", - "orphans", - "oscar", - "ostrich", - "otherwise", - "otter", - "ouch", - "ought", - "ounce", - "ourselves", - "oust", - "outbreak", - "oval", - "oven", - "owed", - "owls", - "owner", - "oxidant", - "oxygen", - "oyster", - "ozone", - "pact", - "paddles", - "pager", - "pairing", - "palace", - "pamphlet", - "pancakes", - "paper", - "paradise", - "pastry", - "patio", - "pause", - "pavements", - "pawnshop", - "payment", - "peaches", - "pebbles", - "peculiar", - "pedantic", - "peeled", - "pegs", - "pelican", - "pencil", - "people", - "pepper", - "perfect", - "pests", - "petals", - "phase", - "pheasants", - "phone", - "phrases", - "physics", - "piano", - "picked", - "pierce", - "pigment", - "piloted", - "pimple", - "pinched", - "pioneer", - "pipeline", - "pirate", - "pistons", - "pitched", - "pivot", - "pixels", - "pizza", - "playful", - "pledge", - "pliers", - "plotting", - "plus", - "plywood", - "poaching", - "pockets", - "podcast", - "poetry", - "point", - "poker", - "polar", - "ponies", - "pool", - "popular", - "portents", - "possible", - "potato", - "pouch", - "poverty", - "powder", - "pram", - "present", - "pride", - "problems", - "pruned", - "prying", - "psychic", - "public", - "puck", - "puddle", - "puffin", - "pulp", - "pumpkins", - "punch", - "puppy", - "purged", - "push", - "putty", - "puzzled", - "pylons", - "pyramid", - "python", - "queen", - "quick", - "quote", - "rabbits", - "racetrack", - "radar", - "rafts", - "rage", - "railway", - "raking", - "rally", - "ramped", - "randomly", - "rapid", - "rarest", - "rash", - "rated", - "ravine", - "rays", - "razor", - "react", - "rebel", - "recipe", - "reduce", - "reef", - "refer", - "regular", - "reheat", - "reinvest", - "rejoices", - "rekindle", - "relic", - "remedy", - "renting", - "reorder", - "repent", - "request", - "reruns", - "rest", - "return", - "reunion", - "revamp", - "rewind", - "rhino", - "rhythm", - "ribbon", - "richly", - "ridges", - "rift", - "rigid", - "rims", - "ringing", - "riots", - "ripped", - "rising", - "ritual", - "river", - "roared", - "robot", - "rockets", - "rodent", - "rogue", - "roles", - "romance", - "roomy", - "roped", - "roster", - "rotate", - "rounded", - "rover", - "rowboat", - "royal", - "ruby", - "rudely", - "ruffled", - "rugged", - "ruined", - "ruling", - "rumble", - "runway", - "rural", - "rustled", - "ruthless", - "sabotage", - "sack", - "sadness", - "safety", - "saga", - "sailor", - "sake", - "salads", - "sample", - "sanity", - "sapling", - "sarcasm", - "sash", - "satin", - "saucepan", - "saved", - "sawmill", - "saxophone", - "sayings", - "scamper", - "scenic", - "school", - "science", - "scoop", - "scrub", - "scuba", - "seasons", - "second", - "sedan", - "seeded", - "segments", - "seismic", - "selfish", - "semifinal", - "sensible", - "september", - "sequence", - "serving", - "session", - "setup", - "seventh", - "sewage", - "shackles", - "shelter", - "shipped", - "shocking", - "shrugged", - "shuffled", - "shyness", - "siblings", - "sickness", - "sidekick", - "sieve", - "sifting", - "sighting", - "silk", - "simplest", - "sincerely", - "sipped", - "siren", - "situated", - "sixteen", - "sizes", - "skater", - "skew", - "skirting", - "skulls", - "skydive", - "slackens", - "sleepless", - "slid", - "slower", - "slug", - "smash", - "smelting", - "smidgen", - "smog", - "smuggled", - "snake", - "sneeze", - "sniff", - "snout", - "snug", - "soapy", - "sober", - "soccer", - "soda", - "software", - "soggy", - "soil", - "solved", - "somewhere", - "sonic", - "soothe", - "soprano", - "sorry", - "southern", - "sovereign", - "sowed", - "soya", - "space", - "speedy", - "sphere", - "spiders", - "splendid", - "spout", - "sprig", - "spud", - "spying", - "square", - "stacking", - "stellar", - "stick", - "stockpile", - "strained", - "stunning", - "stylishly", - "subtly", - "succeed", - "suddenly", - "suede", - "suffice", - "sugar", - "suitcase", - "sulking", - "summon", - "sunken", - "superior", - "surfer", - "sushi", - "suture", - "swagger", - "swept", - "swiftly", - "sword", - "swung", - "syllabus", - "symptoms", - "syndrome", - "syringe", - "system", - "taboo", - "tacit", - "tadpoles", - "tagged", - "tail", - "taken", - "talent", - "tamper", - "tanks", - "tapestry", - "tarnished", - "tasked", - "tattoo", - "taunts", - "tavern", - "tawny", - "taxi", - "teardrop", - "technical", - "tedious", - "teeming", - "tell", - "template", - "tender", - "tepid", - "tequila", - "terminal", - "testing", - "tether", - "textbook", - "thaw", - "theatrics", - "thirsty", - "thorn", - "threaten", - "thumbs", - "thwart", - "ticket", - "tidy", - "tiers", - "tiger", - "tilt", - "timber", - "tinted", - "tipsy", - "tirade", - "tissue", - "titans", - "toaster", - "tobacco", - "today", - "toenail", - "toffee", - "together", - "toilet", - "token", - "tolerant", - "tomorrow", - "tonic", - "toolbox", - "topic", - "torch", - "tossed", - "total", - "touchy", - "towel", - "toxic", - "toyed", - "trash", - "trendy", - "tribal", - "trolling", - "truth", - "trying", - "tsunami", - "tubes", - "tucks", - "tudor", - "tuesday", - "tufts", - "tugs", - "tuition", - "tulips", - "tumbling", - "tunnel", - "turnip", - "tusks", - "tutor", - "tuxedo", - "twang", - "tweezers", - "twice", - "twofold", - "tycoon", - "typist", - "tyrant", - "ugly", - "ulcers", - "ultimate", - "umbrella", - "umpire", - "unafraid", - "unbending", - "uncle", - "under", - "uneven", - "unfit", - "ungainly", - "unhappy", - "union", - "unjustly", - "unknown", - "unlikely", - "unmask", - "unnoticed", - "unopened", - "unplugs", - "unquoted", - "unrest", - "unsafe", - "until", - "unusual", - "unveil", - "unwind", - "unzip", - "upbeat", - "upcoming", - "update", - "upgrade", - "uphill", - "upkeep", - "upload", - "upon", - "upper", - "upright", - "upstairs", - "uptight", - "upwards", - "urban", - "urchins", - "urgent", - "usage", - "useful", - "usher", - "using", - "usual", - "utensils", - "utility", - "utmost", - "utopia", - "uttered", - "vacation", - "vague", - "vain", - "value", - "vampire", - "vane", - "vapidly", - "vary", - "vastness", - "vats", - "vaults", - "vector", - "veered", - "vegan", - "vehicle", - "vein", - "velvet", - "venomous", - "verification", - "vessel", - "veteran", - "vexed", - "vials", - "vibrate", - "victim", - "video", - "viewpoint", - "vigilant", - "viking", - "village", - "vinegar", - "violin", - "vipers", - "virtual", - "visited", - "vitals", - "vivid", - "vixen", - "vocal", - "vogue", - "voice", - "volcano", - "vortex", - "voted", - "voucher", - "vowels", - "voyage", - "vulture", - "wade", - "waffle", - "wagtail", - "waist", - "waking", - "wallets", - "wanted", - "warped", - "washing", - "water", - "waveform", - "waxing", - "wayside", - "weavers", - "website", - "wedge", - "weekday", - "weird", - "welders", - "went", - "wept", - "were", - "western", - "wetsuit", - "whale", - "when", - "whipped", - "whole", - "wickets", - "width", - "wield", - "wife", - "wiggle", - "wildly", - "winter", - "wipeout", - "wiring", - "wise", - "withdrawn", - "wives", - "wizard", - "wobbly", - "woes", - "woken", - "wolf", - "womanly", - "wonders", - "woozy", - "worry", - "wounded", - "woven", - "wrap", - "wrist", - "wrong", - "yacht", - "yahoo", - "yanks", - "yard", - "yawning", - "yearbook", - "yellow", - "yesterday", - "yeti", - "yields", - "yodel", - "yoga", - "younger", - "yoyo", - "zapped", - "zeal", - "zebra", - "zero", - "zesty", - "zigzags", - "zinger", - "zippers", - "zodiac", - "zombie", - "zones", - "zoom" -]; diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 011fed169..38299b2dc 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: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.3" 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: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + 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,42 +246,42 @@ 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: "5037d3b8c36384c03a728543ae67d962a56970c5432a50862279fe68ee4c8411" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.19.1" hashlib_codecs: dependency: transitive description: name: hashlib_codecs - sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626" + sha256: "2b570061f5a4b378425be28a576c1e11783450355ad4345a19f606ff3d96db0f" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.5.0" hive: 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,33 +414,33 @@ 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" monero: dependency: "direct main" description: - path: "." - ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 - resolved-ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 - url: "https://github.com/mrcyjanek/monero.dart" + path: "impls/monero.dart" + ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b + resolved-ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b + url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" mutex: @@ -451,6 +451,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -471,26 +479,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: @@ -503,10 +511,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: @@ -519,26 +527,26 @@ packages: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.5" 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: @@ -555,46 +563,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: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" 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 @@ -604,10 +612,10 @@ packages: dependency: transitive description: name: socks5_proxy - sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a" + sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" source_gen: dependency: transitive description: @@ -692,10 +700,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" unorm_dart: dependency: transitive description: @@ -728,38 +736,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.yaml b/cw_monero/pubspec.yaml index 53e50877f..b5a13a126 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -24,8 +24,9 @@ dependencies: path: ../cw_core monero: git: - url: https://github.com/mrcyjanek/monero.dart - ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 + url: https://github.com/mrcyjanek/monero_c + ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b # monero_c hash + path: impls/monero.dart mutex: ^3.1.0 dev_dependencies: diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index 5efe3006d..55e01d10b 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -1,8 +1,12 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:bip39/bip39.dart' as bip39; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/n2_node.dart'; +import 'package:cw_core/nano_account.dart'; import 'package:cw_core/nano_account_info_response.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/pathForWallet.dart'; @@ -10,23 +14,20 @@ 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/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_nano/file.dart'; -import 'package:cw_core/nano_account.dart'; -import 'package:cw_core/n2_node.dart'; import 'package:cw_nano/nano_balance.dart'; import 'package:cw_nano/nano_client.dart'; import 'package:cw_nano/nano_transaction_credentials.dart'; import 'package:cw_nano/nano_transaction_history.dart'; import 'package:cw_nano/nano_transaction_info.dart'; +import 'package:cw_nano/nano_wallet_addresses.dart'; import 'package:cw_nano/nano_wallet_keys.dart'; import 'package:cw_nano/pending_nano_transaction.dart'; import 'package:mobx/mobx.dart'; -import 'dart:async'; -import 'package:cw_nano/nano_wallet_addresses.dart'; -import 'package:cw_core/wallet_base.dart'; import 'package:nanodart/nanodart.dart'; -import 'package:bip39/bip39.dart' as bip39; import 'package:nanoutil/nanoutil.dart'; part 'nano_wallet.g.dart'; @@ -34,7 +35,8 @@ part 'nano_wallet.g.dart'; class NanoWallet = NanoWalletBase with _$NanoWallet; abstract class NanoWalletBase - extends WalletBase with Store { + extends WalletBase + with Store, WalletKeysFile { NanoWalletBase({ required WalletInfo walletInfo, required String mnemonic, @@ -70,6 +72,7 @@ abstract class NanoWalletBase String? _representativeAddress; int repScore = 100; + bool get isRepOk => repScore >= 90; late final NanoClient _client; @@ -128,14 +131,10 @@ abstract class NanoWalletBase } @override - int calculateEstimatedFee(TransactionPriority priority, int? amount) { - return 0; // always 0 :) - } + int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0; // always 0 :) @override - Future changePassword(String password) { - throw UnimplementedError("changePassword"); - } + Future changePassword(String password) => throw UnimplementedError("changePassword"); @override void close() { @@ -170,9 +169,7 @@ abstract class NanoWalletBase } @override - Future connectToPowNode({required Node node}) async { - _client.connectPow(node); - } + Future connectToPowNode({required Node node}) async => _client.connectPow(node); @override Future createTransaction(Object credentials) async { @@ -296,9 +293,7 @@ abstract class NanoWalletBase } @override - NanoWalletKeys get keys { - return NanoWalletKeys(seedKey: _hexSeed!); - } + NanoWalletKeys get keys => NanoWalletKeys(seedKey: _hexSeed!); @override String? get privateKey => _privateKey!; @@ -312,6 +307,11 @@ abstract class NanoWalletBase @override Future save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password); + saveKeysFile(_password, true); + } + await walletAddresses.updateAddressesInBox(); final path = await makePath(); await write(path: path, password: _password, data: toJSON()); @@ -323,6 +323,9 @@ abstract class NanoWalletBase String get hexSeed => _hexSeed!; + @override + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, altMnemonic: hexSeed); + String get representative => _representativeAddress ?? ""; @action @@ -358,8 +361,6 @@ abstract class NanoWalletBase } } - Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - String toJSON() => json.encode({ 'seedKey': _hexSeed, 'mnemonic': _mnemonic, @@ -373,31 +374,47 @@ abstract class NanoWalletBase required String password, required WalletInfo walletInfo, }) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String; + Map? data = null; + try { + final jsonSource = await read(path: path, password: password); + + data = json.decode(jsonSource) as Map; + } catch (e) { + if (!hasKeysFile) rethrow; + } final balance = NanoBalance.fromRawString( - currentBalance: data['currentBalance'] as String? ?? "0", - receivableBalance: data['receivableBalance'] as String? ?? "0", + currentBalance: data?['currentBalance'] as String? ?? "0", + receivableBalance: data?['receivableBalance'] as String? ?? "0", ); + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String; + final isHexSeed = !mnemonic.contains(' '); + + keysData = WalletKeysData( + mnemonic: isHexSeed ? null : mnemonic, altMnemonic: isHexSeed ? mnemonic : null); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + DerivationType derivationType = DerivationType.nano; - if (data['derivationType'] == "DerivationType.bip39") { + if (data?['derivationType'] == "DerivationType.bip39") { derivationType = DerivationType.bip39; } walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType); - if (walletInfo.derivationInfo!.derivationType == null) { - walletInfo.derivationInfo!.derivationType = derivationType; - } + walletInfo.derivationInfo!.derivationType ??= derivationType; return NanoWallet( walletInfo: walletInfo, password: password, - mnemonic: mnemonic, + mnemonic: keysData.mnemonic!, initialBalance: balance, ); // init() should always be run after this! @@ -435,7 +452,7 @@ abstract class NanoWalletBase _representativeAddress = await _client.getRepFromPrefs(); throw Exception("Failed to get representative address $e"); } - + repScore = await _client.getRepScore(_representativeAddress!); } diff --git a/cw_nano/lib/nano_wallet_service.dart b/cw_nano/lib/nano_wallet_service.dart index a1af3c872..755598705 100644 --- a/cw_nano/lib/nano_wallet_service.dart +++ b/cw_nano/lib/nano_wallet_service.dart @@ -39,7 +39,7 @@ class NanoWalletService extends WalletService=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_nano/pubspec.yaml b/cw_nano/pubspec.yaml index 768c1bb4e..6fae6a895 100644 --- a/cw_nano/pubspec.yaml +++ b/cw_nano/pubspec.yaml @@ -38,6 +38,7 @@ dev_dependencies: dependency_overrides: watcher: ^1.1.0 + build_runner_core: 7.2.7+1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart index 60c7ad2ff..b0b0793b9 100644 --- a/cw_polygon/lib/polygon_wallet.dart +++ b/cw_polygon/lib/polygon_wallet.dart @@ -1,11 +1,12 @@ import 'dart:convert'; -import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_evm/evm_chain_transaction_history.dart'; import 'package:cw_evm/evm_chain_transaction_info.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; @@ -13,9 +14,9 @@ import 'package:cw_evm/evm_chain_wallet.dart'; import 'package:cw_evm/evm_erc20_balance.dart'; import 'package:cw_evm/file.dart'; import 'package:cw_polygon/default_polygon_erc20_tokens.dart'; -import 'package:cw_polygon/polygon_transaction_info.dart'; import 'package:cw_polygon/polygon_client.dart'; import 'package:cw_polygon/polygon_transaction_history.dart'; +import 'package:cw_polygon/polygon_transaction_info.dart'; class PolygonWallet extends EVMChainWallet { PolygonWallet({ @@ -97,19 +98,37 @@ class PolygonWallet extends EVMChainWallet { static Future open( {required String name, required String password, required WalletInfo walletInfo}) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String?; - final privateKey = data['private_key'] as String?; - final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ?? + + Map? data; + try { + final jsonSource = await read(path: path, password: password); + + data = json.decode(jsonSource) as Map; + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ?? EVMChainERC20Balance(BigInt.zero); + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + return PolygonWallet( walletInfo: walletInfo, password: password, - mnemonic: mnemonic, - privateKey: privateKey, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, initialBalance: balance, client: PolygonClient(), ); diff --git a/cw_polygon/lib/polygon_wallet_service.dart b/cw_polygon/lib/polygon_wallet_service.dart index ee84a014e..14baffc44 100644 --- a/cw_polygon/lib/polygon_wallet_service.dart +++ b/cw_polygon/lib/polygon_wallet_service.dart @@ -35,7 +35,6 @@ class PolygonWalletService extends EVMChainWalletService { await wallet.init(); wallet.addInitialTokens(); await wallet.save(); - return wallet; } @@ -83,7 +82,6 @@ class PolygonWalletService extends EVMChainWalletService { await wallet.init(); wallet.addInitialTokens(); await wallet.save(); - return wallet; } diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 38f2864df..23e88fe5e 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -456,7 +456,7 @@ class SolanaWalletClient { funder: ownerKeypair, ); } catch (e) { - throw Exception('Insufficient SOL balance to complete this transaction'); + throw Exception('Insufficient SOL balance to complete this transaction: ${e.toString()}'); } // Input by the user diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 401968698..2b30a204c 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; + import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/node.dart'; @@ -12,6 +13,7 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_solana/default_spl_tokens.dart'; import 'package:cw_solana/file.dart'; import 'package:cw_solana/solana_balance.dart'; @@ -36,7 +38,8 @@ part 'solana_wallet.g.dart'; class SolanaWallet = SolanaWalletBase with _$SolanaWallet; abstract class SolanaWalletBase - extends WalletBase with Store { + extends WalletBase + with Store, WalletKeysFile { SolanaWalletBase({ required WalletInfo walletInfo, String? mnemonic, @@ -121,6 +124,9 @@ abstract class SolanaWalletBase return privateKey; } + @override + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey); + Future init() async { final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}"; @@ -336,6 +342,11 @@ abstract class SolanaWalletBase @override Future save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password); + saveKeysFile(_password, true); + } + await walletAddresses.updateAddressesInBox(); final path = await makePath(); await write(path: path, password: _password, data: toJSON()); @@ -361,8 +372,6 @@ abstract class SolanaWalletBase } } - Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - String toJSON() => json.encode({ 'mnemonic': _mnemonic, 'private_key': _hexPrivateKey, @@ -374,18 +383,36 @@ abstract class SolanaWalletBase required String password, required WalletInfo walletInfo, }) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String?; - final privateKey = data['private_key'] as String?; - final balance = SolanaBalance.fromJSON(data['balance'] as String) ?? SolanaBalance(0.0); + + Map? data; + try { + final jsonSource = await read(path: path, password: password); + + data = json.decode(jsonSource) as Map; + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final balance = SolanaBalance.fromJSON(data?['balance'] as String) ?? SolanaBalance(0.0); + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } return SolanaWallet( walletInfo: walletInfo, password: password, - mnemonic: mnemonic, - privateKey: privateKey, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, initialBalance: balance, ); } diff --git a/cw_solana/pubspec.yaml b/cw_solana/pubspec.yaml index 6b59282b4..6fd5cd97c 100644 --- a/cw_solana/pubspec.yaml +++ b/cw_solana/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - solana: ^0.30.1 + solana: ^0.30.4 cw_core: path: ../cw_core http: ^1.1.0 diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index 96f92e450..3566dcd94 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -16,6 +16,7 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_tron/default_tron_tokens.dart'; import 'package:cw_tron/file.dart'; @@ -37,7 +38,8 @@ part 'tron_wallet.g.dart'; class TronWallet = TronWalletBase with _$TronWallet; abstract class TronWalletBase - extends WalletBase with Store { + extends WalletBase + with Store, WalletKeysFile { TronWalletBase({ required WalletInfo walletInfo, String? mnemonic, @@ -124,18 +126,36 @@ abstract class TronWalletBase required String password, required WalletInfo walletInfo, }) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String?; - final privateKey = data['private_key'] as String?; - final balance = TronBalance.fromJSON(data['balance'] as String) ?? TronBalance(BigInt.zero); + + Map? data; + try { + final jsonSource = await read(path: path, password: password); + + data = json.decode(jsonSource) as Map; + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final balance = TronBalance.fromJSON(data?['balance'] as String) ?? TronBalance(BigInt.zero); + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } return TronWallet( walletInfo: walletInfo, password: password, - mnemonic: mnemonic, - privateKey: privateKey, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, initialBalance: balance, ); } @@ -163,9 +183,7 @@ abstract class TronWalletBase }) async { assert(mnemonic != null || privateKey != null); - if (privateKey != null) { - return TronPrivateKey(privateKey); - } + if (privateKey != null) return TronPrivateKey(privateKey); final seed = bip39.mnemonicToSeed(mnemonic!); @@ -181,14 +199,10 @@ abstract class TronWalletBase int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0; @override - Future changePassword(String password) { - throw UnimplementedError("changePassword"); - } + Future changePassword(String password) => throw UnimplementedError("changePassword"); @override - void close() { - _transactionsUpdateTimer?.cancel(); - } + void close() => _transactionsUpdateTimer?.cancel(); @action @override @@ -335,6 +349,11 @@ abstract class TronWalletBase continue; } + // Filter out spam transaactions that involve receiving TRC10 assets transaction, we deal with TRX and TRC20 transactions + if (transactionModel.contracts?.first.type == "TransferAssetContract") { + continue; + } + String? tokenSymbol; if (transactionModel.contractAddress != null) { final tokenAddress = TronAddress(transactionModel.contractAddress!); @@ -406,12 +425,15 @@ abstract class TronWalletBase Object get keys => throw UnimplementedError("keys"); @override - Future rescan({required int height}) { - throw UnimplementedError("rescan"); - } + Future rescan({required int height}) => throw UnimplementedError("rescan"); @override Future save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password); + saveKeysFile(_password, true); + } + await walletAddresses.updateAddressesInBox(); final path = await makePath(); await write(path: path, password: _password, data: toJSON()); @@ -424,7 +446,8 @@ abstract class TronWalletBase @override String get privateKey => _tronPrivateKey.toHex(); - Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); + @override + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey); String toJSON() => json.encode({ 'mnemonic': _mnemonic, @@ -512,7 +535,7 @@ abstract class TronWalletBase @override Future renameWalletFiles(String newWalletName) async { - String transactionHistoryFileNameForWallet = 'tron_transactions.json'; + const transactionHistoryFileNameForWallet = 'tron_transactions.json'; final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type); final currentWalletFile = File(currentWalletPath); @@ -550,9 +573,7 @@ abstract class TronWalletBase Future signMessage(String message, {String? address}) async => _tronPrivateKey.signPersonalMessage(ascii.encode(message)); - String getTronBase58AddressFromHex(String hexAddress) { - return TronAddress(hexAddress).toAddress(); - } + String getTronBase58AddressFromHex(String hexAddress) => TronAddress(hexAddress).toAddress(); void updateScanProviderUsageState(bool isEnabled) { if (isEnabled) { diff --git a/cw_tron/lib/tron_wallet_service.dart b/cw_tron/lib/tron_wallet_service.dart index c8344d5f4..ba217a265 100644 --- a/cw_tron/lib/tron_wallet_service.dart +++ b/cw_tron/lib/tron_wallet_service.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:bip39/bip39.dart' as bip39; +import 'package:collection/collection.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/transaction_history.dart'; @@ -14,7 +15,6 @@ import 'package:cw_tron/tron_exception.dart'; import 'package:cw_tron/tron_wallet.dart'; import 'package:cw_tron/tron_wallet_creation_credentials.dart'; import 'package:hive/hive.dart'; -import 'package:collection/collection.dart'; class TronWalletService extends WalletService< TronNewWalletCredentials, @@ -153,7 +153,8 @@ class TronWalletService extends WalletService< } @override - Future, TransactionInfo>> restoreFromHardwareWallet(TronNewWalletCredentials credentials) { + Future, TransactionInfo>> + restoreFromHardwareWallet(TronNewWalletCredentials credentials) { // TODO: implement restoreFromHardwareWallet throw UnimplementedError(); } diff --git a/cw_tron/pubspec.yaml b/cw_tron/pubspec.yaml index f27e1b460..e69fd7ca0 100644 --- a/cw_tron/pubspec.yaml +++ b/cw_tron/pubspec.yaml @@ -18,11 +18,11 @@ dependencies: on_chain: git: url: https://github.com/cake-tech/On_chain - ref: cake-update-v1 + ref: cake-update-v2 blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils - ref: cake-update-v1 + ref: cake-update-v2 mobx: ^2.3.0+1 bip39: ^1.0.6 hive: ^2.2.3 diff --git a/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart b/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart deleted file mode 100644 index 483b0a174..000000000 --- a/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart +++ /dev/null @@ -1,5 +0,0 @@ -class ConnectionToNodeException implements Exception { - ConnectionToNodeException({required this.message}); - - final String message; -} \ No newline at end of file diff --git a/cw_wownero/lib/api/structs/account_row.dart b/cw_wownero/lib/api/structs/account_row.dart deleted file mode 100644 index aa492ee0f..000000000 --- a/cw_wownero/lib/api/structs/account_row.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class AccountRow extends Struct { - @Int64() - external int id; - - external Pointer label; - - String getLabel() => label.toDartString(); - int getId() => id; -} diff --git a/cw_wownero/lib/api/structs/coins_info_row.dart b/cw_wownero/lib/api/structs/coins_info_row.dart deleted file mode 100644 index ff6f6ce73..000000000 --- a/cw_wownero/lib/api/structs/coins_info_row.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class CoinsInfoRow extends Struct { - @Int64() - external int blockHeight; - - external Pointer hash; - - @Uint64() - external int internalOutputIndex; - - @Uint64() - external int globalOutputIndex; - - @Int8() - external int spent; - - @Int8() - external int frozen; - - @Uint64() - external int spentHeight; - - @Uint64() - external int amount; - - @Int8() - external int rct; - - @Int8() - external int keyImageKnown; - - @Uint64() - external int pkIndex; - - @Uint32() - external int subaddrIndex; - - @Uint32() - external int subaddrAccount; - - external Pointer address; - - external Pointer addressLabel; - - external Pointer keyImage; - - @Uint64() - external int unlockTime; - - @Int8() - external int unlocked; - - external Pointer pubKey; - - @Int8() - external int coinbase; - - external Pointer description; - - String getHash() => hash.toDartString(); - - String getAddress() => address.toDartString(); - - String getAddressLabel() => addressLabel.toDartString(); - - String getKeyImage() => keyImage.toDartString(); - - String getPubKey() => pubKey.toDartString(); - - String getDescription() => description.toDartString(); -} diff --git a/cw_wownero/lib/api/structs/subaddress_row.dart b/cw_wownero/lib/api/structs/subaddress_row.dart deleted file mode 100644 index d593a793d..000000000 --- a/cw_wownero/lib/api/structs/subaddress_row.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class SubaddressRow extends Struct { - @Int64() - external int id; - - external Pointer address; - - external Pointer label; - - String getLabel() => label.toDartString(); - String getAddress() => address.toDartString(); - int getId() => id; -} \ No newline at end of file diff --git a/cw_wownero/lib/api/structs/transaction_info_row.dart b/cw_wownero/lib/api/structs/transaction_info_row.dart deleted file mode 100644 index bdcc64d3f..000000000 --- a/cw_wownero/lib/api/structs/transaction_info_row.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class TransactionInfoRow extends Struct { - @Uint64() - external int amount; - - @Uint64() - external int fee; - - @Uint64() - external int blockHeight; - - @Uint64() - external int confirmations; - - @Uint32() - external int subaddrAccount; - - @Int8() - external int direction; - - @Int8() - external int isPending; - - @Uint32() - external int subaddrIndex; - - external Pointer hash; - - external Pointer paymentId; - - @Int64() - external int datetime; - - int getDatetime() => datetime; - int getAmount() => amount >= 0 ? amount : amount * -1; - bool getIsPending() => isPending != 0; - String getHash() => hash.toDartString(); - String getPaymentId() => paymentId.toDartString(); -} diff --git a/cw_wownero/lib/api/structs/ut8_box.dart b/cw_wownero/lib/api/structs/ut8_box.dart deleted file mode 100644 index 53e678c88..000000000 --- a/cw_wownero/lib/api/structs/ut8_box.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class Utf8Box extends Struct { - external Pointer value; - - String getValue() => value.toDartString(); -} diff --git a/cw_wownero/lib/api/subaddress_list.dart b/cw_wownero/lib/api/subaddress_list.dart index cec7d94cb..d8c91a584 100644 --- a/cw_wownero/lib/api/subaddress_list.dart +++ b/cw_wownero/lib/api/subaddress_list.dart @@ -41,12 +41,16 @@ class Subaddress { List getAllSubaddresses() { final size = wownero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex); - return List.generate(size, (index) { + final list = List.generate(size, (index) { return Subaddress( accountIndex: subaddress!.accountIndex, addressIndex: index, ); }).reversed.toList(); + if (list.isEmpty) { + list.add(Subaddress(addressIndex: 0, accountIndex: subaddress!.accountIndex)); + } + return list; } void addSubaddressSync({required int accountIndex, required String label}) { diff --git a/cw_wownero/lib/api/transaction_history.dart b/cw_wownero/lib/api/transaction_history.dart index 3ccd0b3c6..a1e1e3c9b 100644 --- a/cw_wownero/lib/api/transaction_history.dart +++ b/cw_wownero/lib/api/transaction_history.dart @@ -285,7 +285,7 @@ class Transaction { }; } - // S finalubAddress? subAddress; + // final SubAddress? subAddress; // List transfers = []; // final int txIndex; final wownero.TransactionInfo txInfo; @@ -321,4 +321,4 @@ class Transaction { required this.key, required this.txInfo }); -} \ No newline at end of file +} diff --git a/cw_wownero/lib/api/wallet_manager.dart b/cw_wownero/lib/api/wallet_manager.dart index 68d0796f9..7915373bb 100644 --- a/cw_wownero/lib/api/wallet_manager.dart +++ b/cw_wownero/lib/api/wallet_manager.dart @@ -1,4 +1,5 @@ import 'dart:ffi'; +import 'dart:io'; import 'dart:isolate'; import 'package:cw_wownero/api/account_list.dart'; @@ -8,8 +9,42 @@ import 'package:cw_wownero/api/exceptions/wallet_restore_from_keys_exception.dar import 'package:cw_wownero/api/exceptions/wallet_restore_from_seed_exception.dart'; import 'package:cw_wownero/api/transaction_history.dart'; import 'package:cw_wownero/api/wallet.dart'; +import 'package:flutter/foundation.dart'; import 'package:monero/wownero.dart' as wownero; +class MoneroCException implements Exception { + final String message; + + MoneroCException(this.message); + + @override + String toString() { + return message; + } +} + +void checkIfMoneroCIsFine() { + final cppCsCpp = wownero.WOWNERO_checksum_wallet2_api_c_cpp(); + final cppCsH = wownero.WOWNERO_checksum_wallet2_api_c_h(); + final cppCsExp = wownero.WOWNERO_checksum_wallet2_api_c_exp(); + + final dartCsCpp = wownero.wallet2_api_c_cpp_sha256; + final dartCsH = wownero.wallet2_api_c_h_sha256; + final dartCsExp = wownero.wallet2_api_c_exp_sha256; + + if (cppCsCpp != dartCsCpp) { + throw MoneroCException("monero_c and monero.dart cpp wrapper code mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsCpp'\ndart: '$dartCsCpp'"); + } + + if (cppCsH != dartCsH) { + throw MoneroCException("monero_c and monero.dart cpp wrapper header mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsH'\ndart: '$dartCsH'"); + } + + if (cppCsExp != dartCsExp && (Platform.isIOS || Platform.isMacOS)) { + throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'"); + } +} + wownero.WalletManager? _wmPtr; final wownero.WalletManager wmPtr = Pointer.fromAddress((() { try { @@ -32,13 +67,14 @@ void createWalletSync( required String language, int nettype = 0}) { txhistory = null; - wptr = wownero.WalletManager_createWallet(wmPtr, + final newWptr = wownero.WalletManager_createWallet(wmPtr, path: path, password: password, language: language, networkType: 0); - final status = wownero.Wallet_status(wptr!); + final status = wownero.Wallet_status(newWptr); if (status != 0) { - throw WalletCreationException(message: wownero.Wallet_errorString(wptr!)); + throw WalletCreationException(message: wownero.Wallet_errorString(newWptr)); } + wptr = newWptr; wownero.Wallet_store(wptr!, path: path); openedWalletsByPath[path] = wptr!; @@ -56,9 +92,10 @@ void restoreWalletFromSeedSync( required String seed, int nettype = 0, int restoreHeight = 0}) { + var newWptr; if (seed.split(" ").length == 14) { txhistory = null; - wptr = wownero.WOWNERO_deprecated_restore14WordSeed( + newWptr = wownero.WOWNERO_deprecated_restore14WordSeed( path: path, password: password, language: seed, // I KNOW - this is supposed to be called seed @@ -70,7 +107,7 @@ void restoreWalletFromSeedSync( ); } else { txhistory = null; - wptr = wownero.WalletManager_recoveryWallet( + newWptr = wownero.WalletManager_recoveryWallet( wmPtr, path: path, password: password, @@ -81,13 +118,15 @@ void restoreWalletFromSeedSync( ); } - final status = wownero.Wallet_status(wptr!); + final status = wownero.Wallet_status(newWptr); if (status != 0) { - final error = wownero.Wallet_errorString(wptr!); + final error = wownero.Wallet_errorString(newWptr); throw WalletRestoreFromSeedException(message: error); } + wptr = newWptr; + openedWalletsByPath[path] = wptr!; } @@ -101,7 +140,7 @@ void restoreWalletFromKeysSync( int nettype = 0, int restoreHeight = 0}) { txhistory = null; - wptr = wownero.WalletManager_createWalletFromKeys( + final newWptr = wownero.WalletManager_createWalletFromKeys( wmPtr, path: path, password: password, @@ -112,12 +151,14 @@ void restoreWalletFromKeysSync( nettype: 0, ); - final status = wownero.Wallet_status(wptr!); + final status = wownero.Wallet_status(newWptr); if (status != 0) { throw WalletRestoreFromKeysException( - message: wownero.Wallet_errorString(wptr!)); + message: wownero.Wallet_errorString(newWptr)); } + wptr = newWptr; + openedWalletsByPath[path] = wptr!; } @@ -142,7 +183,7 @@ void restoreWalletFromSpendKeySync( // ); txhistory = null; - wptr = wownero.WalletManager_createDeterministicWalletFromSpendKey( + final newWptr = wownero.WalletManager_createDeterministicWalletFromSpendKey( wmPtr, path: path, password: password, @@ -152,14 +193,16 @@ void restoreWalletFromSpendKeySync( restoreHeight: restoreHeight, ); - final status = wownero.Wallet_status(wptr!); + final status = wownero.Wallet_status(newWptr); if (status != 0) { - final err = wownero.Wallet_errorString(wptr!); + final err = wownero.Wallet_errorString(newWptr); print("err: $err"); throw WalletRestoreFromKeysException(message: err); } + wptr = newWptr; + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); storeSync(); @@ -217,15 +260,16 @@ void loadWallet( }); } txhistory = null; - wptr = wownero.WalletManager_openWallet(wmPtr, + final newWptr = wownero.WalletManager_openWallet(wmPtr, path: path, password: password); _lastOpenedWallet = path; - final status = wownero.Wallet_status(wptr!); + final status = wownero.Wallet_status(newWptr); if (status != 0) { - final err = wownero.Wallet_errorString(wptr!); + final err = wownero.Wallet_errorString(newWptr); print(err); throw WalletOpeningException(message: err); } + wptr = newWptr; openedWalletsByPath[path] = wptr!; } } diff --git a/cw_wownero/lib/cw_wownero.dart b/cw_wownero/lib/cw_wownero.dart deleted file mode 100644 index 33a55e305..000000000 --- a/cw_wownero/lib/cw_wownero.dart +++ /dev/null @@ -1,8 +0,0 @@ - -import 'cw_wownero_platform_interface.dart'; - -class CwWownero { - Future getPlatformVersion() { - return CwWowneroPlatform.instance.getPlatformVersion(); - } -} diff --git a/cw_wownero/lib/cw_wownero_method_channel.dart b/cw_wownero/lib/cw_wownero_method_channel.dart deleted file mode 100644 index d797f5f81..000000000 --- a/cw_wownero/lib/cw_wownero_method_channel.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; - -import 'cw_wownero_platform_interface.dart'; - -/// An implementation of [CwWowneroPlatform] that uses method channels. -class MethodChannelCwWownero extends CwWowneroPlatform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel('cw_wownero'); - - @override - Future getPlatformVersion() async { - final version = await methodChannel.invokeMethod('getPlatformVersion'); - return version; - } -} diff --git a/cw_wownero/lib/cw_wownero_platform_interface.dart b/cw_wownero/lib/cw_wownero_platform_interface.dart deleted file mode 100644 index 78b21592c..000000000 --- a/cw_wownero/lib/cw_wownero_platform_interface.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -import 'cw_wownero_method_channel.dart'; - -abstract class CwWowneroPlatform extends PlatformInterface { - /// Constructs a CwWowneroPlatform. - CwWowneroPlatform() : super(token: _token); - - static final Object _token = Object(); - - static CwWowneroPlatform _instance = MethodChannelCwWownero(); - - /// The default instance of [CwWowneroPlatform] to use. - /// - /// Defaults to [MethodChannelCwWownero]. - static CwWowneroPlatform get instance => _instance; - - /// Platform-specific implementations should set this with their own - /// platform-specific class that extends [CwWowneroPlatform] when - /// they register themselves. - static set instance(CwWowneroPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - Future getPlatformVersion() { - throw UnimplementedError('platformVersion() has not been implemented.'); - } -} diff --git a/cw_wownero/lib/mywownero.dart b/cw_wownero/lib/mywownero.dart deleted file mode 100644 index d50e48b64..000000000 --- a/cw_wownero/lib/mywownero.dart +++ /dev/null @@ -1,1689 +0,0 @@ -const prefixLength = 3; - -String swapEndianBytes(String original) { - if (original.length != 8) { - return ''; - } - - return original[6] + - original[7] + - original[4] + - original[5] + - original[2] + - original[3] + - original[0] + - original[1]; -} - -List tructWords(List wordSet) { - final start = 0; - final end = prefixLength; - - return wordSet.map((word) => word.substring(start, end)).toList(); -} - -String mnemonicDecode(String seed) { - final n = englistWordSet.length; - var out = ''; - var wlist = seed.split(' '); - wlist.removeLast(); - - for (var i = 0; i < wlist.length; i += 3) { - final w1 = - tructWords(englistWordSet).indexOf(wlist[i].substring(0, prefixLength)); - final w2 = tructWords(englistWordSet) - .indexOf(wlist[i + 1].substring(0, prefixLength)); - final w3 = tructWords(englistWordSet) - .indexOf(wlist[i + 2].substring(0, prefixLength)); - - if (w1 == -1 || w2 == -1 || w3 == -1) { - print("invalid word in mnemonic"); - return ''; - } - - final x = w1 + n * (((n - w1) + w2) % n) + n * n * (((n - w2) + w3) % n); - - if (x % n != w1) { - print("Something went wrong when decoding your private key, please try again"); - return ''; - } - - final _res = '0000000' + x.toRadixString(16); - final start = _res.length - 8; - final end = _res.length; - final res = _res.substring(start, end); - - out += swapEndianBytes(res); - } - - return out; -} - -final englistWordSet = [ - "abbey", - "abducts", - "ability", - "ablaze", - "abnormal", - "abort", - "abrasive", - "absorb", - "abyss", - "academy", - "aces", - "aching", - "acidic", - "acoustic", - "acquire", - "across", - "actress", - "acumen", - "adapt", - "addicted", - "adept", - "adhesive", - "adjust", - "adopt", - "adrenalin", - "adult", - "adventure", - "aerial", - "afar", - "affair", - "afield", - "afloat", - "afoot", - "afraid", - "after", - "against", - "agenda", - "aggravate", - "agile", - "aglow", - "agnostic", - "agony", - "agreed", - "ahead", - "aided", - "ailments", - "aimless", - "airport", - "aisle", - "ajar", - "akin", - "alarms", - "album", - "alchemy", - "alerts", - "algebra", - "alkaline", - "alley", - "almost", - "aloof", - "alpine", - "already", - "also", - "altitude", - "alumni", - "always", - "amaze", - "ambush", - "amended", - "amidst", - "ammo", - "amnesty", - "among", - "amply", - "amused", - "anchor", - "android", - "anecdote", - "angled", - "ankle", - "annoyed", - "answers", - "antics", - "anvil", - "anxiety", - "anybody", - "apart", - "apex", - "aphid", - "aplomb", - "apology", - "apply", - "apricot", - "aptitude", - "aquarium", - "arbitrary", - "archer", - "ardent", - "arena", - "argue", - "arises", - "army", - "around", - "arrow", - "arsenic", - "artistic", - "ascend", - "ashtray", - "aside", - "asked", - "asleep", - "aspire", - "assorted", - "asylum", - "athlete", - "atlas", - "atom", - "atrium", - "attire", - "auburn", - "auctions", - "audio", - "august", - "aunt", - "austere", - "autumn", - "avatar", - "avidly", - "avoid", - "awakened", - "awesome", - "awful", - "awkward", - "awning", - "awoken", - "axes", - "axis", - "axle", - "aztec", - "azure", - "baby", - "bacon", - "badge", - "baffles", - "bagpipe", - "bailed", - "bakery", - "balding", - "bamboo", - "banjo", - "baptism", - "basin", - "batch", - "bawled", - "bays", - "because", - "beer", - "befit", - "begun", - "behind", - "being", - "below", - "bemused", - "benches", - "berries", - "bested", - "betting", - "bevel", - "beware", - "beyond", - "bias", - "bicycle", - "bids", - "bifocals", - "biggest", - "bikini", - "bimonthly", - "binocular", - "biology", - "biplane", - "birth", - "biscuit", - "bite", - "biweekly", - "blender", - "blip", - "bluntly", - "boat", - "bobsled", - "bodies", - "bogeys", - "boil", - "boldly", - "bomb", - "border", - "boss", - "both", - "bounced", - "bovine", - "bowling", - "boxes", - "boyfriend", - "broken", - "brunt", - "bubble", - "buckets", - "budget", - "buffet", - "bugs", - "building", - "bulb", - "bumper", - "bunch", - "business", - "butter", - "buying", - "buzzer", - "bygones", - "byline", - "bypass", - "cabin", - "cactus", - "cadets", - "cafe", - "cage", - "cajun", - "cake", - "calamity", - "camp", - "candy", - "casket", - "catch", - "cause", - "cavernous", - "cease", - "cedar", - "ceiling", - "cell", - "cement", - "cent", - "certain", - "chlorine", - "chrome", - "cider", - "cigar", - "cinema", - "circle", - "cistern", - "citadel", - "civilian", - "claim", - "click", - "clue", - "coal", - "cobra", - "cocoa", - "code", - "coexist", - "coffee", - "cogs", - "cohesive", - "coils", - "colony", - "comb", - "cool", - "copy", - "corrode", - "costume", - "cottage", - "cousin", - "cowl", - "criminal", - "cube", - "cucumber", - "cuddled", - "cuffs", - "cuisine", - "cunning", - "cupcake", - "custom", - "cycling", - "cylinder", - "cynical", - "dabbing", - "dads", - "daft", - "dagger", - "daily", - "damp", - "dangerous", - "dapper", - "darted", - "dash", - "dating", - "dauntless", - "dawn", - "daytime", - "dazed", - "debut", - "decay", - "dedicated", - "deepest", - "deftly", - "degrees", - "dehydrate", - "deity", - "dejected", - "delayed", - "demonstrate", - "dented", - "deodorant", - "depth", - "desk", - "devoid", - "dewdrop", - "dexterity", - "dialect", - "dice", - "diet", - "different", - "digit", - "dilute", - "dime", - "dinner", - "diode", - "diplomat", - "directed", - "distance", - "ditch", - "divers", - "dizzy", - "doctor", - "dodge", - "does", - "dogs", - "doing", - "dolphin", - "domestic", - "donuts", - "doorway", - "dormant", - "dosage", - "dotted", - "double", - "dove", - "down", - "dozen", - "dreams", - "drinks", - "drowning", - "drunk", - "drying", - "dual", - "dubbed", - "duckling", - "dude", - "duets", - "duke", - "dullness", - "dummy", - "dunes", - "duplex", - "duration", - "dusted", - "duties", - "dwarf", - "dwelt", - "dwindling", - "dying", - "dynamite", - "dyslexic", - "each", - "eagle", - "earth", - "easy", - "eating", - "eavesdrop", - "eccentric", - "echo", - "eclipse", - "economics", - "ecstatic", - "eden", - "edgy", - "edited", - "educated", - "eels", - "efficient", - "eggs", - "egotistic", - "eight", - "either", - "eject", - "elapse", - "elbow", - "eldest", - "eleven", - "elite", - "elope", - "else", - "eluded", - "emails", - "ember", - "emerge", - "emit", - "emotion", - "empty", - "emulate", - "energy", - "enforce", - "enhanced", - "enigma", - "enjoy", - "enlist", - "enmity", - "enough", - "enraged", - "ensign", - "entrance", - "envy", - "epoxy", - "equip", - "erase", - "erected", - "erosion", - "error", - "eskimos", - "espionage", - "essential", - "estate", - "etched", - "eternal", - "ethics", - "etiquette", - "evaluate", - "evenings", - "evicted", - "evolved", - "examine", - "excess", - "exhale", - "exit", - "exotic", - "exquisite", - "extra", - "exult", - "fabrics", - "factual", - "fading", - "fainted", - "faked", - "fall", - "family", - "fancy", - "farming", - "fatal", - "faulty", - "fawns", - "faxed", - "fazed", - "feast", - "february", - "federal", - "feel", - "feline", - "females", - "fences", - "ferry", - "festival", - "fetches", - "fever", - "fewest", - "fiat", - "fibula", - "fictional", - "fidget", - "fierce", - "fifteen", - "fight", - "films", - "firm", - "fishing", - "fitting", - "five", - "fixate", - "fizzle", - "fleet", - "flippant", - "flying", - "foamy", - "focus", - "foes", - "foggy", - "foiled", - "folding", - "fonts", - "foolish", - "fossil", - "fountain", - "fowls", - "foxes", - "foyer", - "framed", - "friendly", - "frown", - "fruit", - "frying", - "fudge", - "fuel", - "fugitive", - "fully", - "fuming", - "fungal", - "furnished", - "fuselage", - "future", - "fuzzy", - "gables", - "gadget", - "gags", - "gained", - "galaxy", - "gambit", - "gang", - "gasp", - "gather", - "gauze", - "gave", - "gawk", - "gaze", - "gearbox", - "gecko", - "geek", - "gels", - "gemstone", - "general", - "geometry", - "germs", - "gesture", - "getting", - "geyser", - "ghetto", - "ghost", - "giant", - "giddy", - "gifts", - "gigantic", - "gills", - "gimmick", - "ginger", - "girth", - "giving", - "glass", - "gleeful", - "glide", - "gnaw", - "gnome", - "goat", - "goblet", - "godfather", - "goes", - "goggles", - "going", - "goldfish", - "gone", - "goodbye", - "gopher", - "gorilla", - "gossip", - "gotten", - "gourmet", - "governing", - "gown", - "greater", - "grunt", - "guarded", - "guest", - "guide", - "gulp", - "gumball", - "guru", - "gusts", - "gutter", - "guys", - "gymnast", - "gypsy", - "gyrate", - "habitat", - "hacksaw", - "haggled", - "hairy", - "hamburger", - "happens", - "hashing", - "hatchet", - "haunted", - "having", - "hawk", - "haystack", - "hazard", - "hectare", - "hedgehog", - "heels", - "hefty", - "height", - "hemlock", - "hence", - "heron", - "hesitate", - "hexagon", - "hickory", - "hiding", - "highway", - "hijack", - "hiker", - "hills", - "himself", - "hinder", - "hippo", - "hire", - "history", - "hitched", - "hive", - "hoax", - "hobby", - "hockey", - "hoisting", - "hold", - "honked", - "hookup", - "hope", - "hornet", - "hospital", - "hotel", - "hounded", - "hover", - "howls", - "hubcaps", - "huddle", - "huge", - "hull", - "humid", - "hunter", - "hurried", - "husband", - "huts", - "hybrid", - "hydrogen", - "hyper", - "iceberg", - "icing", - "icon", - "identity", - "idiom", - "idled", - "idols", - "igloo", - "ignore", - "iguana", - "illness", - "imagine", - "imbalance", - "imitate", - "impel", - "inactive", - "inbound", - "incur", - "industrial", - "inexact", - "inflamed", - "ingested", - "initiate", - "injury", - "inkling", - "inline", - "inmate", - "innocent", - "inorganic", - "input", - "inquest", - "inroads", - "insult", - "intended", - "inundate", - "invoke", - "inwardly", - "ionic", - "irate", - "iris", - "irony", - "irritate", - "island", - "isolated", - "issued", - "italics", - "itches", - "items", - "itinerary", - "itself", - "ivory", - "jabbed", - "jackets", - "jaded", - "jagged", - "jailed", - "jamming", - "january", - "jargon", - "jaunt", - "javelin", - "jaws", - "jazz", - "jeans", - "jeers", - "jellyfish", - "jeopardy", - "jerseys", - "jester", - "jetting", - "jewels", - "jigsaw", - "jingle", - "jittery", - "jive", - "jobs", - "jockey", - "jogger", - "joining", - "joking", - "jolted", - "jostle", - "journal", - "joyous", - "jubilee", - "judge", - "juggled", - "juicy", - "jukebox", - "july", - "jump", - "junk", - "jury", - "justice", - "juvenile", - "kangaroo", - "karate", - "keep", - "kennel", - "kept", - "kernels", - "kettle", - "keyboard", - "kickoff", - "kidneys", - "king", - "kiosk", - "kisses", - "kitchens", - "kiwi", - "knapsack", - "knee", - "knife", - "knowledge", - "knuckle", - "koala", - "laboratory", - "ladder", - "lagoon", - "lair", - "lakes", - "lamb", - "language", - "laptop", - "large", - "last", - "later", - "launching", - "lava", - "lawsuit", - "layout", - "lazy", - "lectures", - "ledge", - "leech", - "left", - "legion", - "leisure", - "lemon", - "lending", - "leopard", - "lesson", - "lettuce", - "lexicon", - "liar", - "library", - "licks", - "lids", - "lied", - "lifestyle", - "light", - "likewise", - "lilac", - "limits", - "linen", - "lion", - "lipstick", - "liquid", - "listen", - "lively", - "loaded", - "lobster", - "locker", - "lodge", - "lofty", - "logic", - "loincloth", - "long", - "looking", - "lopped", - "lordship", - "losing", - "lottery", - "loudly", - "love", - "lower", - "loyal", - "lucky", - "luggage", - "lukewarm", - "lullaby", - "lumber", - "lunar", - "lurk", - "lush", - "luxury", - "lymph", - "lynx", - "lyrics", - "macro", - "madness", - "magically", - "mailed", - "major", - "makeup", - "malady", - "mammal", - "maps", - "masterful", - "match", - "maul", - "maverick", - "maximum", - "mayor", - "maze", - "meant", - "mechanic", - "medicate", - "meeting", - "megabyte", - "melting", - "memoir", - "menu", - "merger", - "mesh", - "metro", - "mews", - "mice", - "midst", - "mighty", - "mime", - "mirror", - "misery", - "mittens", - "mixture", - "moat", - "mobile", - "mocked", - "mohawk", - "moisture", - "molten", - "moment", - "money", - "moon", - "mops", - "morsel", - "mostly", - "motherly", - "mouth", - "movement", - "mowing", - "much", - "muddy", - "muffin", - "mugged", - "mullet", - "mumble", - "mundane", - "muppet", - "mural", - "musical", - "muzzle", - "myriad", - "mystery", - "myth", - "nabbing", - "nagged", - "nail", - "names", - "nanny", - "napkin", - "narrate", - "nasty", - "natural", - "nautical", - "navy", - "nearby", - "necklace", - "needed", - "negative", - "neither", - "neon", - "nephew", - "nerves", - "nestle", - "network", - "neutral", - "never", - "newt", - "nexus", - "nibs", - "niche", - "niece", - "nifty", - "nightly", - "nimbly", - "nineteen", - "nirvana", - "nitrogen", - "nobody", - "nocturnal", - "nodes", - "noises", - "nomad", - "noodles", - "northern", - "nostril", - "noted", - "nouns", - "novelty", - "nowhere", - "nozzle", - "nuance", - "nucleus", - "nudged", - "nugget", - "nuisance", - "null", - "number", - "nuns", - "nurse", - "nutshell", - "nylon", - "oaks", - "oars", - "oasis", - "oatmeal", - "obedient", - "object", - "obliged", - "obnoxious", - "observant", - "obtains", - "obvious", - "occur", - "ocean", - "october", - "odds", - "odometer", - "offend", - "often", - "oilfield", - "ointment", - "okay", - "older", - "olive", - "olympics", - "omega", - "omission", - "omnibus", - "onboard", - "oncoming", - "oneself", - "ongoing", - "onion", - "online", - "onslaught", - "onto", - "onward", - "oozed", - "opacity", - "opened", - "opposite", - "optical", - "opus", - "orange", - "orbit", - "orchid", - "orders", - "organs", - "origin", - "ornament", - "orphans", - "oscar", - "ostrich", - "otherwise", - "otter", - "ouch", - "ought", - "ounce", - "ourselves", - "oust", - "outbreak", - "oval", - "oven", - "owed", - "owls", - "owner", - "oxidant", - "oxygen", - "oyster", - "ozone", - "pact", - "paddles", - "pager", - "pairing", - "palace", - "pamphlet", - "pancakes", - "paper", - "paradise", - "pastry", - "patio", - "pause", - "pavements", - "pawnshop", - "payment", - "peaches", - "pebbles", - "peculiar", - "pedantic", - "peeled", - "pegs", - "pelican", - "pencil", - "people", - "pepper", - "perfect", - "pests", - "petals", - "phase", - "pheasants", - "phone", - "phrases", - "physics", - "piano", - "picked", - "pierce", - "pigment", - "piloted", - "pimple", - "pinched", - "pioneer", - "pipeline", - "pirate", - "pistons", - "pitched", - "pivot", - "pixels", - "pizza", - "playful", - "pledge", - "pliers", - "plotting", - "plus", - "plywood", - "poaching", - "pockets", - "podcast", - "poetry", - "point", - "poker", - "polar", - "ponies", - "pool", - "popular", - "portents", - "possible", - "potato", - "pouch", - "poverty", - "powder", - "pram", - "present", - "pride", - "problems", - "pruned", - "prying", - "psychic", - "public", - "puck", - "puddle", - "puffin", - "pulp", - "pumpkins", - "punch", - "puppy", - "purged", - "push", - "putty", - "puzzled", - "pylons", - "pyramid", - "python", - "queen", - "quick", - "quote", - "rabbits", - "racetrack", - "radar", - "rafts", - "rage", - "railway", - "raking", - "rally", - "ramped", - "randomly", - "rapid", - "rarest", - "rash", - "rated", - "ravine", - "rays", - "razor", - "react", - "rebel", - "recipe", - "reduce", - "reef", - "refer", - "regular", - "reheat", - "reinvest", - "rejoices", - "rekindle", - "relic", - "remedy", - "renting", - "reorder", - "repent", - "request", - "reruns", - "rest", - "return", - "reunion", - "revamp", - "rewind", - "rhino", - "rhythm", - "ribbon", - "richly", - "ridges", - "rift", - "rigid", - "rims", - "ringing", - "riots", - "ripped", - "rising", - "ritual", - "river", - "roared", - "robot", - "rockets", - "rodent", - "rogue", - "roles", - "romance", - "roomy", - "roped", - "roster", - "rotate", - "rounded", - "rover", - "rowboat", - "royal", - "ruby", - "rudely", - "ruffled", - "rugged", - "ruined", - "ruling", - "rumble", - "runway", - "rural", - "rustled", - "ruthless", - "sabotage", - "sack", - "sadness", - "safety", - "saga", - "sailor", - "sake", - "salads", - "sample", - "sanity", - "sapling", - "sarcasm", - "sash", - "satin", - "saucepan", - "saved", - "sawmill", - "saxophone", - "sayings", - "scamper", - "scenic", - "school", - "science", - "scoop", - "scrub", - "scuba", - "seasons", - "second", - "sedan", - "seeded", - "segments", - "seismic", - "selfish", - "semifinal", - "sensible", - "september", - "sequence", - "serving", - "session", - "setup", - "seventh", - "sewage", - "shackles", - "shelter", - "shipped", - "shocking", - "shrugged", - "shuffled", - "shyness", - "siblings", - "sickness", - "sidekick", - "sieve", - "sifting", - "sighting", - "silk", - "simplest", - "sincerely", - "sipped", - "siren", - "situated", - "sixteen", - "sizes", - "skater", - "skew", - "skirting", - "skulls", - "skydive", - "slackens", - "sleepless", - "slid", - "slower", - "slug", - "smash", - "smelting", - "smidgen", - "smog", - "smuggled", - "snake", - "sneeze", - "sniff", - "snout", - "snug", - "soapy", - "sober", - "soccer", - "soda", - "software", - "soggy", - "soil", - "solved", - "somewhere", - "sonic", - "soothe", - "soprano", - "sorry", - "southern", - "sovereign", - "sowed", - "soya", - "space", - "speedy", - "sphere", - "spiders", - "splendid", - "spout", - "sprig", - "spud", - "spying", - "square", - "stacking", - "stellar", - "stick", - "stockpile", - "strained", - "stunning", - "stylishly", - "subtly", - "succeed", - "suddenly", - "suede", - "suffice", - "sugar", - "suitcase", - "sulking", - "summon", - "sunken", - "superior", - "surfer", - "sushi", - "suture", - "swagger", - "swept", - "swiftly", - "sword", - "swung", - "syllabus", - "symptoms", - "syndrome", - "syringe", - "system", - "taboo", - "tacit", - "tadpoles", - "tagged", - "tail", - "taken", - "talent", - "tamper", - "tanks", - "tapestry", - "tarnished", - "tasked", - "tattoo", - "taunts", - "tavern", - "tawny", - "taxi", - "teardrop", - "technical", - "tedious", - "teeming", - "tell", - "template", - "tender", - "tepid", - "tequila", - "terminal", - "testing", - "tether", - "textbook", - "thaw", - "theatrics", - "thirsty", - "thorn", - "threaten", - "thumbs", - "thwart", - "ticket", - "tidy", - "tiers", - "tiger", - "tilt", - "timber", - "tinted", - "tipsy", - "tirade", - "tissue", - "titans", - "toaster", - "tobacco", - "today", - "toenail", - "toffee", - "together", - "toilet", - "token", - "tolerant", - "tomorrow", - "tonic", - "toolbox", - "topic", - "torch", - "tossed", - "total", - "touchy", - "towel", - "toxic", - "toyed", - "trash", - "trendy", - "tribal", - "trolling", - "truth", - "trying", - "tsunami", - "tubes", - "tucks", - "tudor", - "tuesday", - "tufts", - "tugs", - "tuition", - "tulips", - "tumbling", - "tunnel", - "turnip", - "tusks", - "tutor", - "tuxedo", - "twang", - "tweezers", - "twice", - "twofold", - "tycoon", - "typist", - "tyrant", - "ugly", - "ulcers", - "ultimate", - "umbrella", - "umpire", - "unafraid", - "unbending", - "uncle", - "under", - "uneven", - "unfit", - "ungainly", - "unhappy", - "union", - "unjustly", - "unknown", - "unlikely", - "unmask", - "unnoticed", - "unopened", - "unplugs", - "unquoted", - "unrest", - "unsafe", - "until", - "unusual", - "unveil", - "unwind", - "unzip", - "upbeat", - "upcoming", - "update", - "upgrade", - "uphill", - "upkeep", - "upload", - "upon", - "upper", - "upright", - "upstairs", - "uptight", - "upwards", - "urban", - "urchins", - "urgent", - "usage", - "useful", - "usher", - "using", - "usual", - "utensils", - "utility", - "utmost", - "utopia", - "uttered", - "vacation", - "vague", - "vain", - "value", - "vampire", - "vane", - "vapidly", - "vary", - "vastness", - "vats", - "vaults", - "vector", - "veered", - "vegan", - "vehicle", - "vein", - "velvet", - "venomous", - "verification", - "vessel", - "veteran", - "vexed", - "vials", - "vibrate", - "victim", - "video", - "viewpoint", - "vigilant", - "viking", - "village", - "vinegar", - "violin", - "vipers", - "virtual", - "visited", - "vitals", - "vivid", - "vixen", - "vocal", - "vogue", - "voice", - "volcano", - "vortex", - "voted", - "voucher", - "vowels", - "voyage", - "vulture", - "wade", - "waffle", - "wagtail", - "waist", - "waking", - "wallets", - "wanted", - "warped", - "washing", - "water", - "waveform", - "waxing", - "wayside", - "weavers", - "website", - "wedge", - "weekday", - "weird", - "welders", - "went", - "wept", - "were", - "western", - "wetsuit", - "whale", - "when", - "whipped", - "whole", - "wickets", - "width", - "wield", - "wife", - "wiggle", - "wildly", - "winter", - "wipeout", - "wiring", - "wise", - "withdrawn", - "wives", - "wizard", - "wobbly", - "woes", - "woken", - "wolf", - "womanly", - "wonders", - "woozy", - "worry", - "wounded", - "woven", - "wrap", - "wrist", - "wrong", - "yacht", - "yahoo", - "yanks", - "yard", - "yawning", - "yearbook", - "yellow", - "yesterday", - "yeti", - "yields", - "yodel", - "yoga", - "younger", - "yoyo", - "zapped", - "zeal", - "zebra", - "zero", - "zesty", - "zigzags", - "zinger", - "zippers", - "zodiac", - "zombie", - "zones", - "zoom" -]; diff --git a/cw_wownero/lib/wownero_transaction_info.dart b/cw_wownero/lib/wownero_transaction_info.dart index 7b0073452..db5345e5d 100644 --- a/cw_wownero/lib/wownero_transaction_info.dart +++ b/cw_wownero/lib/wownero_transaction_info.dart @@ -1,6 +1,5 @@ import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wownero_amount_format.dart'; -import 'package:cw_wownero/api/structs/transaction_info_row.dart'; import 'package:cw_core/parseBoolFromString.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/format_amount.dart'; @@ -35,26 +34,6 @@ class WowneroTransactionInfo extends TransactionInfo { }; } - WowneroTransactionInfo.fromRow(TransactionInfoRow row) - : id = "${row.getHash()}_${row.getAmount()}_${row.subaddrAccount}_${row.subaddrIndex}", - txHash = row.getHash(), - height = row.blockHeight, - direction = parseTransactionDirectionFromInt(row.direction), - date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000), - isPending = row.isPending != 0, - amount = row.getAmount(), - accountIndex = row.subaddrAccount, - addressIndex = row.subaddrIndex, - confirmations = row.confirmations, - key = getTxKey(row.getHash()), - fee = row.fee { - additionalInfo = { - 'key': key, - 'accountIndex': accountIndex, - 'addressIndex': addressIndex - }; - } - final String id; final String txHash; final int height; diff --git a/cw_wownero/lib/wownero_unspent.dart b/cw_wownero/lib/wownero_unspent.dart index a79106886..fdfdfc7a4 100644 --- a/cw_wownero/lib/wownero_unspent.dart +++ b/cw_wownero/lib/wownero_unspent.dart @@ -1,5 +1,4 @@ import 'package:cw_core/unspent_transaction_output.dart'; -import 'package:cw_wownero/api/structs/coins_info_row.dart'; class WowneroUnspent extends Unspent { WowneroUnspent( @@ -8,13 +7,5 @@ class WowneroUnspent extends Unspent { this.isFrozen = isFrozen; } - factory WowneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow) => WowneroUnspent( - coinsInfoRow.getAddress(), - coinsInfoRow.getHash(), - coinsInfoRow.getKeyImage(), - coinsInfoRow.amount, - coinsInfoRow.frozen == 1, - coinsInfoRow.unlocked == 1); - final bool isUnlocked; } diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index 52f84e26a..e02c0ec2e 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -107,9 +107,7 @@ abstract class WowneroWalletBase @override String get seed => wownero_wallet.getSeed(); - String seedLegacy(String? language) { - return wownero_wallet.getSeedLegacy(language); - } + String seedLegacy(String? language) => wownero_wallet.getSeedLegacy(language); @override MoneroWalletKeys get keys => MoneroWalletKeys( @@ -182,12 +180,12 @@ abstract class WowneroWalletBase @override Future startSync() async { try { - _setInitialHeight(); + _assertInitialHeight(); } catch (_) { // our restore height wasn't correct, so lets see if using the backup works: try { await resetCache(name); - _setInitialHeight(); + _assertInitialHeight(); } catch (e) { // we still couldn't get a valid height from the backup?!: // try to use the date instead: @@ -604,18 +602,14 @@ abstract class WowneroWalletBase _listener = wownero_wallet.setListeners(_onNewBlock, _onNewTransaction); } - // check if the height is correct: - void _setInitialHeight() { - if (walletInfo.isRecovery) { - return; - } + /// Asserts the current height to be above [MIN_RESTORE_HEIGHT] + void _assertInitialHeight() { + if (walletInfo.isRecovery) return; final height = wownero_wallet.getCurrentHeight(); - if (height > MIN_RESTORE_HEIGHT) { - // the restore height is probably correct, so we do nothing: - return; - } + // the restore height is probably correct, so we do nothing: + if (height > MIN_RESTORE_HEIGHT) return; throw Exception("height isn't > $MIN_RESTORE_HEIGHT!"); } diff --git a/cw_wownero/lib/wownero_wallet_addresses.dart b/cw_wownero/lib/wownero_wallet_addresses.dart index dc4b42840..9eeb182eb 100644 --- a/cw_wownero/lib/wownero_wallet_addresses.dart +++ b/cw_wownero/lib/wownero_wallet_addresses.dart @@ -109,7 +109,7 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store { accountIndex: accountIndex, defaultLabel: defaultLabel, usedAddresses: usedAddresses.toList()); - subaddress = subaddressList.subaddresses.last; + subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel) : subaddressList.subaddresses.last; address = subaddress!.address; } diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 011fed169..737743925 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -254,10 +254,10 @@ packages: 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: @@ -437,10 +437,10 @@ packages: monero: dependency: "direct main" description: - path: "." - ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 - resolved-ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 - url: "https://github.com/mrcyjanek/monero.dart" + path: "impls/monero.dart" + ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b + resolved-ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b + url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" mutex: @@ -555,14 +555,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" pub_semver: dependency: transitive description: @@ -748,10 +740,10 @@ packages: 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: diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml index 4537955ab..7a45eb628 100644 --- a/cw_wownero/pubspec.yaml +++ b/cw_wownero/pubspec.yaml @@ -24,8 +24,9 @@ dependencies: path: ../cw_core monero: git: - url: https://github.com/mrcyjanek/monero.dart - ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 + url: https://github.com/mrcyjanek/monero_c + ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b # monero_c hash + path: impls/monero.dart mutex: ^3.1.0 dev_dependencies: diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b99afe1d2..6277c147d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -238,7 +238,7 @@ SPEC CHECKSUMS: MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 - package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 + package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 Protobuf: fb2c13674723f76ff6eede14f78847a776455fa2 @@ -246,7 +246,7 @@ SPEC CHECKSUMS: reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 - share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 + share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 SwiftProtobuf: 5e8349171e7c2f88f5b9e683cb3cb79d1dc780b3 @@ -255,7 +255,7 @@ SPEC CHECKSUMS: uni_links: d97da20c7701486ba192624d99bffaaffcfc298a UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 + wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 PODFILE CHECKSUM: a2fe518be61cdbdc5b0e2da085ab543d556af2d3 diff --git a/lib/anonpay/anonpay_api.dart b/lib/anonpay/anonpay_api.dart index e46499407..acab662d1 100644 --- a/lib/anonpay/anonpay_api.dart +++ b/lib/anonpay/anonpay_api.dart @@ -20,7 +20,7 @@ class AnonPayApi { final WalletBase wallet; static const anonpayRef = secrets.anonPayReferralCode; - static const onionApiAuthority = 'trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion'; + static const onionApiAuthority = 'tqzngtf2hybjbexznel6dhgsvbynjzezoybvtv6iofomx7gchqfssgqd.onion'; static const clearNetAuthority = 'trocador.app'; static const markup = secrets.trocadorExchangeMarkup; static const anonPayPath = '/anonpay'; diff --git a/lib/app_scroll_behavior.dart b/lib/app_scroll_behavior.dart new file mode 100644 index 000000000..d5abd5688 --- /dev/null +++ b/lib/app_scroll_behavior.dart @@ -0,0 +1,9 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class AppScrollBehavior extends MaterialScrollBehavior { + @override + Set get dragDevices => + {PointerDeviceKind.touch, PointerDeviceKind.mouse, PointerDeviceKind.trackpad}; +} diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 0d7d3a1f2..4b4495dc3 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -307,16 +307,13 @@ class CWBitcoin extends Bitcoin { await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); late BasedUtxoNetwork network; - btc.NetworkType networkType; switch (node.type) { case WalletType.litecoin: network = LitecoinNetwork.mainnet; - networkType = litecoinNetwork; break; case WalletType.bitcoin: default: network = BitcoinNetwork.mainnet; - networkType = btc.bitcoin; break; } @@ -346,10 +343,8 @@ class CWBitcoin extends Bitcoin { balancePath += "/0"; } - final hd = btc.HDWallet.fromSeed( - seedBytes, - network: networkType, - ).derivePath(balancePath); + final hd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(balancePath) + as Bip32Slip10Secp256k1; // derive address at index 0: String? address; @@ -440,6 +435,13 @@ class CWBitcoin extends Bitcoin { ); } + @override + int feeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, + {int? size}) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.feeAmountWithFeeRate(feeRate, inputsCount, outputsCount, size: size); + } + @override int getMaxCustomFeeRate(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; @@ -513,10 +515,7 @@ class CWBitcoin extends Bitcoin { @override Future setScanningActive(Object wallet, bool active) async { final bitcoinWallet = wallet as ElectrumWallet; - bitcoinWallet.setSilentPaymentsScanning( - active, - active && (await getNodeIsElectrsSPEnabled(wallet)), - ); + bitcoinWallet.setSilentPaymentsScanning(active); } @override @@ -534,44 +533,10 @@ class CWBitcoin extends Bitcoin { bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan); } - Future getNodeIsElectrs(Object wallet) async { - final bitcoinWallet = wallet as ElectrumWallet; - - final version = await bitcoinWallet.electrumClient.version(); - - if (version.isEmpty) { - return false; - } - - final server = version[0]; - - if (server.toLowerCase().contains('electrs')) { - return true; - } - - return false; - } - @override Future getNodeIsElectrsSPEnabled(Object wallet) async { - if (!(await getNodeIsElectrs(wallet))) { - return false; - } - final bitcoinWallet = wallet as ElectrumWallet; - try { - final tweaksResponse = await bitcoinWallet.electrumClient.getTweaks(height: 0); - - if (tweaksResponse != null) { - return true; - } - } on RequestFailedTimeoutException catch (_) { - return false; - } catch (_) { - rethrow; - } - - return false; + return bitcoinWallet.getNodeSupportsSilentPayments(); } @override diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index c4cc3929f..465211f23 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -52,5 +52,9 @@ String syncStatusTitle(SyncStatus syncStatus) { return S.current.sync_status_syncronizing; } + if (syncStatus is StartingScanSyncStatus) { + return S.current.sync_status_starting_scan; + } + return ''; } diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart index 1f17a7a1c..ca29576e4 100644 --- a/lib/core/wallet_loading_service.dart +++ b/lib/core/wallet_loading_service.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:cake_wallet/core/generate_wallet_password.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; +import 'package:cake_wallet/reactions/on_authentication_state_change.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/wallet_base.dart'; @@ -52,6 +55,12 @@ class WalletLoadingService { } catch (error, stack) { ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack)); + // try fetching the seeds of the corrupted wallet to show it to the user + String corruptedWalletsSeeds = "Corrupted wallets seeds (if retrievable, empty otherwise):"; + try { + corruptedWalletsSeeds += await _getCorruptedWalletSeeds(name, type); + } catch (_) {} + // try opening another wallet that is not corrupted to give user access to the app final walletInfoSource = await CakeHive.openBox(WalletInfo.boxName); @@ -69,12 +78,23 @@ class WalletLoadingService { await sharedPreferences.setInt( PreferencesKey.currentWalletType, serializeToInt(wallet.type)); + // if found a wallet that is not corrupted, then still display the seeds of the corrupted ones + authenticatedErrorStreamController.add(corruptedWalletsSeeds); + return wallet; - } catch (_) {} + } catch (_) { + // save seeds and show corrupted wallets' seeds to the user + try { + final seeds = await _getCorruptedWalletSeeds(walletInfo.name, walletInfo.type); + if (!corruptedWalletsSeeds.contains(seeds)) { + corruptedWalletsSeeds += seeds; + } + } catch (_) {} + } } // if all user's wallets are corrupted throw exception - throw error; + throw error.toString() + "\n\n" + corruptedWalletsSeeds; } } @@ -96,4 +116,11 @@ class WalletLoadingService { isPasswordUpdated = true; await sharedPreferences.setBool(key, isPasswordUpdated); } + + Future _getCorruptedWalletSeeds(String name, WalletType type) async { + final walletService = walletServiceFactory.call(type); + final password = await keyService.getWalletPassword(walletName: name); + + return "\n\n$type ($name): ${await walletService.getSeeds(name, password, type)}"; + } } diff --git a/lib/di.dart b/lib/di.dart index 8f617fe41..29f50362d 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,3 +1,5 @@ +import 'dart:async' show Timer; + import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/anonpay/anonpay_api.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; @@ -489,6 +491,7 @@ Future setup({ if (loginError != null) { authPageState.changeProcessText('ERROR: ${loginError.toString()}'); + loginError = null; } ReactionDisposer? _reaction; @@ -500,6 +503,17 @@ Future setup({ linkViewModel.handleLink(); } }); + + Timer.periodic(Duration(seconds: 1), (timer) { + if (timer.tick > 30) { + timer.cancel(); + } + + if (loginError != null) { + authPageState.changeProcessText('ERROR: ${loginError.toString()}'); + timer.cancel(); + } + }); } }); }); @@ -753,8 +767,8 @@ Future setup({ getIt.registerFactory(() => TrocadorProvidersViewModel(getIt.get())); getIt.registerFactory(() { - return OtherSettingsViewModel(getIt.get(), getIt.get().wallet!); - }); + return OtherSettingsViewModel(getIt.get(), getIt.get().wallet!, + getIt.get());}); getIt.registerFactory(() { return SecuritySettingsViewModel(getIt.get()); diff --git a/lib/main.dart b/lib/main.dart index ff4057a26..67da927ce 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; +import 'package:cake_wallet/app_scroll_behavior.dart'; import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/di.dart'; @@ -76,6 +77,7 @@ Future main() async { runApp( MaterialApp( debugShowCheckedModeBanner: false, + scrollBehavior: AppScrollBehavior(), home: Scaffold( body: SingleChildScrollView( child: Container( @@ -301,6 +303,7 @@ class AppState extends State with SingleTickerProviderStateMixin { locale: Locale(settingsStore.languageCode), onGenerateRoute: (settings) => Router.createRoute(settings), initialRoute: initialRoute, + scrollBehavior: AppScrollBehavior(), home: _Home(), )); }); diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index c1384a3df..1f1888b44 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -346,4 +346,9 @@ class CWMonero extends Monero { Future getCurrentHeight() async { return monero_wallet_api.getCurrentHeight(); } + + @override + void monerocCheck() { + checkIfMoneroCIsFine(); + } } diff --git a/lib/reactions/on_authentication_state_change.dart b/lib/reactions/on_authentication_state_change.dart index 5f1214b76..e4fd9b32f 100644 --- a/lib/reactions/on_authentication_state_change.dart +++ b/lib/reactions/on_authentication_state_change.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:flutter/widgets.dart'; @@ -8,9 +10,16 @@ import 'package:cake_wallet/store/authentication_store.dart'; ReactionDisposer? _onAuthenticationStateChange; dynamic loginError; +StreamController authenticatedErrorStreamController = StreamController(); void startAuthenticationStateChange( AuthenticationStore authenticationStore, GlobalKey navigatorKey) { + authenticatedErrorStreamController.stream.listen((event) { + if (authenticationStore.state == AuthenticationState.allowed) { + ExceptionHandler.showError(event.toString(), delayInSeconds: 3); + } + }); + _onAuthenticationStateChange ??= autorun((_) async { final state = authenticationStore.state; @@ -26,6 +35,11 @@ void startAuthenticationStateChange( if (state == AuthenticationState.allowed) { await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); + if (!(await authenticatedErrorStreamController.stream.isEmpty)) { + ExceptionHandler.showError( + (await authenticatedErrorStreamController.stream.first).toString()); + authenticatedErrorStreamController.stream.drain(); + } return; } }); diff --git a/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart b/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart index fd8dce103..02ddf037d 100644 --- a/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart +++ b/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart @@ -34,7 +34,7 @@ class CakePayBuyCardDetailPage extends BasePage { @override Widget? middle(BuildContext context) { - return Text( + return Text( title, textAlign: TextAlign.center, maxLines: 2, @@ -359,7 +359,7 @@ class CakePayBuyCardDetailPage extends BasePage { reaction((_) => cakePayPurchaseViewModel.sendViewModel.state, (ExecutionState state) { if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { - showStateAlert(context, S.of(context).error, state.error); + if (context.mounted) showStateAlert(context, S.of(context).error, state.error); }); } @@ -381,31 +381,35 @@ class CakePayBuyCardDetailPage extends BasePage { } void showStateAlert(BuildContext context, String title, String content) { - showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: title, - alertContent: content, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); + if (context.mounted) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: title, + alertContent: content, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } } Future showSentAlert(BuildContext context) async { + if (!context.mounted) { + return; + } final order = cakePayPurchaseViewModel.order!.orderId; final isCopy = await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).transaction_sent, - alertContent: - S.of(context).cake_pay_save_order + '\n${order}', - leftButtonText: S.of(context).ignor, - rightButtonText: S.of(context).copy, - actionLeftButton: () => Navigator.of(context).pop(false), - actionRightButton: () => Navigator.of(context).pop(true)); - }) ?? + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).transaction_sent, + alertContent: S.of(context).cake_pay_save_order + '\n${order}', + leftButtonText: S.of(context).ignor, + rightButtonText: S.of(context).copy, + actionLeftButton: () => Navigator.of(context).pop(false), + actionRightButton: () => Navigator.of(context).pop(true)); + }) ?? false; if (isCopy) { diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index 7a2055930..ad6e68cd8 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -237,7 +237,11 @@ class _DashboardPageView extends BasePage { padding: EdgeInsets.only(bottom: 24, top: 10), child: Observer( builder: (context) { - return ExcludeSemantics( + return Semantics( + button: false, + label: 'Page Indicator', + hint: 'Swipe to change page', + excludeSemantics: true, child: SmoothPageIndicator( controller: controller, count: pages.length, diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index e9c113d8e..11f119071 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -24,6 +24,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; 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/widgets.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -126,6 +127,36 @@ class CryptoBalanceWidget extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ + Observer( + builder: (_) { + if (dashboardViewModel.getMoneroError != null) { + return Padding( + padding: const EdgeInsets.fromLTRB(16,0,16,16), + child: DashBoardRoundedCardWidget( + title: "Invalid monero bindings", + subTitle: dashboardViewModel.getMoneroError.toString(), + onTap: () {}, + ), + ); + } + return Container(); + }, + ), + Observer( + builder: (_) { + if (dashboardViewModel.getWowneroError != null) { + return Padding( + padding: const EdgeInsets.fromLTRB(16,0,16,16), + child: DashBoardRoundedCardWidget( + title: "Invalid wownero bindings", + subTitle: dashboardViewModel.getWowneroError.toString(), + onTap: () {}, + ) + ); + } + return Container(); + }, + ), Observer( builder: (_) => dashboardViewModel.balanceViewModel.hasAccounts ? HomeScreenAccountWidget( @@ -252,6 +283,20 @@ class CryptoBalanceWidget extends StatelessWidget { Observer(builder: (context) { return Column( children: [ + if (dashboardViewModel.isMoneroWalletBrokenReasons.isNotEmpty) ...[ + SizedBox(height: 10), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: DashBoardRoundedCardWidget( + customBorder: 30, + title: "Monero wallet is broken", + subTitle: "Here are the things that are broken:\n - " + +dashboardViewModel.isMoneroWalletBrokenReasons.join("\n - ") + +"\n\nPlease restart your wallet and if it doesn't help contact our support.", + onTap: () {}, + ) + ) + ], if (dashboardViewModel.showSilentPaymentsCard) ...[ SizedBox(height: 10), Padding( @@ -509,15 +554,19 @@ class BalanceRowWidget extends StatelessWidget { children: [ Row( children: [ - Text('${availableBalanceLabel}', - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .labelTextColor, - height: 1)), + Semantics( + hint: 'Double tap to see more information', + container: true, + child: Text('${availableBalanceLabel}', + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .labelTextColor, + height: 1)), + ), if (hasAdditionalBalance) Padding( padding: const EdgeInsets.symmetric(horizontal: 4), diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 5c064df27..2c717a3c8 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -67,17 +67,6 @@ class ExchangePage extends BasePage { Debounce _depositAmountDebounce = Debounce(Duration(milliseconds: 500)); var _isReactionsSet = false; - final arrowBottomPurple = Image.asset( - 'assets/images/arrow_bottom_purple_icon.png', - color: Colors.white, - height: 8, - ); - final arrowBottomCakeGreen = Image.asset( - 'assets/images/arrow_bottom_cake_green.png', - color: Colors.white, - height: 8, - ); - late final String? depositWalletName; late final String? receiveWalletName; @@ -101,11 +90,11 @@ class ExchangePage extends BasePage { @override Function(BuildContext)? get pushToNextWidget => (context) { - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.focusedChild?.unfocus(); - } - }; + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.focusedChild?.unfocus(); + } + }; @override Widget middle(BuildContext context) => Row( @@ -340,7 +329,6 @@ class ExchangePage extends BasePage { void applyTemplate( BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async { - final depositCryptoCurrency = CryptoCurrency.fromString(template.depositCurrency); final receiveCryptoCurrency = CryptoCurrency.fromString(template.receiveCurrency); @@ -354,10 +342,12 @@ class ExchangePage extends BasePage { exchangeViewModel.isFixedRateMode = false; var domain = template.depositAddress; - exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, depositCryptoCurrency); + exchangeViewModel.depositAddress = + await fetchParsedAddress(context, domain, depositCryptoCurrency); domain = template.receiveAddress; - exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, receiveCryptoCurrency); + exchangeViewModel.receiveAddress = + await fetchParsedAddress(context, domain, receiveCryptoCurrency); } void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) { @@ -529,14 +519,16 @@ class ExchangePage extends BasePage { _depositAddressFocus.addListener(() async { if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) { final domain = depositAddressController.text; - exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency); + exchangeViewModel.depositAddress = + await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency); } }); _receiveAddressFocus.addListener(() async { if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) { final domain = receiveAddressController.text; - exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency); + exchangeViewModel.receiveAddress = + await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency); } }); @@ -589,7 +581,8 @@ class ExchangePage extends BasePage { } } - Future fetchParsedAddress(BuildContext context, String domain, CryptoCurrency currency) async { + Future fetchParsedAddress( + BuildContext context, String domain, CryptoCurrency currency) async { final parsedAddress = await getIt.get().resolve(context, domain, currency); final address = await extractAddressFromParsed(context, parsedAddress); return address; @@ -658,7 +651,6 @@ class ExchangePage extends BasePage { exchangeViewModel.changeDepositCurrency(currency: currency); }, - imageArrow: arrowBottomPurple, currencyButtonColor: Colors.transparent, addressButtonsColor: Theme.of(context).extension()!.textFieldButtonColor, @@ -705,7 +697,6 @@ class ExchangePage extends BasePage { currencies: exchangeViewModel.receiveCurrencies, onCurrencySelected: (currency) => exchangeViewModel.changeReceiveCurrency(currency: currency), - imageArrow: arrowBottomCakeGreen, currencyButtonColor: Colors.transparent, addressButtonsColor: Theme.of(context).extension()!.textFieldButtonColor, diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 760b0c137..02218f848 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/core/amount_validator.dart'; import 'package:cake_wallet/entities/contact_base.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; @@ -27,7 +28,7 @@ class ExchangeCard extends StatefulWidget { required this.isAmountEstimated, required this.currencies, required this.onCurrencySelected, - required this.imageArrow, + this.imageArrow, this.currencyValueValidator, this.addressTextFieldValidator, this.title = '', @@ -58,7 +59,7 @@ class ExchangeCard extends StatefulWidget { final bool isAmountEstimated; final bool hasRefundAddress; final bool isMoneroWallet; - final Image imageArrow; + final Image? imageArrow; final Color currencyButtonColor; final Color? addressButtonsColor; final Color borderColor; @@ -191,120 +192,18 @@ class ExchangeCardState extends State { ) ], ), - Padding( - padding: EdgeInsets.only(top: 20), - child: Row( - children: [ - Container( - padding: EdgeInsets.only(right: 8), - height: 32, - color: widget.currencyButtonColor, - child: InkWell( - onTap: () => _presentPicker(context), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only(right: 5), - child: widget.imageArrow, - ), - Text(_selectedCurrency.toString(), - style: TextStyle( - fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)) - ]), - ), - ), - if (_selectedCurrency.tag != null) - Padding( - padding: const EdgeInsets.only(right: 3.0), - child: Container( - height: 32, - decoration: BoxDecoration( - color: widget.addressButtonsColor ?? - Theme.of(context).extension()!.textFieldButtonColor, - borderRadius: BorderRadius.all(Radius.circular(6))), - child: Center( - child: Padding( - padding: const EdgeInsets.all(6.0), - child: Text(_selectedCurrency.tag!, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context) - .extension()! - .textFieldButtonIconColor)), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Text(':', - style: TextStyle( - fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)), - ), - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: FocusTraversalOrder( - order: NumericFocusOrder(1), - child: BaseTextFormField( - focusNode: widget.amountFocusNode, - controller: amountController, - enabled: _isAmountEditable, - textAlign: TextAlign.left, - keyboardType: - TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')) - ], - hintText: '0.0000', - borderColor: Colors.transparent, - //widget.borderColor, - textStyle: TextStyle( - fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), - placeholderTextStyle: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .extension()! - .hintTextColor), - validator: _isAmountEditable - ? widget.currencyValueValidator - : null), - ), - ), - if (widget.hasAllAmount) - Container( - height: 32, - width: 32, - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldButtonColor, - borderRadius: BorderRadius.all(Radius.circular(6))), - child: InkWell( - onTap: () => widget.allAmount?.call(), - child: Center( - child: Text(S.of(context).all, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context) - .extension()! - .textFieldButtonIconColor)), - ), - ), - ) - ], - ), - ), - ], - )), + CurrencyAmountTextField( + imageArrow: widget.imageArrow, + selectedCurrency: _selectedCurrency.toString(), + amountFocusNode: widget.amountFocusNode, + amountController: amountController, + onTapPicker: () => _presentPicker(context), + isAmountEditable: _isAmountEditable, + isPickerEnable: true, + allAmountButton: widget.hasAllAmount, + currencyValueValidator: widget.currencyValueValidator, + tag: _selectedCurrency.tag, + allAmountCallback: widget.allAmount), Divider(height: 1, color: Theme.of(context).extension()!.textFieldHintColor), Padding( padding: EdgeInsets.only(top: 5), diff --git a/lib/src/screens/monero_accounts/monero_account_edit_or_create_page.dart b/lib/src/screens/monero_accounts/monero_account_edit_or_create_page.dart index 779628be8..2c9918d74 100644 --- a/lib/src/screens/monero_accounts/monero_account_edit_or_create_page.dart +++ b/lib/src/screens/monero_accounts/monero_account_edit_or_create_page.dart @@ -51,7 +51,9 @@ class MoneroAccountEditOrCreatePage extends BasePage { await moneroAccountCreationViewModel.save(); - Navigator.of(context).pop(_textController.text); + if (context.mounted) { + Navigator.of(context).pop(_textController.text); + } }, text: moneroAccountCreationViewModel.isEdit ? S.of(context).rename diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index 306c41479..d9427af0a 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -40,11 +40,11 @@ class NewWalletPage extends BasePage { @override Function(BuildContext)? get pushToNextWidget => (context) { - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.focusedChild?.unfocus(); - } - }; + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.focusedChild?.unfocus(); + } + }; @override Widget body(BuildContext context) => WalletNameForm( @@ -88,15 +88,17 @@ class _WalletNameFormState extends State { if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { - showPopUp( - context: context, - builder: (_) { - return AlertWithOneAction( - alertTitle: S.current.new_wallet, - alertContent: state.error, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); + if (context.mounted) { + showPopUp( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: S.current.new_wallet, + alertContent: state.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } }); } }); diff --git a/lib/src/screens/receive/widgets/currency_input_field.dart b/lib/src/screens/receive/widgets/currency_input_field.dart index 84b2a7bca..ce3de9a6c 100644 --- a/lib/src/screens/receive/widgets/currency_input_field.dart +++ b/lib/src/screens/receive/widgets/currency_input_field.dart @@ -1,135 +1,210 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; -import 'package:cake_wallet/utils/responsive_layout_util.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/currency.dart'; +import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; -import 'package:cake_wallet/themes/extensions/picker_theme.dart'; -import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; -class CurrencyInputField extends StatelessWidget { - const CurrencyInputField({ - super.key, - required this.onTapPicker, +class CurrencyAmountTextField extends StatelessWidget { + const CurrencyAmountTextField({ required this.selectedCurrency, - this.focusNode, - required this.controller, - required this.isLight, + required this.amountFocusNode, + required this.amountController, + required this.isAmountEditable, + this.allAmountButton = false, + this.isPickerEnable = false, + this.isSelected = false, + this.currentTheme = ThemeType.dark, + this.onTapPicker, + this.padding, + this.imageArrow, + this.hintText, + this.tag, + this.tagBackgroundColor, + this.currencyValueValidator, + this.allAmountCallback, }); - final Function() onTapPicker; - final Currency selectedCurrency; - final FocusNode? focusNode; - final TextEditingController controller; - final bool isLight; - - String get _currencyName { - if (selectedCurrency is CryptoCurrency) { - return (selectedCurrency as CryptoCurrency).title.toUpperCase(); - } - return selectedCurrency.name.toUpperCase(); - } + final Widget? imageArrow; + final String selectedCurrency; + final String? tag; + final String? hintText; + final Color? tagBackgroundColor; + final EdgeInsets? padding; + final FocusNode? amountFocusNode; + final TextEditingController amountController; + final bool isAmountEditable; + final FormFieldValidator? currencyValueValidator; + final bool isPickerEnable; + final ThemeType currentTheme; + final bool isSelected; + final bool allAmountButton; + final VoidCallback? allAmountCallback; + final VoidCallback? onTapPicker; @override Widget build(BuildContext context) { - final arrowBottomPurple = Image.asset( - 'assets/images/arrow_bottom_purple_icon.png', - color: Theme.of(context).extension()!.textColor, - height: 8, - ); - // This magic number for wider screen sets the text input focus at center of the inputfield - final _width = - responsiveLayoutUtil.shouldRenderMobileUI ? MediaQuery.of(context).size.width : 500; - - return Column( + final textColor = currentTheme == ThemeType.light + ? Theme.of(context).appBarTheme.titleTextStyle!.color! + : Colors.white; + final _prefixContent = Row( children: [ - Padding( - padding: EdgeInsets.only(top: 20), - child: SizedBox( - height: 40, - child: BaseTextFormField( - focusNode: focusNode, - controller: controller, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+(\.|\,)?\d{0,8}'))], - hintText: '0.000', - placeholderTextStyle: isLight - ? null - : TextStyle( - color: Theme.of(context).extension()!.textFieldBorderColor, - fontWeight: FontWeight.w600, - ), - borderColor: Theme.of(context).extension()!.dividerColor, - textColor: Theme.of(context).extension()!.textColor, - textStyle: TextStyle( - color: Theme.of(context).extension()!.textColor, - ), - prefixIcon: Padding( - padding: EdgeInsets.only( - left: _width / 4, + isPickerEnable + ? Container( + height: 32, + child: InkWell( + onTap: onTapPicker, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(right: 5), + child: imageArrow ?? + Image.asset('assets/images/arrow_bottom_purple_icon.png', + color: textColor, height: 8)), + Text( + selectedCurrency, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: textColor, + ), + ), + ], + ), ), - child: Container( - padding: EdgeInsets.only(right: 8), - child: InkWell( - onTap: onTapPicker, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only(right: 5), - child: arrowBottomPurple, - ), - Text( - _currencyName, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: Theme.of(context).extension()!.textColor, - ), - ), - if (selectedCurrency.tag != null) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 3.0), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()!.textFieldButtonColor, - borderRadius: BorderRadius.all( - Radius.circular(6), - ), - ), - child: Center( - child: Text( - selectedCurrency.tag!, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context).extension()!.textFieldButtonIconColor, - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 3.0), - child: Text( - ':', - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 20, - color: Theme.of(context).extension()!.textColor, - ), - ), - ), - ]), + ) + : Text( + selectedCurrency, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: textColor, + ), + ), + if (tag != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3.0), + child: Container( + height: 32, + decoration: BoxDecoration( + color: tagBackgroundColor ?? + Theme.of(context).extension()!.textFieldButtonColor, + borderRadius: const BorderRadius.all(Radius.circular(6)), + ), + child: Center( + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Text( + tag!, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Theme.of(context).extension()!.textFieldButtonIconColor, + ), ), ), ), ), ), + Padding( + padding: EdgeInsets.only(right: 4.0), + child: Text( + ':', + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: textColor, + ), + ), ), ], ); + return Padding( + padding: padding ?? const EdgeInsets.only(top: 20), + child: Row( + children: [ + isSelected + ? Container( + child: _prefixContent, + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8), + margin: const EdgeInsets.only(right: 3), + decoration: BoxDecoration( + border: Border.all( + color: textColor, + ), + borderRadius: BorderRadius.circular(26), + color: Theme.of(context).primaryColor)) + : _prefixContent, + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: FocusTraversalOrder( + order: NumericFocusOrder(1), + child: BaseTextFormField( + focusNode: amountFocusNode, + controller: amountController, + enabled: isAmountEditable, + textAlign: TextAlign.left, + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')), + ], + hintText: hintText ?? '0.0000', + borderColor: Colors.transparent, + textStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: textColor, + ), + placeholderTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: currentTheme == ThemeType.light + ? Theme.of(context).appBarTheme.titleTextStyle!.color! + : Theme.of(context).extension()!.hintTextColor, + ), + validator: isAmountEditable ? currencyValueValidator : null, + ), + ), + ), + if (allAmountButton) + Container( + height: 32, + width: 32, + decoration: BoxDecoration( + color: Theme.of(context).extension()!.textFieldButtonColor, + borderRadius: const BorderRadius.all(Radius.circular(6)), + ), + child: InkWell( + onTap: allAmountCallback, + child: Center( + child: Text( + S.of(context).all, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Theme.of(context) + .extension()! + .textFieldButtonIconColor, + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); } } diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index bbfd4d5c1..9f0db059a 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -1,11 +1,15 @@ import 'package:cake_wallet/entities/qr_view_data.dart'; +import 'package:cake_wallet/themes/extensions/picker_theme.dart'; import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/brightness_util.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -38,6 +42,10 @@ class QRWidget extends StatelessWidget { final copyImage = Image.asset('assets/images/copy_address.png', color: Theme.of(context).extension()!.qrWidgetCopyButtonColor); + // This magic number for wider screen sets the text input focus at center of the inputfield + final _width = + responsiveLayoutUtil.shouldRenderMobileUI ? MediaQuery.of(context).size.width : 500; + return Center( child: SingleChildScrollView( child: Column( @@ -85,8 +93,9 @@ class QRWidget extends StatelessWidget { decoration: BoxDecoration( border: Border.all( width: 3, - color: - Theme.of(context).extension()!.textColor, + color: Theme.of(context) + .extension()! + .textColor, ), ), child: Container( @@ -116,20 +125,23 @@ class QRWidget extends StatelessWidget { children: [ Expanded( child: Form( - key: formKey, - child: CurrencyInputField( - focusNode: amountTextFieldFocusNode, - controller: amountController, - onTapPicker: () => _presentPicker(context), - selectedCurrency: addressListViewModel.selectedCurrency, - isLight: isLight, - ), - ), + key: formKey, + child: CurrencyAmountTextField( + selectedCurrency: _currencyName, + amountFocusNode: amountTextFieldFocusNode, + amountController: amountController, + padding: EdgeInsets.only(top: 20, left: _width / 4), + currentTheme: isLight ? ThemeType.light : ThemeType.dark, + isAmountEditable: true, + tag: addressListViewModel.selectedCurrency.tag, + onTapPicker: () => _presentPicker(context), + isPickerEnable: true)), ), ], ), ); }), + Divider(height: 1, color: Theme.of(context).extension()!.dividerColor), Padding( padding: EdgeInsets.only(top: 20, bottom: 8), child: Builder( @@ -150,7 +162,8 @@ class QRWidget extends StatelessWidget { style: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.textColor), + color: + Theme.of(context).extension()!.textColor), ), ), Padding( @@ -169,6 +182,13 @@ class QRWidget extends StatelessWidget { ); } + String get _currencyName { + if (addressListViewModel.selectedCurrency is CryptoCurrency) { + return (addressListViewModel.selectedCurrency as CryptoCurrency).title.toUpperCase(); + } + return addressListViewModel.selectedCurrency.name.toUpperCase(); + } + void _presentPicker(BuildContext context) async { await showPopUp( builder: (_) => CurrencyPicker( diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index a9bd52b26..746b73dca 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -177,16 +177,22 @@ class WalletRestorePage extends BasePage { if (_pages.length > 1) Padding( padding: EdgeInsets.only(top: 10), - child: SmoothPageIndicator( - controller: _controller, - count: _pages.length, - effect: ColorTransitionEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context).hintColor.withOpacity(0.5), - activeDotColor: Theme.of(context).hintColor, + child: Semantics( + button: false, + label: 'Page Indicator', + hint: 'Swipe to change restore mode', + excludeSemantics: true, + child: SmoothPageIndicator( + controller: _controller, + count: _pages.length, + effect: ColorTransitionEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context).hintColor.withOpacity(0.5), + activeDotColor: Theme.of(context).hintColor, + ), ), ), ), diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index b46a7f3db..97a7ad88d 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -212,7 +212,12 @@ class SendPage extends BasePage { final count = sendViewModel.outputs.length; return count > 1 - ? SmoothPageIndicator( + ? Semantics ( + label: 'Page Indicator', + hint: 'Swipe to change receiver', + excludeSemantics: true, + child: + SmoothPageIndicator( controller: controller, count: count, effect: ScrollingDotsEffect( @@ -226,7 +231,7 @@ class SendPage extends BasePage { activeDotColor: Theme.of(context) .extension()! .templateBackgroundColor), - ) + )) : Offstage(); }, ), diff --git a/lib/src/screens/send/send_template_page.dart b/lib/src/screens/send/send_template_page.dart index 76414ecb2..5db70c0eb 100644 --- a/lib/src/screens/send/send_template_page.dart +++ b/lib/src/screens/send/send_template_page.dart @@ -94,21 +94,27 @@ class SendTemplatePage extends BasePage { final count = sendTemplateViewModel.recipients.length; return count > 1 - ? SmoothPageIndicator( - controller: controller, - count: count, - effect: ScrollingDotsEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context) - .extension()! - .indicatorDotColor, - activeDotColor: Theme.of(context) - .extension()! - .indicatorDotTheme - .activeIndicatorColor)) + ? Semantics( + button: false, + label: 'Page Indicator', + hint: 'Swipe to change receiver', + excludeSemantics: true, + child: SmoothPageIndicator( + controller: controller, + count: count, + effect: ScrollingDotsEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context) + .extension()! + .indicatorDotColor, + activeDotColor: Theme.of(context) + .extension()! + .indicatorDotTheme + .activeIndicatorColor)), + ) : Offstage(); }, ), @@ -144,7 +150,7 @@ class SendTemplatePage extends BasePage { .toList(); sendTemplateViewModel.addTemplate( - isCurrencySelected: mainTemplate.isCurrencySelected, + isCurrencySelected: mainTemplate.isCryptoSelected, name: mainTemplate.name, address: mainTemplate.address, cryptoCurrency: mainTemplate.selectedCurrency.title, diff --git a/lib/src/screens/send/widgets/prefix_currency_icon_widget.dart b/lib/src/screens/send/widgets/prefix_currency_icon_widget.dart deleted file mode 100644 index d30349066..000000000 --- a/lib/src/screens/send/widgets/prefix_currency_icon_widget.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; -import 'package:flutter/material.dart'; - -class PrefixCurrencyIcon extends StatelessWidget { - PrefixCurrencyIcon({ - required this.isSelected, - required this.title, - this.onTap, - }); - - final bool isSelected; - final String title; - final Function()? onTap; - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Padding( - padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0), - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(26), - color: isSelected - ? Theme.of(context) - .extension()! - .templateSelectedCurrencyBackgroundColor - : Colors.transparent, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (onTap != null) - Padding( - padding: EdgeInsets.only(right: 5), - child: Image.asset( - 'assets/images/arrow_bottom_purple_icon.png', - color: Colors.white, - height: 8, - ), - ), - Text( - title + ':', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: isSelected - ? Theme.of(context) - .extension()! - .templateSelectedCurrencyTitleColor - : Colors.white, - ), - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index ec833159f..0a3de3e58 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; @@ -207,166 +208,19 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin Padding( - padding: const EdgeInsets.only(top: 20), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Row( - children: [ - sendViewModel.hasMultipleTokens - ? Container( - padding: EdgeInsets.only(right: 8), - height: 32, - child: InkWell( - onTap: () => _presentPicker(context), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only(right: 5), - child: Image.asset( - 'assets/images/arrow_bottom_purple_icon.png', - color: Colors.white, - height: 8, - ), - ), - Text( - sendViewModel.selectedCryptoCurrency.title, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: Colors.white), - ), - ], - ), - ), - ) - : Text( - sendViewModel.selectedCryptoCurrency.title, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: Colors.white), - ), - sendViewModel.selectedCryptoCurrency.tag != null - ? Padding( - padding: const EdgeInsets.fromLTRB(3.0, 0, 3.0, 0), - child: Container( - height: 32, - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldButtonColor, - borderRadius: BorderRadius.all( - Radius.circular(6), - )), - child: Center( - child: Padding( - padding: const EdgeInsets.all(6.0), - child: Text( - sendViewModel.selectedCryptoCurrency.tag!, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context) - .extension()! - .textFieldButtonIconColor), - ), - ), - ), - ), - ) - : Container(), - Padding( - padding: const EdgeInsets.only(right: 10.0), - child: Text( - ':', - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: Colors.white), - ), - ), - ], - ), - ), - Expanded( - child: Stack( - children: [ - BaseTextFormField( - focusNode: cryptoAmountFocus, - controller: cryptoAmountController, - keyboardType: TextInputType.numberWithOptions( - signed: false, decimal: true), - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')) - ], - suffixIcon: SizedBox( - width: prefixIconWidth, - ), - hintText: '0.0000', - borderColor: Colors.transparent, - textStyle: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.white), - placeholderTextStyle: TextStyle( - color: Theme.of(context) - .extension()! - .textFieldHintColor, - fontWeight: FontWeight.w500, - fontSize: 14), - validator: output.sendAll - ? sendViewModel.allAmountValidator - : sendViewModel.amountValidator, - ), - if (!sendViewModel.isBatchSending) - Positioned( - top: 2, - right: 0, - child: Container( - width: prefixIconWidth, - height: prefixIconHeight, - child: InkWell( - onTap: () async { - output.setSendAll(sendViewModel.balance); - }, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldButtonColor, - borderRadius: BorderRadius.all( - Radius.circular(6), - ), - ), - child: Center( - child: Text( - S.of(context).all, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context) - .extension()! - .textFieldButtonIconColor, - ), - ), - ), - ), - ), - ), - ), - ], - ), - ), - ], - )), - ), + CurrencyAmountTextField( + selectedCurrency: sendViewModel.selectedCryptoCurrency.title, + amountFocusNode: cryptoAmountFocus, + amountController: cryptoAmountController, + isAmountEditable: true, + onTapPicker: () => _presentPicker(context), + isPickerEnable: sendViewModel.hasMultipleTokens, + tag: sendViewModel.selectedCryptoCurrency.tag, + allAmountButton: !sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL, + currencyValueValidator: output.sendAll + ? sendViewModel.allAmountValidator + : sendViewModel.amountValidator, + allAmountCallback: () async => output.setSendAll(sendViewModel.balance)), Divider( height: 1, color: Theme.of(context).extension()!.textFieldHintColor), @@ -402,41 +256,16 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin()!.textFieldBorderColor, - textStyle: TextStyle( - fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), - placeholderTextStyle: TextStyle( - color: - Theme.of(context).extension()!.textFieldHintColor, - fontWeight: FontWeight.w500, - fontSize: 14), - ), - ), + isAmountEditable: true, + allAmountButton: false), + Divider( + height: 1, + color: Theme.of(context).extension()!.textFieldHintColor), Padding( padding: EdgeInsets.only(top: 20), child: BaseTextFormField( @@ -715,12 +544,11 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin( context: context, builder: (_) => CurrencyPicker( - selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency), - items: sendViewModel.currencies, - hintText: S.of(context).search_currency, - onItemSelected: (Currency cur) => - sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency), - ), + selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency), + items: sendViewModel.currencies, + hintText: S.of(context).search_currency, + onItemSelected: (Currency cur) => + sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency)), ); } diff --git a/lib/src/screens/send/widgets/send_template_card.dart b/lib/src/screens/send/widgets/send_template_card.dart index 4f62616e3..bf2a66b73 100644 --- a/lib/src/screens/send/widgets/send_template_card.dart +++ b/lib/src/screens/send/widgets/send_template_card.dart @@ -1,5 +1,5 @@ import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; -import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -59,7 +59,8 @@ class SendTemplateCard extends StatelessWidget { hintText: sendTemplateViewModel.recipients.length > 1 ? S.of(context).template_name : S.of(context).send_name, - borderColor: Theme.of(context).extension()!.textFieldBorderColor, + borderColor: + Theme.of(context).extension()!.textFieldBorderColor, textStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), placeholderTextStyle: TextStyle( @@ -69,107 +70,87 @@ class SendTemplateCard extends StatelessWidget { validator: sendTemplateViewModel.templateValidator), Padding( padding: EdgeInsets.only(top: 20), - child: Observer( - builder: (context) { - return AddressTextField( - selectedCurrency: template.selectedCurrency, - controller: _addressController, - onURIScanned: (uri) { - final paymentRequest = PaymentRequest.fromUri(uri); - _addressController.text = paymentRequest.address; - _cryptoAmountController.text = paymentRequest.amount; - }, - options: [ - AddressTextFieldOption.paste, - AddressTextFieldOption.qrCode, - AddressTextFieldOption.addressBook - ], - onPushPasteButton: (context) async { - template.output.resetParsedAddress(); - await template.output.fetchParsedAddress(context); - }, - onPushAddressBookButton: (context) async { - template.output.resetParsedAddress(); - await template.output.fetchParsedAddress(context); - }, - buttonColor: Theme.of(context).extension()!.textFieldButtonColor, - borderColor: Theme.of(context).extension()!.textFieldBorderColor, - textStyle: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - hintStyle: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.textFieldHintColor, - ), - validator: sendTemplateViewModel.addressValidator, - ); - } - ), - ), - Padding( - padding: const EdgeInsets.only(top: 20), - child: Focus( - onFocusChange: (hasFocus) { - if (hasFocus) { - template.selectCurrency(); - } - }, - child: BaseTextFormField( - focusNode: _cryptoAmountFocus, - controller: _cryptoAmountController, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))], - prefixIcon: Observer( - builder: (_) => PrefixCurrencyIcon( - title: template.selectedCurrency.title, - isSelected: template.isCurrencySelected, - onTap: sendTemplateViewModel.walletCurrencies.length > 1 - ? () => _presentPicker(context) - : null, - ), - ), - hintText: '0.0000', - borderColor: Theme.of(context).extension()!.textFieldBorderColor, - textStyle: - TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), - placeholderTextStyle: TextStyle( - color: Theme.of(context).extension()!.textFieldHintColor, - fontWeight: FontWeight.w500, - fontSize: 14), - validator: sendTemplateViewModel.amountValidator, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 20), - child: Focus( - onFocusChange: (hasFocus) { - if (hasFocus) { - template.selectFiat(); - } - }, - child: BaseTextFormField( - focusNode: _fiatAmountFocus, - controller: _fiatAmountController, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))], - prefixIcon: Observer( - builder: (_) => PrefixCurrencyIcon( - title: sendTemplateViewModel.fiatCurrency, - isSelected: template.isFiatSelected)), - hintText: '0.00', - borderColor: Theme.of(context).extension()!.textFieldBorderColor, - textStyle: - TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), - placeholderTextStyle: TextStyle( - color: Theme.of(context).extension()!.textFieldHintColor, - fontWeight: FontWeight.w500, + child: Observer(builder: (context) { + return AddressTextField( + selectedCurrency: template.selectedCurrency, + controller: _addressController, + onURIScanned: (uri) { + final paymentRequest = PaymentRequest.fromUri(uri); + _addressController.text = paymentRequest.address; + _cryptoAmountController.text = paymentRequest.amount; + }, + options: [ + AddressTextFieldOption.paste, + AddressTextFieldOption.qrCode, + AddressTextFieldOption.addressBook + ], + onPushPasteButton: (context) async { + template.output.resetParsedAddress(); + await template.output.fetchParsedAddress(context); + }, + onPushAddressBookButton: (context) async { + template.output.resetParsedAddress(); + await template.output.fetchParsedAddress(context); + }, + buttonColor: + Theme.of(context).extension()!.textFieldButtonColor, + borderColor: + Theme.of(context).extension()!.textFieldBorderColor, + textStyle: TextStyle( fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.white, ), - ), + hintStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.textFieldHintColor, + ), + validator: sendTemplateViewModel.addressValidator, + ); + }), + ), + Focus( + onFocusChange: (hasFocus) { + if (hasFocus) template.setCryptoCurrency(true); + }, + child: Column( + children: [ + Observer( + builder: (context) => CurrencyAmountTextField( + selectedCurrency: template.selectedCurrency.title, + amountFocusNode: _cryptoAmountFocus, + amountController: _cryptoAmountController, + isSelected: template.isCryptoSelected, + tag: template.selectedCurrency.tag, + isPickerEnable: sendTemplateViewModel.hasMultipleTokens, + onTapPicker: () => _presentPicker(context), + currencyValueValidator: sendTemplateViewModel.amountValidator, + isAmountEditable: true)), + Divider( + height: 1, + color: Theme.of(context).extension()!.textFieldBorderColor) + ], + ), + ), + Focus( + onFocusChange: (hasFocus) { + if (hasFocus) template.setCryptoCurrency(false); + }, + child: Column( + children: [ + Observer( + builder: (context) => CurrencyAmountTextField( + selectedCurrency: sendTemplateViewModel.fiatCurrency, + amountFocusNode: _fiatAmountFocus, + amountController: _fiatAmountController, + isSelected: !template.isCryptoSelected, + hintText: '0.00', + isAmountEditable: true)), + Divider( + height: 1, + color: Theme.of(context).extension()!.textFieldBorderColor) + ], ), ), ], diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index 9bba51944..137f699f5 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -12,7 +13,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; class OtherSettingsPage extends BasePage { - OtherSettingsPage(this._otherSettingsViewModel); + OtherSettingsPage(this._otherSettingsViewModel) { + if (_otherSettingsViewModel.sendViewModel.isElectrumWallet) { + bitcoin!.updateFeeRates(_otherSettingsViewModel.sendViewModel.wallet); + } + } @override String get title => S.current.other_settings; 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 e067c78d0..b69a6d8df 100644 --- a/lib/src/screens/subaddress/address_edit_or_create_page.dart +++ b/lib/src/screens/subaddress/address_edit_or_create_page.dart @@ -58,7 +58,7 @@ class AddressEditOrCreatePage extends BasePage { isLoading: addressEditOrCreateViewModel.state is AddressIsSaving, isDisabled: - addressEditOrCreateViewModel.label?.isEmpty ?? true, + addressEditOrCreateViewModel.label.isEmpty, ), ) ], @@ -74,7 +74,9 @@ class AddressEditOrCreatePage extends BasePage { (AddressEditOrCreateState state) { if (state is AddressSavedSuccessfully) { WidgetsBinding.instance - .addPostFrameCallback((_) => Navigator.of(context).pop()); + .addPostFrameCallback((_) { + if (context.mounted) Navigator.of(context).pop(); + }); } }); diff --git a/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart b/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart index 7615065d7..db3d94500 100644 --- a/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart +++ b/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart @@ -17,7 +17,7 @@ class StandardPickerListItem extends TransactionDetailsListItem { final List items; final String Function(T item, double sliderValue) displayItem; final Function(double) onSliderChanged; - final Function(T) onItemSelected; + final Function(T item, double sliderValue) onItemSelected; final int selectedIdx; final double? maxValue; final int customItemIndex; diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart index 7734f37ed..d06b935dd 100644 --- a/lib/src/screens/transaction_details/transaction_details_page.dart +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; @@ -15,7 +16,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; class TransactionDetailsPage extends BasePage { - TransactionDetailsPage({required this.transactionDetailsViewModel}); + TransactionDetailsPage({required this.transactionDetailsViewModel}) { + if (transactionDetailsViewModel.sendViewModel.isElectrumWallet) { + bitcoin!.updateFeeRates(transactionDetailsViewModel.sendViewModel.wallet); + } + } @override String get title => S.current.transaction_details_title; diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index b744d1db0..a7cb03a4e 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -499,10 +499,10 @@ class _PickerState extends State> { children: [ Expanded( child: Slider( - value: widget.sliderValue ?? 1, + value: widget.sliderValue == null || widget.sliderValue! < 1 ? 1 : widget.sliderValue!, onChanged: isActivated ? widget.onSliderChanged : null, min: widget.minValue ?? 1, - max: widget.maxValue ?? 100, + max: (widget.maxValue == null || widget.maxValue! < 1) ? 100 : widget.maxValue!, divisions: 100, ), ), diff --git a/lib/src/widgets/standard_picker_list.dart b/lib/src/widgets/standard_picker_list.dart index ea8b07097..0e9831420 100644 --- a/lib/src/widgets/standard_picker_list.dart +++ b/lib/src/widgets/standard_picker_list.dart @@ -23,7 +23,7 @@ class StandardPickerList extends StatefulWidget { final int customItemIndex; final String Function(T item, double sliderValue) displayItem; final Function(double) onSliderChanged; - final Function(T) onItemSelected; + final Function(T item, double sliderValue) onItemSelected; final String value; final int selectedIdx; final double customValue; @@ -50,6 +50,7 @@ class _StandardPickerListState extends State> { @override Widget build(BuildContext context) { String adaptedDisplayItem(T item) => widget.displayItem(item, customValue); + String adaptedOnItemSelected(T item) => widget.onItemSelected(item, customValue).toString(); return Column( children: [ @@ -74,7 +75,7 @@ class _StandardPickerListState extends State> { }, onItemSelected: (T item) { setState(() => selectedIdx = widget.items.indexOf(item)); - value = widget.onItemSelected(item).toString(); + value = adaptedOnItemSelected(item); }, ), ), diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index c5c241d87..04b624e48 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -4,11 +4,13 @@ import 'package:cake_wallet/entities/preferences_key.dart'; 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_bar.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/services.dart'; import 'package:flutter_mailer/flutter_mailer.dart'; import 'package:cake_wallet/utils/package_info.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -254,4 +256,53 @@ class ExceptionHandler { 'productName': data.productName, }; } + + static void showError(String error, {int? delayInSeconds}) async { + if (_hasError) { + return; + } + _hasError = true; + + if (delayInSeconds != null) { + Future.delayed(Duration(seconds: delayInSeconds), () => _showCopyPopup(error)); + return; + } + + WidgetsBinding.instance.addPostFrameCallback( + (_) async => _showCopyPopup(error), + ); + } + + static Future _showCopyPopup(String content) async { + if (navigatorKey.currentContext != null) { + final shouldCopy = await showPopUp( + context: navigatorKey.currentContext!, + builder: (context) { + return AlertWithTwoActions( + isDividerExist: true, + alertTitle: S.of(context).error, + alertContent: content, + rightButtonText: S.of(context).copy, + leftButtonText: S.of(context).close, + actionRightButton: () { + Navigator.of(context).pop(true); + }, + actionLeftButton: () { + Navigator.of(context).pop(); + }, + ); + }, + ); + + if (shouldCopy == true) { + await Clipboard.setData(ClipboardData(text: content)); + await showBar( + navigatorKey.currentContext!, + S.of(navigatorKey.currentContext!).copied_to_clipboard, + ); + } + } + + _hasError = false; + } } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index d3527f997..1a0ef9477 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -11,6 +11,7 @@ import 'package:cake_wallet/entities/service_status.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/wownero/wownero.dart' as wow; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -341,6 +342,44 @@ abstract class DashboardViewModelBase with Store { wallet.type == WalletType.wownero || wallet.type == WalletType.haven; + @computed + String? get getMoneroError { + if (wallet.type != WalletType.monero) return null; + try { + monero!.monerocCheck(); + } catch (e) { + return e.toString(); + } + return null; + } + + @computed + String? get getWowneroError { + if (wallet.type != WalletType.wownero) return null; + try { + wow.wownero!.wownerocCheck(); + } catch (e) { + return e.toString(); + } + return null; + } + + List get isMoneroWalletBrokenReasons { + if (wallet.type != WalletType.monero) return []; + final keys = monero!.getKeys(wallet); + List errors = [ + if (keys['privateSpendKey'] == List.generate(64, (index) => "0").join("")) "Private spend key is 0", + if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("")) "private view key is 0", + if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0", + if (keys['publicViewKey'] == List.generate(64, (index) => "0").join("")) "private view key is 0", + if (wallet.seed == null) "wallet seed is null", + if (wallet.seed == "") "wallet seed is empty", + if (monero!.getSubaddressList(wallet).getAll(wallet)[0].address == "41d7FXjswpK1111111111111111111111111111111111111111111111111111111111111111111111111111112KhNi4") + "primary address is invalid, you won't be able to receive / spend funds", + ]; + return errors; + } + @computed bool get hasSilentPayments => wallet.type == WalletType.bitcoin && !wallet.isHardwareWallet; diff --git a/lib/view_model/send/send_template_view_model.dart b/lib/view_model/send/send_template_view_model.dart index 66a3c37c8..3c78f3000 100644 --- a/lib/view_model/send/send_template_view_model.dart +++ b/lib/view_model/send/send_template_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/view_model/send/template_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_type.dart'; @@ -97,4 +98,8 @@ abstract class SendTemplateViewModelBase with Store { @computed List get walletCurrencies => _wallet.balance.keys.toList(); + + bool get hasMultipleTokens => isEVMCompatibleChain(_wallet.type) || + _wallet.type == WalletType.solana || + _wallet.type == WalletType.tron; } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index a1997e81d..d0514bb19 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -118,7 +118,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed bool get isBatchSending => outputs.length > 1; - bool get shouldDisplaySendALL => walletType != WalletType.solana || walletType != WalletType.tron; + bool get shouldDisplaySendALL => walletType != WalletType.solana; @computed String get pendingTransactionFiatAmount { @@ -582,9 +582,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor ) { String errorMessage = error.toString(); + if (walletType == WalletType.solana) { + if (errorMessage.contains('insufficient funds for rent')) { + return S.current.insufficientFundsForRentError; + } + + return errorMessage; + } if (walletType == WalletType.ethereum || walletType == WalletType.polygon || - walletType == WalletType.solana || walletType == WalletType.haven) { if (errorMessage.contains('gas required exceeds allowance') || errorMessage.contains('insufficient funds')) { diff --git a/lib/view_model/send/template_view_model.dart b/lib/view_model/send/template_view_model.dart index 5b799c343..fcd40bd43 100644 --- a/lib/view_model/send/template_view_model.dart +++ b/lib/view_model/send/template_view_model.dart @@ -40,35 +40,22 @@ abstract class TemplateViewModelBase with Store { CryptoCurrency _currency; @observable - bool isCurrencySelected = true; - - @observable - bool isFiatSelected = false; + bool isCryptoSelected = true; @action - void selectCurrency() { - isCurrencySelected = true; - isFiatSelected = false; - } - - @action - void selectFiat() { - isFiatSelected = true; - isCurrencySelected = false; - } + void setCryptoCurrency(bool value) => isCryptoSelected = value; @action void reset() { name = ''; address = ''; - isCurrencySelected = true; - isFiatSelected = false; + isCryptoSelected = true; output.reset(); } Template toTemplate({required String cryptoCurrency, required String fiatCurrency}) { return Template( - isCurrencySelectedRaw: isCurrencySelected, + isCurrencySelectedRaw: isCryptoSelected, nameRaw: name, addressRaw: address, cryptoCurrencyRaw: cryptoCurrency, @@ -79,7 +66,7 @@ abstract class TemplateViewModelBase with Store { @action void changeSelectedCurrency(CryptoCurrency currency) { - isCurrencySelected = true; + isCryptoSelected = true; _currency = currency; } diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index bd04755fa..9af8c67cf 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -4,6 +4,7 @@ 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:cake_wallet/view_model/send/send_view_model.dart'; // import 'package:package_info/package_info.dart'; import 'package:collection/collection.dart'; import 'package:cw_core/balance.dart'; @@ -20,7 +21,7 @@ class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel; abstract class OtherSettingsViewModelBase with Store { - OtherSettingsViewModelBase(this._settingsStore, this._wallet) + OtherSettingsViewModelBase(this._settingsStore, this._wallet, this.sendViewModel) : walletType = _wallet.type, currentVersion = '' { PackageInfo.fromPlatform().then( @@ -42,6 +43,7 @@ abstract class OtherSettingsViewModelBase with Store { String currentVersion; final SettingsStore _settingsStore; + final SendViewModel sendViewModel; @computed TransactionPriority get transactionPriority { diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 9e71837a7..ef6474974 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -378,9 +378,9 @@ abstract class TransactionDetailsViewModelBase with Store { sendViewModel.displayFeeRate(priority, sliderValue.round()), onSliderChanged: (double newValue) => setNewFee(value: newValue, priority: transactionPriority!), - onItemSelected: (dynamic item) { + onItemSelected: (dynamic item, double sliderValue) { transactionPriority = item as TransactionPriority; - return setNewFee(priority: transactionPriority!); + return setNewFee(value: sliderValue, priority: transactionPriority!); })); if (transactionInfo.inputAddresses != null) { @@ -427,7 +427,11 @@ abstract class TransactionDetailsViewModelBase with Store { String setNewFee({double? value, required TransactionPriority priority}) { newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null - ? bitcoin!.getEstimatedFeeWithFeeRate(wallet, value.round(), transactionInfo.amount) + ? bitcoin!.feeAmountWithFeeRate( + wallet, + value.round(), + transactionInfo.inputAddresses?.length ?? 1, + transactionInfo.outputAddresses?.length ?? 1) : bitcoin!.getFeeAmountForPriority( wallet, priority, diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 511822601..1d5c27fed 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -83,8 +83,8 @@ abstract class WalletKeysViewModelBase with Store { StandartListItem( title: S.current.view_key_private, value: keys['privateViewKey']!), - StandartListItem( - title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + if (_appStore.wallet!.seed!.isNotEmpty) + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); if (_appStore.wallet?.seed != null && @@ -123,8 +123,8 @@ abstract class WalletKeysViewModelBase with Store { StandartListItem( title: S.current.view_key_private, value: keys['privateViewKey']!), - StandartListItem( - title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + if (_appStore.wallet!.seed!.isNotEmpty) + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); } @@ -147,8 +147,8 @@ abstract class WalletKeysViewModelBase with Store { StandartListItem( title: S.current.view_key_private, value: keys['privateViewKey']!), - StandartListItem( - title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + if (_appStore.wallet!.seed!.isNotEmpty) + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); if (_appStore.wallet?.seed != null && diff --git a/lib/wownero/cw_wownero.dart b/lib/wownero/cw_wownero.dart index 03bebc463..0e0b00fd4 100644 --- a/lib/wownero/cw_wownero.dart +++ b/lib/wownero/cw_wownero.dart @@ -347,4 +347,9 @@ class CWWownero extends Wownero { String getLegacySeed(Object wallet, String langName) => (wallet as WowneroWalletBase).seedLegacy(langName); + + @override + void wownerocCheck() { + checkIfMoneroCIsFine(); + } } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 18dfee0b0..3d63ab0ea 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -16,7 +16,7 @@ import in_app_review import package_info import package_info_plus import path_provider_foundation -import share_plus_macos +import share_plus import shared_preferences_foundation import url_launcher_macos import wakelock_plus diff --git a/pubspec_base.yaml b/pubspec_base.yaml index b374b88cf..4c4df60fc 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -21,7 +21,7 @@ dependencies: mobx: ^2.1.4 flutter_mobx: ^2.0.6+5 flutter_slidable: ^3.0.1 - share_plus: ^4.0.10 + share_plus: ^10.0.0 # date_range_picker: ^1.0.6 #https://api.flutter.dev/flutter/material/showDateRangePicker.html dio: ^4.0.6 @@ -66,7 +66,7 @@ dependencies: url: https://github.com/cake-tech/device_display_brightness.git ref: master workmanager: ^0.5.1 - wakelock_plus: ^1.1.3 + wakelock_plus: ^1.2.5 flutter_mailer: ^2.0.2 device_info_plus: ^9.1.0 base32: 2.1.3 @@ -87,10 +87,6 @@ dependencies: git: url: https://github.com/cake-tech/ens_dart.git ref: main - bitcoin_flutter: - git: - url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v4 fluttertoast: 8.1.4 # tor: # git: @@ -124,6 +120,7 @@ dev_dependencies: git: url: https://github.com/cake-tech/google-translator.git version: 1.0.0 + archive: ^3.6.1 dependency_overrides: bech32: @@ -132,7 +129,7 @@ dependency_overrides: ledger_flutter: git: url: https://github.com/cake-tech/ledger-flutter.git - ref: cake-stax + ref: cake-v3 web3dart: git: url: https://github.com/cake-tech/web3dart.git diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index ea9e6dbbc..8ba4888ee 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -337,6 +337,7 @@ "incoming": "الواردة", "incorrect_seed": "النص الذي تم إدخاله غير صالح.", "inputs": "المدخلات", + "insufficientFundsForRentError": "ليس لديك ما يكفي من SOL لتغطية رسوم المعاملة والإيجار للحساب. يرجى إضافة المزيد من sol إلى محفظتك أو تقليل مبلغ sol الذي ترسله", "introducing_cake_pay": "نقدم لكم Cake Pay!", "invalid_input": "مدخل غير صالح", "invoice_details": "تفاصيل الفاتورة", @@ -702,6 +703,7 @@ "sync_status_connecting": "يتم التوصيل", "sync_status_failed_connect": "انقطع الاتصال", "sync_status_not_connected": "غير متصل", + "sync_status_starting_scan": "بدء المسح", "sync_status_starting_sync": "بدء المزامنة", "sync_status_syncronized": "متزامن", "sync_status_syncronizing": "يتم المزامنة", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 5f3eeda16..af3f7cc4d 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -337,6 +337,7 @@ "incoming": "Входящи", "incorrect_seed": "Въведеният текст е невалиден.", "inputs": "Входове", + "insufficientFundsForRentError": "Нямате достатъчно SOL, за да покриете таксата за транзакцията и наемането на сметката. Моля, добавете повече SOL към портфейла си или намалете сумата на SOL, която изпращате", "introducing_cake_pay": "Запознайте се с Cake Pay!", "invalid_input": "Невалиден вход", "invoice_details": "IДанни за фактура", @@ -702,6 +703,7 @@ "sync_status_connecting": "СВЪРЗВАНЕ", "sync_status_failed_connect": "НЕУСПЕШНО СВЪРЗВАНЕ", "sync_status_not_connected": "НЯМА ВРЪЗКА", + "sync_status_starting_scan": "Стартово сканиране", "sync_status_starting_sync": "ЗАПОЧВАНЕ НА СИНХРОНИЗАЦИЯ", "sync_status_syncronized": "СИНХРОНИЗИРАНО", "sync_status_syncronizing": "СИНХРОНИЗИРАНЕ", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index b8f74aebf..032cfd0a5 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -337,6 +337,7 @@ "incoming": "Příchozí", "incorrect_seed": "Zadaný text není správný.", "inputs": "Vstupy", + "insufficientFundsForRentError": "Nemáte dostatek SOL na pokrytí transakčního poplatku a nájemného za účet. Laskavě přidejte do své peněženky více SOL nebo snižte množství Sol, kterou odesíláte", "introducing_cake_pay": "Představujeme Cake Pay!", "invalid_input": "Neplatný vstup", "invoice_details": "detaily faktury", @@ -702,6 +703,7 @@ "sync_status_connecting": "PŘIPOJOVÁNÍ", "sync_status_failed_connect": "ODPOJENO", "sync_status_not_connected": "NEPŘIPOJENO", + "sync_status_starting_scan": "Počáteční skenování", "sync_status_starting_sync": "SPOUŠTĚNÍ SYNCHRONIZACE", "sync_status_syncronized": "SYNCHRONIZOVÁNO", "sync_status_syncronizing": "SYNCHRONIZUJI", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 7c549887b..9f4f8caca 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -337,6 +337,7 @@ "incoming": "Eingehend", "incorrect_seed": "Der eingegebene Text ist ungültig.", "inputs": "Eingänge", + "insufficientFundsForRentError": "Sie haben nicht genug SOL, um die Transaktionsgebühr und die Miete für das Konto zu decken. Bitte fügen Sie mehr Sol zu Ihrer Brieftasche hinzu oder reduzieren Sie den von Ihnen gesendeten Sol -Betrag", "introducing_cake_pay": "Einführung von Cake Pay!", "invalid_input": "Ungültige Eingabe", "invoice_details": "Rechnungs-Details", @@ -703,6 +704,7 @@ "sync_status_connecting": "VERBINDEN", "sync_status_failed_connect": "GETRENNT", "sync_status_not_connected": "NICHT VERBUNDEN", + "sync_status_starting_scan": "Scan beginnen", "sync_status_starting_sync": "STARTE SYNCHRONISIERUNG", "sync_status_syncronized": "SYNCHRONISIERT", "sync_status_syncronizing": "SYNCHRONISIERE", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index a2634e964..472fa33eb 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -337,6 +337,7 @@ "incoming": "Incoming", "incorrect_seed": "The text entered is not valid.", "inputs": "Inputs", + "insufficientFundsForRentError": "You do not have enough SOL to cover the transaction fee and rent for the account. Kindly add more SOL to your wallet or reduce the SOL amount you\\'re sending", "introducing_cake_pay": "Introducing Cake Pay!", "invalid_input": "Invalid input", "invoice_details": "Invoice details", @@ -703,6 +704,7 @@ "sync_status_connecting": "CONNECTING", "sync_status_failed_connect": "DISCONNECTED", "sync_status_not_connected": "NOT CONNECTED", + "sync_status_starting_scan": "STARTING SCAN", "sync_status_starting_sync": "STARTING SYNC", "sync_status_syncronized": "SYNCHRONIZED", "sync_status_syncronizing": "SYNCHRONIZING", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 540343a5c..b26434e43 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -337,6 +337,7 @@ "incoming": "Entrante", "incorrect_seed": "El texto ingresado no es válido.", "inputs": "Entradas", + "insufficientFundsForRentError": "No tiene suficiente SOL para cubrir la tarifa de transacción y alquilar para la cuenta. Por favor, agregue más sol a su billetera o reduzca la cantidad de sol que está enviando", "introducing_cake_pay": "¡Presentamos Cake Pay!", "invalid_input": "Entrada inválida", "invoice_details": "Detalles de la factura", @@ -703,6 +704,7 @@ "sync_status_connecting": "CONECTANDO", "sync_status_failed_connect": "DESCONECTADO", "sync_status_not_connected": "NO CONECTADO", + "sync_status_starting_scan": "Escaneo inicial", "sync_status_starting_sync": "EMPEZANDO A SINCRONIZAR", "sync_status_syncronized": "SINCRONIZADO", "sync_status_syncronizing": "SINCRONIZANDO", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 1583b4ac7..f052b1781 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -337,6 +337,7 @@ "incoming": "Entrantes", "incorrect_seed": "Le texte entré est invalide.", "inputs": "Contributions", + "insufficientFundsForRentError": "Vous n'avez pas assez de SOL pour couvrir les frais de transaction et le loyer pour le compte. Veuillez ajouter plus de Sol à votre portefeuille ou réduire la quantité de sol que vous envoyez", "introducing_cake_pay": "Présentation de Cake Pay !", "invalid_input": "Entrée invalide", "invoice_details": "Détails de la facture", @@ -702,6 +703,7 @@ "sync_status_connecting": "CONNEXION EN COURS", "sync_status_failed_connect": "DÉCONNECTÉ", "sync_status_not_connected": "NON CONNECTÉ", + "sync_status_starting_scan": "Démarrage", "sync_status_starting_sync": "DÉBUT DE SYNCHRO", "sync_status_syncronized": "SYNCHRONISÉ", "sync_status_syncronizing": "SYNCHRONISATION EN COURS", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index e90913cb9..ef67fac7d 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -337,6 +337,7 @@ "incoming": "Mai shigowa", "incorrect_seed": "rubutun da aka shigar ba shi da inganci.", "inputs": "Abubuwan da ke ciki", + "insufficientFundsForRentError": "Ba ku da isasshen Sol don rufe kuɗin ma'amala da haya don asusun. Da kyau ƙara ƙarin sool zuwa walat ɗinku ko rage adadin Sol ɗin da kuke aikawa", "introducing_cake_pay": "Gabatar da Cake Pay!", "invalid_input": "Shigar da ba daidai ba", "invoice_details": "Bayanin wadannan", @@ -704,6 +705,7 @@ "sync_status_connecting": "HADA", "sync_status_failed_connect": "BABU INTERNET", "sync_status_not_connected": "BABU INTERNET", + "sync_status_starting_scan": "Fara scan", "sync_status_starting_sync": "KWAFI", "sync_status_syncronized": "KYAU", "sync_status_syncronizing": "KWAFI", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index b8261107b..dc684e5c7 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -337,6 +337,7 @@ "incoming": "आने वाली", "incorrect_seed": "दर्ज किया गया पाठ मान्य नहीं है।", "inputs": "इनपुट", + "insufficientFundsForRentError": "आपके पास लेन -देन शुल्क और खाते के लिए किराए को कवर करने के लिए पर्याप्त सोल नहीं है। कृपया अपने बटुए में अधिक सोल जोड़ें या सोल राशि को कम करें जिसे आप भेज रहे हैं", "introducing_cake_pay": "परिचय Cake Pay!", "invalid_input": "अमान्य निवेश", "invoice_details": "चालान विवरण", @@ -704,6 +705,7 @@ "sync_status_connecting": "कनेक्ट", "sync_status_failed_connect": "डिस्कनेक्ट किया गया", "sync_status_not_connected": "जुड़े नहीं हैं", + "sync_status_starting_scan": "स्कैन शुरू करना", "sync_status_starting_sync": "सिताज़ा करना", "sync_status_syncronized": "सिंक्रनाइज़", "sync_status_syncronizing": "सिंक्रनाइज़ करने", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index bcc4940b5..f44f65ae6 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -337,6 +337,7 @@ "incoming": "Dolazno", "incorrect_seed": "Uneseni tekst nije valjan.", "inputs": "Unosi", + "insufficientFundsForRentError": "Nemate dovoljno SOL -a za pokrivanje naknade za transakciju i najamninu za račun. Ljubazno dodajte više sol u svoj novčanik ili smanjite količinu SOL -a koju šaljete", "introducing_cake_pay": "Predstavljamo Cake Pay!", "invalid_input": "Pogrešan unos", "invoice_details": "Podaci o fakturi", @@ -702,6 +703,7 @@ "sync_status_connecting": "SPAJANJE", "sync_status_failed_connect": "ISKLJUČENO", "sync_status_not_connected": "NIJE POVEZANO", + "sync_status_starting_scan": "Početno skeniranje", "sync_status_starting_sync": "ZAPOČINJEMO SINKRONIZIRANJE", "sync_status_syncronized": "SINKRONIZIRANO", "sync_status_syncronizing": "SINKRONIZIRANJE", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 57bb18e28..613799b06 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -337,6 +337,7 @@ "incoming": "Masuk", "incorrect_seed": "Teks yang dimasukkan tidak valid.", "inputs": "Input", + "insufficientFundsForRentError": "Anda tidak memiliki cukup SOL untuk menutupi biaya transaksi dan menyewa untuk akun tersebut. Mohon tambahkan lebih banyak sol ke dompet Anda atau kurangi jumlah sol yang Anda kirim", "introducing_cake_pay": "Perkenalkan Cake Pay!", "invalid_input": "Masukan tidak valid", "invoice_details": "Detail faktur", @@ -705,6 +706,7 @@ "sync_status_connecting": "MENGHUBUNGKAN", "sync_status_failed_connect": "GAGAL TERHUBUNG", "sync_status_not_connected": "TIDAK TERHUBUNG", + "sync_status_starting_scan": "Mulai pindai", "sync_status_starting_sync": "MULAI SINKRONISASI", "sync_status_syncronized": "SUDAH TERSINKRONISASI", "sync_status_syncronizing": "SEDANG SINKRONISASI", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index c72c8c076..c64763536 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -338,6 +338,7 @@ "incoming": "In arrivo", "incorrect_seed": "Il testo inserito non è valido.", "inputs": "Input", + "insufficientFundsForRentError": "Non hai abbastanza SOL per coprire la tassa di transazione e l'affitto per il conto. Si prega di aggiungere più SOL al tuo portafoglio o ridurre l'importo SOL che stai inviando", "introducing_cake_pay": "Presentazione di Cake Pay!", "invalid_input": "Inserimento non valido", "invoice_details": "Dettagli della fattura", @@ -704,6 +705,7 @@ "sync_status_connecting": "CONNESSIONE", "sync_status_failed_connect": "DISCONNESSO", "sync_status_not_connected": "NON CONNESSO", + "sync_status_starting_scan": "Scansione di partenza", "sync_status_starting_sync": "INIZIO SINC", "sync_status_syncronized": "SINCRONIZZATO", "sync_status_syncronizing": "SINCRONIZZAZIONE", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 407135aa0..0ce96f01d 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -338,6 +338,7 @@ "incoming": "着信", "incorrect_seed": "入力されたテキストは無効です。", "inputs": "入力", + "insufficientFundsForRentError": "アカウントの取引料金とレンタルをカバーするのに十分なソルがありません。財布にソルを追加するか、送信するソル量を減らしてください", "introducing_cake_pay": "序章Cake Pay!", "invalid_input": "無効入力", "invoice_details": "請求の詳細", @@ -703,6 +704,7 @@ "sync_status_connecting": "接続中", "sync_status_failed_connect": "切断されました", "sync_status_not_connected": "接続されていません", + "sync_status_starting_scan": "スキャンを開始します", "sync_status_starting_sync": "同期の開始", "sync_status_syncronized": "同期された", "sync_status_syncronizing": "同期", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index e548c1f7a..621ea2b47 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -337,6 +337,7 @@ "incoming": "들어오는", "incorrect_seed": "입력하신 텍스트가 유효하지 않습니다.", "inputs": "입력", + "insufficientFundsForRentError": "거래 수수료와 계좌 임대료를 충당하기에 충분한 SOL이 없습니다. 지갑에 더 많은 솔을 추가하거나 보내는 솔을 줄이십시오.", "introducing_cake_pay": "소개 Cake Pay!", "invalid_input": "잘못된 입력", "invoice_details": "인보이스 세부정보", @@ -703,6 +704,7 @@ "sync_status_connecting": "연결 중", "sync_status_failed_connect": "연결 해제", "sync_status_not_connected": "연결되지 않은", + "sync_status_starting_scan": "스캔 시작", "sync_status_starting_sync": "동기화 시작", "sync_status_syncronized": "동기화", "sync_status_syncronizing": "동기화", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 352fffac4..284fc2b2f 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -337,6 +337,7 @@ "incoming": "ဝင်လာ", "incorrect_seed": "ထည့်သွင်းထားသော စာသားသည် မမှန်ကန်ပါ။", "inputs": "သွင်းငေှ", + "insufficientFundsForRentError": "သင်ငွေပေးချေမှုအခကြေးငွေကိုဖုံးအုပ်ရန်နှင့်အကောင့်ငှားရန်လုံလောက်သော sol ရှိသည်မဟုတ်ကြဘူး။ ကြင်နာစွာသင်၏ပိုက်ဆံအိတ်သို့ပိုမို sol ကိုပိုမိုထည့်ပါသို့မဟုတ်သင်ပို့ခြင်း sol ပမာဏကိုလျှော့ချပါ", "introducing_cake_pay": "Cake Pay ကို မိတ်ဆက်ခြင်း။", "invalid_input": "ထည့်သွင်းမှု မမှန်ကန်ပါ။", "invoice_details": "ပြေစာအသေးစိတ်", @@ -702,6 +703,7 @@ "sync_status_connecting": "ချိတ်ဆက်ခြင်း။", "sync_status_failed_connect": "အဆက်အသွယ်ဖြတ်ထားသည်။", "sync_status_not_connected": "မချိတ်ဆက်ပါ။", + "sync_status_starting_scan": "စကင်ဖတ်စစ်ဆေးမှု", "sync_status_starting_sync": "စင့်ခ်လုပ်ခြင်း။", "sync_status_syncronized": "ထပ်တူပြုထားသည်။", "sync_status_syncronizing": "ထပ်တူပြုခြင်း။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 6f8e4a0ff..4b3faade0 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -337,6 +337,7 @@ "incoming": "inkomend", "incorrect_seed": "De ingevoerde tekst is niet geldig.", "inputs": "Invoer", + "insufficientFundsForRentError": "U hebt niet genoeg SOL om de transactiekosten en huur voor de rekening te dekken. Voeg vriendelijk meer SOL toe aan uw portemonnee of verminder de SOL -hoeveelheid die u verzendt", "introducing_cake_pay": "Introductie van Cake Pay!", "invalid_input": "Ongeldige invoer", "invoice_details": "Factuurgegevens", @@ -702,6 +703,7 @@ "sync_status_connecting": "AANSLUITING", "sync_status_failed_connect": "LOSGEKOPPELD", "sync_status_not_connected": "NIET VERBONDEN", + "sync_status_starting_scan": "Startscan", "sync_status_starting_sync": "BEGINNEN MET SYNCHRONISEREN", "sync_status_syncronized": "SYNCHRONIZED", "sync_status_syncronizing": "SYNCHRONISEREN", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 3476ba454..f67ea9870 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -337,6 +337,7 @@ "incoming": "Przychodzące", "incorrect_seed": "Wprowadzony seed jest nieprawidłowy.", "inputs": "Wejścia", + "insufficientFundsForRentError": "Nie masz wystarczającej ilości SOL, aby pokryć opłatę za transakcję i czynsz za konto. Uprzejmie dodaj więcej sol do portfela lub zmniejsz solę, którą wysyłasz", "introducing_cake_pay": "Przedstawiamy Cake Pay!", "invalid_input": "Nieprawidłowe dane wejściowe", "invoice_details": "Dane do faktury", @@ -702,6 +703,7 @@ "sync_status_connecting": "ŁĄCZENIE", "sync_status_failed_connect": "POŁĄCZENIE NIEUDANE", "sync_status_not_connected": "NIE POŁĄCZONY", + "sync_status_starting_scan": "Rozpoczęcie skanowania", "sync_status_starting_sync": "ROZPOCZĘCIE SYNCHRONIZACJI", "sync_status_syncronized": "ZSYNCHRONIZOWANO", "sync_status_syncronizing": "SYNCHRONIZACJA", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 48831c79b..6ff347323 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -337,6 +337,7 @@ "incoming": "Recebidas", "incorrect_seed": "O texto digitado não é válido.", "inputs": "Entradas", + "insufficientFundsForRentError": "Você não tem Sol suficiente para cobrir a taxa de transação e o aluguel da conta. Por favor, adicione mais sol à sua carteira ou reduza a quantidade de sol que você envia", "introducing_cake_pay": "Apresentando o Cake Pay!", "invalid_input": "Entrada inválida", "invoice_details": "Detalhes da fatura", @@ -704,6 +705,7 @@ "sync_status_connecting": "CONECTANDO", "sync_status_failed_connect": "DESCONECTADO", "sync_status_not_connected": "DESCONECTADO", + "sync_status_starting_scan": "Diretor inicial", "sync_status_starting_sync": "INICIANDO SINCRONIZAÇÃO", "sync_status_syncronized": "SINCRONIZADO", "sync_status_syncronizing": "SINCRONIZANDO", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 4b0d6325c..6d2fe975a 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -337,6 +337,7 @@ "incoming": "Входящие", "incorrect_seed": "Введённый текст некорректный.", "inputs": "Входы", + "insufficientFundsForRentError": "У вас недостаточно Sol, чтобы покрыть плату за транзакцию и аренду для счета. Пожалуйста, добавьте больше Sol в свой кошелек или уменьшите сумму Sol, которую вы отправляете", "introducing_cake_pay": "Представляем Cake Pay!", "invalid_input": "Неверный Ввод", "invoice_details": "Детали счета", @@ -703,6 +704,7 @@ "sync_status_connecting": "ПОДКЛЮЧЕНИЕ", "sync_status_failed_connect": "ОТКЛЮЧЕНО", "sync_status_not_connected": "НЕ ПОДКЛЮЧЁН", + "sync_status_starting_scan": "Начальное сканирование", "sync_status_starting_sync": "НАЧАЛО СИНХРОНИЗАЦИИ", "sync_status_syncronized": "СИНХРОНИЗИРОВАН", "sync_status_syncronizing": "СИНХРОНИЗАЦИЯ", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 8b424a78e..119f9ab0e 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -337,6 +337,7 @@ "incoming": "ขาเข้า", "incorrect_seed": "ข้อความที่ป้อนไม่ถูกต้อง", "inputs": "อินพุต", + "insufficientFundsForRentError": "คุณไม่มีโซลเพียงพอที่จะครอบคลุมค่าธรรมเนียมการทำธุรกรรมและค่าเช่าสำหรับบัญชี กรุณาเพิ่มโซลให้มากขึ้นลงในกระเป๋าเงินของคุณหรือลดจำนวนโซลที่คุณส่งมา", "introducing_cake_pay": "ยินดีต้อนรับสู่ Cake Pay!", "invalid_input": "อินพุตไม่ถูกต้อง", "invoice_details": "รายละเอียดใบแจ้งหนี้", @@ -702,6 +703,7 @@ "sync_status_connecting": "กำลังเชื่อมต่อ", "sync_status_failed_connect": "การเชื่อมต่อล้มเหลว", "sync_status_not_connected": "ไม่ได้เชื่อมต่อ", + "sync_status_starting_scan": "เริ่มการสแกน", "sync_status_starting_sync": "กำลังเริ่มซิงโครไนซ์", "sync_status_syncronized": "ซิงโครไนซ์แล้ว", "sync_status_syncronizing": "กำลังซิงโครไนซ์", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index ddf073cfb..8bc319c0a 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -337,6 +337,7 @@ "incoming": "Papasok", "incorrect_seed": "Ang teksto na ipinasok ay hindi wasto.", "inputs": "Mga input", + "insufficientFundsForRentError": "Wala kang sapat na sol upang masakop ang bayad sa transaksyon at upa para sa account. Mabait na magdagdag ng higit pa sa iyong pitaka o bawasan ang halaga ng sol na iyong ipinapadala", "introducing_cake_pay": "Ipinakikilala ang cake pay!", "invalid_input": "Di -wastong input", "invoice_details": "Mga detalye ng invoice", @@ -702,6 +703,7 @@ "sync_status_connecting": "Pagkonekta", "sync_status_failed_connect": "Naka -disconnect", "sync_status_not_connected": "HINDI KONEKTADO", + "sync_status_starting_scan": "Simula sa pag -scan", "sync_status_starting_sync": "Simula sa pag -sync", "sync_status_syncronized": "Naka -synchronize", "sync_status_syncronizing": "Pag -synchronize", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 4990500a2..22b0e8f82 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -337,6 +337,7 @@ "incoming": "Gelen", "incorrect_seed": "Girilen metin geçerli değil.", "inputs": "Girişler", + "insufficientFundsForRentError": "İşlem ücretini karşılamak ve hesap için kiralamak için yeterli SOL'nuz yok. Lütfen cüzdanınıza daha fazla sol ekleyin veya gönderdiğiniz sol miktarını azaltın", "introducing_cake_pay": "Cake Pay ile tanışın!", "invalid_input": "Geçersiz Giriş", "invoice_details": "fatura detayları", @@ -702,6 +703,7 @@ "sync_status_connecting": "BAĞLANILIYOR", "sync_status_failed_connect": "BAĞLANTI KESİLDİ", "sync_status_not_connected": "BAĞLI DEĞİL", + "sync_status_starting_scan": "Başlangıç ​​taraması", "sync_status_starting_sync": "SENKRONİZE BAŞLATILIYOR", "sync_status_syncronized": "SENKRONİZE EDİLDİ", "sync_status_syncronizing": "SENKRONİZE EDİLİYOR", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index bb033d284..9d2269149 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -337,6 +337,7 @@ "incoming": "Вхідні", "incorrect_seed": "Введений текст невірний.", "inputs": "Вхoди", + "insufficientFundsForRentError": "У вас недостатньо SOL, щоб покрити плату за транзакцію та оренду на рахунок. Будь ласка, додайте до свого гаманця більше SOL або зменшіть суму, яку ви надсилаєте", "introducing_cake_pay": "Представляємо Cake Pay!", "invalid_input": "Неправильні дані", "invoice_details": "Реквізити рахунку-фактури", @@ -703,6 +704,7 @@ "sync_status_connecting": "ПІДКЛЮЧЕННЯ", "sync_status_failed_connect": "ВІДКЛЮЧЕНО", "sync_status_not_connected": "НЕ ПІДКЛЮЧЕННИЙ", + "sync_status_starting_scan": "Початок сканування", "sync_status_starting_sync": "ПОЧАТОК СИНХРОНІЗАЦІЇ", "sync_status_syncronized": "СИНХРОНІЗОВАНИЙ", "sync_status_syncronizing": "СИНХРОНІЗАЦІЯ", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 2e18e2a3f..6730e5577 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -337,6 +337,7 @@ "incoming": "آنے والا", "incorrect_seed": "درج کردہ متن درست نہیں ہے۔", "inputs": "آدانوں", + "insufficientFundsForRentError": "آپ کے پاس ٹرانزیکشن فیس اور اکاؤنٹ کے لئے کرایہ لینے کے ل enough اتنا SOL نہیں ہے۔ برائے مہربانی اپنے بٹوے میں مزید سول شامل کریں یا آپ کو بھیجنے والی سول رقم کو کم کریں", "introducing_cake_pay": "Cake پے کا تعارف!", "invalid_input": "غلط ان پٹ", "invoice_details": "رسید کی تفصیلات", @@ -704,6 +705,7 @@ "sync_status_connecting": "جڑ رہا ہے۔", "sync_status_failed_connect": "منقطع", "sync_status_not_connected": "منسلک نہیں", + "sync_status_starting_scan": "اسکین شروع کرنا", "sync_status_starting_sync": "مطابقت پذیری شروع کر رہا ہے۔", "sync_status_syncronized": "مطابقت پذیر", "sync_status_syncronizing": "مطابقت پذیری", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 4ff1abb06..f08855a77 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -338,6 +338,7 @@ "incoming": "Wọ́n tó ń bọ̀", "incorrect_seed": "Ọ̀rọ̀ tí a tẹ̀ kì í ṣe èyí.", "inputs": "Igbewọle", + "insufficientFundsForRentError": "O ko ni Sol kan lati bo owo isanwo naa ki o yalo fun iroyin naa. Fi agbara kun Sol diẹ sii si apamọwọ rẹ tabi dinku soso naa ti o \\ 'tun n firanṣẹ", "introducing_cake_pay": "Ẹ bá Cake Pay!", "invalid_input": "Iṣawọle ti ko tọ", "invoice_details": "Iru awọn ẹya ọrọ", @@ -703,6 +704,7 @@ "sync_status_connecting": "Ń DÁRAPỌ̀ MỌ́", "sync_status_failed_connect": "ÌKÀNPỌ̀ TI KÚ", "sync_status_not_connected": "KÒ TI DÁRAPỌ̀ MỌ́ Ọ", + "sync_status_starting_scan": "Bibẹrẹ ọlọjẹ", "sync_status_starting_sync": "Ń BẸ̀RẸ̀ RẸ́", "sync_status_syncronized": "TI MÚDỌ́GBA", "sync_status_syncronizing": "Ń MÚDỌ́GBA", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index d0d8b016e..1f01662bd 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -337,6 +337,7 @@ "incoming": "收到", "incorrect_seed": "输入的文字无效。", "inputs": "输入", + "insufficientFundsForRentError": "您没有足够的溶胶来支付该帐户的交易费和租金。请在钱包中添加更多溶胶或减少您发送的溶胶量", "introducing_cake_pay": "介绍 Cake Pay!", "invalid_input": "输入无效", "invoice_details": "发票明细", @@ -702,6 +703,7 @@ "sync_status_connecting": "连接中", "sync_status_failed_connect": "断线", "sync_status_not_connected": "未连接", + "sync_status_starting_scan": "开始扫描", "sync_status_starting_sync": "开始同步", "sync_status_syncronized": "已同步", "sync_status_syncronizing": "正在同步", diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index a270afab0..35444dcd5 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.16.1" -MONERO_COM_BUILD_NUMBER=95 +MONERO_COM_VERSION="1.16.2" +MONERO_COM_BUILD_NUMBER=96 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.19.1" -CAKEWALLET_BUILD_NUMBER=221 +CAKEWALLET_VERSION="4.19.2" +CAKEWALLET_BUILD_NUMBER=223 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 22daba5de..30573035a 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.16.1" -MONERO_COM_BUILD_NUMBER=93 +MONERO_COM_VERSION="1.16.2" +MONERO_COM_BUILD_NUMBER=94 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.19.1" -CAKEWALLET_BUILD_NUMBER=256 +CAKEWALLET_VERSION="4.19.2" +CAKEWALLET_BUILD_NUMBER=261 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index d46900405..2f6d51a93 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -16,13 +16,13 @@ if [ -n "$1" ]; then fi MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.6.1" -MONERO_COM_BUILD_NUMBER=26 +MONERO_COM_VERSION="1.6.2" +MONERO_COM_BUILD_NUMBER=27 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.12.1" -CAKEWALLET_BUILD_NUMBER=82 +CAKEWALLET_VERSION="1.12.2" +CAKEWALLET_BUILD_NUMBER=83 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/scripts/macos/build_all.sh b/scripts/macos/build_all.sh index 4116704bf..030617f7d 100755 --- a/scripts/macos/build_all.sh +++ b/scripts/macos/build_all.sh @@ -1,3 +1,3 @@ #!/bin/sh -./build_monero_all.sh \ No newline at end of file +./build_monero_all.sh universal \ No newline at end of file diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh index cac5d3ad2..2e53a54ea 100755 --- a/scripts/prepare_moneroc.sh +++ b/scripts/prepare_moneroc.sh @@ -8,7 +8,7 @@ if [[ ! -d "monero_c" ]]; then git clone https://github.com/mrcyjanek/monero_c --branch rewrite-wip cd monero_c - git checkout c094ed5da69d2274747bf6edd7ca24124487bd34 + git checkout bcb328a4956105dc182afd0ce2e48fe263f5f20b git reset --hard git submodule update --init --force --recursive ./apply_patches.sh monero diff --git a/tool/configure.dart b/tool/configure.dart index 58b0971c4..c2802e500 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -94,12 +94,11 @@ import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter/ledger_flutter.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as btc; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:bip39/bip39.dart' as bip39; """; const bitcoinCWHeaders = """ import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_bitcoin/litecoin_network.dart'; import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; @@ -212,6 +211,7 @@ abstract class Bitcoin { int getFeeAmountForPriority(Object wallet, TransactionPriority priority, int inputsCount, int outputsCount, {int? size}); int getEstimatedFeeWithFeeRate(Object wallet, int feeRate, int? amount, {int? outputsCount, int? size}); + int feeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, {int? size}); int getHeightByDate({required DateTime date}); Future rescan(Object wallet, {required int height, bool? doSingleScan}); Future getNodeIsElectrsSPEnabled(Object wallet); @@ -267,6 +267,7 @@ 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/api/wallet_manager.dart'; import 'package:cw_monero/monero_wallet.dart'; import 'package:cw_monero/monero_transaction_info.dart'; import 'package:cw_monero/monero_transaction_creation_credentials.dart'; @@ -382,6 +383,7 @@ abstract class Monero { double formatterMoneroAmountToDouble({required int amount}); int formatterMoneroParseAmount({required String amount}); Account getCurrentAccount(Object wallet); + void monerocCheck(); void setCurrentAccount(Object wallet, int id, String label, String? balance); void onStartup(); int getTransactionInfoAccountId(TransactionInfo tx); @@ -454,6 +456,7 @@ import 'package:cw_wownero/wownero_transaction_info.dart'; import 'package:cw_wownero/wownero_transaction_creation_credentials.dart'; import 'package:cw_core/account.dart' as wownero_account; import 'package:cw_wownero/api/wallet.dart' as wownero_wallet_api; +import 'package:cw_wownero/api/wallet_manager.dart'; import 'package:cw_wownero/mnemonics/english.dart'; import 'package:cw_wownero/mnemonics/chinese_simplified.dart'; import 'package:cw_wownero/mnemonics/dutch.dart'; @@ -545,6 +548,7 @@ abstract class Wownero { Future updateUnspents(Object wallet); Future getCurrentHeight(); + void wownerocCheck(); WalletCredentials createWowneroRestoreWalletFromKeysCredentials({ required String name, diff --git a/tool/download_moneroc_prebuilds.dart b/tool/download_moneroc_prebuilds.dart new file mode 100644 index 000000000..58e8d4b03 --- /dev/null +++ b/tool/download_moneroc_prebuilds.dart @@ -0,0 +1,50 @@ +import 'package:dio/dio.dart'; +import 'package:archive/archive_io.dart'; + +final _dio = Dio(); + +final List triplets = [ + "x86_64-linux-gnu", // linux desktop - majority of users onlinux + // "i686-linux-gnu", // not supported by cake + // "i686-meego-linux-gnu", // sailfishos (emulator)- not supported by cake + // "aarch64-linux-gnu", // not (yet) supported by cake - (mostly) mobile linux + // "aarch64-meego-linux-gnu", // sailfishos - not supported by cake + "x86_64-linux-android", + // "i686-linux-android", // not supported by monero_c - mostly old android emulators + "aarch64-linux-android", + "armv7a-linux-androideabi", + // "i686-w64-mingw32", // 32bit windows - not supported by monero_c + "x86_64-w64-mingw32", + // "x86_64-apple-darwin11", // Intel macbooks (contrib) - not used by cake + // "aarch64-apple-darwin11", // apple silicon macbooks (contrib) - not used by cake + // "host-apple-darwin", // not available on CI (yet) + // "x86_64-host-apple-darwin", // not available on CI (yet) + "aarch64-host-apple-darwin", // apple silicon macbooks (local builds) + "host-apple-ios", +]; + +Future main() async { + final resp = await _dio.get("https://api.github.com/repos/mrcyjanek/monero_c/releases"); + final data = resp.data[0]; + final tagName = data['tag_name']; + print("Downloading artifacts for: ${tagName}"); + final assets = data['assets'] as List; + for (var i = 0; i < assets.length; i++) { + for (var triplet in triplets) { + final asset = assets[i]; + final filename = asset["name"] as String; + if (!filename.contains(triplet)) continue; + final coin = filename.split("_")[0]; + String localFilename = filename.replaceAll("${coin}_${triplet}_", ""); + localFilename = "scripts/monero_c/release/${coin}/${triplet}_${localFilename}"; + final url = asset["browser_download_url"] as String; + print("- downloading $localFilename"); + await _dio.download(url, localFilename); + print(" extracting $localFilename"); + final inputStream = InputFileStream(localFilename); + final archive = XZDecoder().decodeBuffer(inputStream); + final outputStream = OutputFileStream(localFilename.replaceAll(".xz", "")); + outputStream.writeBytes(archive); + } + } +} \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 323f53c9f..c6444e09c 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index d6d9b0a49..0a0b2f9eb 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_local_authentication flutter_secure_storage_windows permission_handler_windows + share_plus url_launcher_windows )