diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 51bc83ce0..b299c9340 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -171,8 +171,8 @@ jobs: echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart - echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart @@ -185,6 +185,7 @@ jobs: echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index d98c0b77b..cdd0e40b4 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -182,8 +182,8 @@ jobs: echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart - echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart @@ -197,6 +197,7 @@ jobs: echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index f690e0236..891327d1e 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -154,8 +154,8 @@ jobs: echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart - echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart @@ -167,6 +167,7 @@ jobs: echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart diff --git a/assets/solana_node_list.yml b/assets/solana_node_list.yml index c96b370a8..3ba74d980 100644 --- a/assets/solana_node_list.yml +++ b/assets/solana_node_list.yml @@ -7,4 +7,7 @@ - uri: solana-rpc.publicnode.com:443 useSSL: true +- + uri: solana-mainnet.core.chainstack.com + useSSL: true is_default: true \ No newline at end of file diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 4fc4c1ad8..1f5c369e3 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -235,7 +235,7 @@ class ElectrumClient { return []; }); - Future>> getListUnspent(String scriptHash) async { + Future>?> getListUnspent(String scriptHash) async { final result = await call(method: 'blockchain.scripthash.listunspent', params: [scriptHash]); if (result is List) { @@ -248,7 +248,7 @@ class ElectrumClient { }).toList(); } - return []; + return null; } Future>> getMempool(String scriptHash) => diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 64eafb021..eae830db1 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -478,6 +478,7 @@ abstract class ElectrumWalletBase if (alwaysScan == true) { _setListeners(walletInfo.restoreHeight); } else { + if (syncStatus is LostConnectionSyncStatus) return; syncStatus = SyncedSyncStatus(); } } catch (e, stacktrace) { @@ -1361,6 +1362,10 @@ abstract class ElectrumWalletBase Future updateAllUnspents() async { List updatedUnspentCoins = []; + final previousUnspentCoins = List.from(unspentCoins.where((utxo) => + utxo.bitcoinAddressRecord.type != SegwitAddresType.mweb && + utxo.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)); + if (hasSilentPaymentsScanning) { // Update unspents stored from scanned silent payment transactions transactionHistory.transactions.values.forEach((tx) { @@ -1377,13 +1382,27 @@ abstract class ElectrumWalletBase if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0; }); - await Future.wait(walletAddresses.allAddresses + final addressFutures = walletAddresses.allAddresses .where((element) => element.type != SegwitAddresType.mweb) - .map((address) async { - updatedUnspentCoins.addAll(await fetchUnspent(address)); - })); + .map((address) => fetchUnspent(address)) + .toList(); - unspentCoins = updatedUnspentCoins; + final results = await Future.wait(addressFutures); + final failedCount = results.where((result) => result == null).length; + + if (failedCount == 0) { + for (final result in results) { + updatedUnspentCoins.addAll(result!); + } + unspentCoins = updatedUnspentCoins; + } else { + unspentCoins = handleFailedUtxoFetch( + failedCount: failedCount, + previousUnspentCoins: previousUnspentCoins, + updatedUnspentCoins: updatedUnspentCoins, + results: results, + ); + } final currentWalletUnspentCoins = unspentCoinsInfo.values.where((element) => element.walletId == id); @@ -1396,6 +1415,38 @@ abstract class ElectrumWalletBase await _refreshUnspentCoinsInfo(); } + List handleFailedUtxoFetch({ + required int failedCount, + required List previousUnspentCoins, + required List updatedUnspentCoins, + required List?> results, + }) { + + if (failedCount == results.length) { + printV("All UTXOs failed to fetch, falling back to previous UTXOs"); + return previousUnspentCoins; + } + + final successfulUtxos = []; + for (final result in results) { + if (result != null) { + successfulUtxos.addAll(result); + } + } + + if (failedCount > 0 && successfulUtxos.isEmpty) { + printV("Some UTXOs failed, but no successful UTXOs, falling back to previous UTXOs"); + return previousUnspentCoins; + } + + if (failedCount > 0) { + printV("Some UTXOs failed, updating with successful UTXOs"); + updatedUnspentCoins.addAll(successfulUtxos); + } + + return updatedUnspentCoins; + } + Future updateCoins(List newUnspentCoins) async { if (newUnspentCoins.isEmpty) { return; @@ -1427,15 +1478,17 @@ abstract class ElectrumWalletBase @action Future updateUnspentsForAddress(BitcoinAddressRecord address) async { final newUnspentCoins = await fetchUnspent(address); - await updateCoins(newUnspentCoins); + await updateCoins(newUnspentCoins ?? []); } @action - Future> fetchUnspent(BitcoinAddressRecord address) async { - List> unspents = []; + Future?> fetchUnspent(BitcoinAddressRecord address) async { List updatedUnspentCoins = []; - unspents = await electrumClient.getListUnspent(address.getScriptHash(network)); + final unspents = await electrumClient.getListUnspent(address.getScriptHash(network)); + + // Failed to fetch unspents + if (unspents == null) return null; await Future.wait(unspents.map((unspent) async { try { diff --git a/cw_core/lib/exceptions.dart b/cw_core/lib/exceptions.dart index cfd44f18f..885f5cb2b 100644 --- a/cw_core/lib/exceptions.dart +++ b/cw_core/lib/exceptions.dart @@ -47,7 +47,11 @@ class TransactionInputNotSupported implements Exception {} class SignNativeTokenTransactionRentException implements Exception {} -class CreateAssociatedTokenAccountException implements Exception {} +class CreateAssociatedTokenAccountException implements Exception { + final String errorMessage; + + CreateAssociatedTokenAccountException(this.errorMessage); +} class SignSPLTokenTransactionRentException implements Exception {} diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 18d2ffc44..aa7d27254 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -22,8 +22,8 @@ class Node extends HiveObject with Keyable { this.useSSL, this.trusted = false, this.socksProxyAddress, + this.path = '', String? uri, - String? path, WalletType? type, }) { if (uri != null) { @@ -32,9 +32,6 @@ class Node extends HiveObject with Keyable { if (type != null) { this.type = type; } - if (path != null) { - this.path = path; - } } Node.fromMap(Map map) @@ -95,19 +92,15 @@ class Node extends HiveObject with Keyable { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: - return createUriFromElectrumAddress(uriRaw, path ?? ''); + return createUriFromElectrumAddress(uriRaw, path!); case WalletType.nano: case WalletType.banano: - if (isSSL) { - return Uri.https(uriRaw, path ?? ''); - } else { - return Uri.http(uriRaw, path ?? ''); - } case WalletType.ethereum: case WalletType.polygon: case WalletType.solana: case WalletType.tron: - return Uri.https(uriRaw, path ?? ''); + return Uri.parse( + "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}"); case WalletType.none: throw Exception('Unexpected type ${type.toString()} for Node uri'); } @@ -247,7 +240,7 @@ class Node extends HiveObject with Keyable { if (proxy == null) { return false; } - final proxyAddress = proxy!.split(':')[0]; + final proxyAddress = proxy.split(':')[0]; final proxyPort = int.parse(proxy.split(':')[1]); try { final socket = await Socket.connect(proxyAddress, proxyPort, timeout: Duration(seconds: 5)); diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 95376c563..9447aad38 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -21,22 +21,23 @@ class SolanaWalletClient { bool connect(Node node) { try { - Uri? rpcUri; - String webSocketUrl; - bool isModifiedNodeUri = false; + Uri rpcUri = node.uri; + String webSocketUrl = 'wss://${node.uriRaw}'; if (node.uriRaw == 'rpc.ankr.com') { - isModifiedNodeUri = true; String ankrApiKey = secrets.ankrApiKey; rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey'); webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey'; - } else { - webSocketUrl = 'wss://${node.uriRaw}'; + } else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') { + String chainStackApiKey = secrets.chainStackApiKey; + + rpcUri = Uri.https(node.uriRaw, '/$chainStackApiKey'); + webSocketUrl = 'wss://${node.uriRaw}/$chainStackApiKey'; } _client = SolanaClient( - rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri, + rpcUrl: rpcUri, websocketUrl: Uri.parse(webSocketUrl), timeout: const Duration(minutes: 2), ); @@ -115,10 +116,14 @@ class SolanaWalletClient { final message = _getMessageForNativeTransaction(ownerKeypair, ownerKeypair.address, lamportsPerSol); - final recentBlockhash = await _getRecentBlockhash(commitment); + final latestBlockhash = await _getLatestBlockhash(commitment); - final estimatedFee = - _getFeeFromCompiledMessage(message, ownerKeypair.publicKey, recentBlockhash, commitment); + final estimatedFee = _getFeeFromCompiledMessage( + message, + ownerKeypair.publicKey, + latestBlockhash, + commitment, + ); return estimatedFee; } @@ -131,13 +136,25 @@ class SolanaWalletClient { List transactions = []; try { - final response = await _client!.rpcClient.getTransactionsList( - publicKey, + final signatures = await _client!.rpcClient.getSignaturesForAddress( + publicKey.toBase58(), commitment: Commitment.confirmed, - limit: 1000, ); - for (final tx in response) { + final List transactionDetails = []; + for (int i = 0; i < signatures.length; i += 20) { + final response = await _client!.rpcClient.getMultipleTransactions( + signatures.sublist(i, math.min(i + 20, signatures.length)), + commitment: Commitment.confirmed, + encoding: Encoding.jsonParsed, + ); + transactionDetails.addAll(response); + + // to avoid reaching the node RPS limit + await Future.delayed(Duration(milliseconds: 500)); + } + + for (final tx in transactionDetails) { if (tx.transaction is ParsedTransaction) { final parsedTx = (tx.transaction as ParsedTransaction); final message = parsedTx.message; @@ -310,16 +327,16 @@ class SolanaWalletClient { } } - Future _getRecentBlockhash(Commitment commitment) async { - final latestBlockhash = + Future _getLatestBlockhash(Commitment commitment) async { + final latestBlockHashResult = await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value; - final recentBlockhash = RecentBlockhash( - blockhash: latestBlockhash.blockhash, - feeCalculator: const FeeCalculator(lamportsPerSignature: 500), + final latestBlockhash = LatestBlockhash( + blockhash: latestBlockHashResult.blockhash, + lastValidBlockHeight: latestBlockHashResult.lastValidBlockHeight, ); - return recentBlockhash; + return latestBlockhash; } Message _getMessageForNativeTransaction( @@ -342,11 +359,11 @@ class SolanaWalletClient { Future _getFeeFromCompiledMessage( Message message, Ed25519HDPublicKey feePayer, - RecentBlockhash recentBlockhash, + LatestBlockhash latestBlockhash, Commitment commitment, ) async { final compile = message.compile( - recentBlockhash: recentBlockhash.blockhash, + recentBlockhash: latestBlockhash.blockhash, feePayer: feePayer, ); @@ -391,12 +408,12 @@ class SolanaWalletClient { final signers = [ownerKeypair]; - RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment); + LatestBlockhash latestBlockhash = await _getLatestBlockhash(commitment); final fee = await _getFeeFromCompiledMessage( message, signers.first.publicKey, - recentBlockhash, + latestBlockhash, commitment, ); @@ -422,14 +439,14 @@ class SolanaWalletClient { message: updatedMessage, signers: signers, commitment: commitment, - recentBlockhash: recentBlockhash, + latestBlockhash: latestBlockhash, ); } else { signedTx = await _signTransactionInternal( message: message, signers: signers, commitment: commitment, - recentBlockhash: recentBlockhash, + latestBlockhash: latestBlockhash, ); } @@ -507,12 +524,12 @@ class SolanaWalletClient { final signers = [ownerKeypair]; - RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment); + LatestBlockhash latestBlockhash = await _getLatestBlockhash(commitment); final fee = await _getFeeFromCompiledMessage( message, signers.first.publicKey, - recentBlockhash, + latestBlockhash, commitment, ); @@ -530,7 +547,7 @@ class SolanaWalletClient { message: message, signers: signers, commitment: commitment, - recentBlockhash: recentBlockhash, + latestBlockhash: latestBlockhash, ); sendTx() async => await sendTransaction( @@ -552,9 +569,9 @@ class SolanaWalletClient { required Message message, required List signers, required Commitment commitment, - required RecentBlockhash recentBlockhash, + required LatestBlockhash latestBlockhash, }) async { - final signedTx = await signTransaction(recentBlockhash, message, signers); + final signedTx = await signTransaction(latestBlockhash, message, signers); return signedTx; } diff --git a/cw_solana/lib/solana_exceptions.dart b/cw_solana/lib/solana_exceptions.dart index 697521c29..96ba0bb6f 100644 --- a/cw_solana/lib/solana_exceptions.dart +++ b/cw_solana/lib/solana_exceptions.dart @@ -25,9 +25,7 @@ class SolanaSignNativeTokenTransactionRentException extends SignNativeTokenTransactionRentException {} class SolanaCreateAssociatedTokenAccountException extends CreateAssociatedTokenAccountException { - SolanaCreateAssociatedTokenAccountException(this.exceptionMessage); - - final String exceptionMessage; + SolanaCreateAssociatedTokenAccountException(super.errorMessage); } class SolanaSignSPLTokenTransactionRentException extends SignSPLTokenTransactionRentException {} diff --git a/cw_solana/pubspec.yaml b/cw_solana/pubspec.yaml index 6fd5cd97c..807acdca8 100644 --- a/cw_solana/pubspec.yaml +++ b/cw_solana/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - solana: ^0.30.4 + solana: ^0.31.0+1 cw_core: path: ../cw_core http: ^1.1.0 diff --git a/lib/buy/payfura/payfura_buy_provider.dart b/lib/buy/payfura/payfura_buy_provider.dart deleted file mode 100644 index eb9104df0..000000000 --- a/lib/buy/payfura/payfura_buy_provider.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:cake_wallet/.secrets.g.dart' as secrets; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cw_core/wallet_base.dart'; - -class PayfuraBuyProvider { - PayfuraBuyProvider({required SettingsStore settingsStore, required WalletBase wallet}) - : this._settingsStore = settingsStore, - this._wallet = wallet; - - final SettingsStore _settingsStore; - final WalletBase _wallet; - - static const _baseUrl = 'exchange.payfura.com'; - - Uri requestUrl() { - return Uri.https(_baseUrl, '', { - 'apiKey': secrets.payfuraApiKey, - 'to': _wallet.currency.title, - 'from': _settingsStore.fiatCurrency.title, - 'walletAddress': '${_wallet.currency.title}:${_wallet.walletAddresses.address}', - 'mode': 'buy' - }); - } -} diff --git a/lib/core/wallet_connect/web3wallet_service.dart b/lib/core/wallet_connect/web3wallet_service.dart index ad892a594..3740d3dfe 100644 --- a/lib/core/wallet_connect/web3wallet_service.dart +++ b/lib/core/wallet_connect/web3wallet_service.dart @@ -140,25 +140,24 @@ abstract class Web3WalletServiceBase with Store { for (final cId in SolanaChainId.values) { final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type); - Uri? rpcUri; - String webSocketUrl; - bool isModifiedNodeUri = false; + Uri rpcUri = node.uri; + String webSocketUrl = 'wss://${node.uriRaw}'; if (node.uriRaw == 'rpc.ankr.com') { - isModifiedNodeUri = true; - - //A better way to handle this instead of adding this to the general secrets? String ankrApiKey = secrets.ankrApiKey; rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey'); webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey'; - } else { - webSocketUrl = 'wss://${node.uriRaw}'; + } else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') { + String chainStackApiKey = secrets.chainStackApiKey; + + rpcUri = Uri.https(node.uriRaw, '/$chainStackApiKey'); + webSocketUrl = 'wss://${node.uriRaw}/$chainStackApiKey'; } SolanaChainServiceImpl( reference: cId, - rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri, + rpcUrl: rpcUri, webSocketUrl: webSocketUrl, wcKeyService: walletKeyService, bottomSheetService: _bottomSheetHandler, diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart index 6b1553443..f1996bae8 100644 --- a/lib/core/wallet_loading_service.dart +++ b/lib/core/wallet_loading_service.dart @@ -2,12 +2,10 @@ import 'dart:async'; import 'package:cake_wallet/core/generate_wallet_password.dart'; import 'package:cake_wallet/core/key_service.dart'; -import 'package:cake_wallet/core/secure_storage.dart'; 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/reactions/on_authentication_state_change.dart'; -import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; diff --git a/lib/di.dart b/lib/di.dart index 358f72a77..91ec692ef 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -11,7 +11,6 @@ import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/buy/order.dart'; -import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; import 'package:cake_wallet/core/new_wallet_arguments.dart'; import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; import 'package:cake_wallet/core/auth_service.dart'; @@ -1022,11 +1021,6 @@ Future setup({ getIt.registerFactoryParam((title, uri) => WebViewPage(title, uri)); - getIt.registerFactory(() => PayfuraBuyProvider( - settingsStore: getIt.get().settingsStore, - wallet: getIt.get().wallet!, - )); - getIt.registerFactory(() => ExchangeViewModel( getIt.get(), _tradesSource, diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 64370503f..96638621a 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -40,7 +40,7 @@ const polygonDefaultNodeUri = 'polygon-bor.publicnode.com'; const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002'; const nanoDefaultNodeUri = 'nano.nownodes.io'; const nanoDefaultPowNodeUri = 'rpc.nano.to'; -const solanaDefaultNodeUri = 'solana-rpc.publicnode.com:443'; +const solanaDefaultNodeUri = 'solana-mainnet.core.chainstack.com'; const tronDefaultNodeUri = 'api.trongrid.io'; const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002'; const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568'; @@ -167,7 +167,11 @@ Future defaultSettingsMigration( break; case 18: - await addOnionNode(nodes); + await updateWalletTypeNodesWithNewNode( + nodes: nodes, + newNodeUri: "cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081", + type: WalletType.monero, + ); break; case 19: @@ -261,15 +265,15 @@ Future defaultSettingsMigration( await updateTronNodesWithNowNodes(sharedPreferences: sharedPreferences, nodes: nodes); break; case 42: - updateBtcElectrumNodeToUseSSL(nodes, sharedPreferences); + _fixNodesUseSSLFlag(nodes); break; case 43: - await _updateCakeXmrNode(nodes); + _fixNodesUseSSLFlag(nodes); _deselectExchangeProvider(sharedPreferences, "THORChain"); _deselectExchangeProvider(sharedPreferences, "SimpleSwap"); break; case 44: - await _updateCakeXmrNode(nodes); + _fixNodesUseSSLFlag(nodes); await _changeDefaultNode( nodes: nodes, sharedPreferences: sharedPreferences, @@ -297,14 +301,12 @@ Future defaultSettingsMigration( updateWalletTypeNodesWithNewNode( newNodeUri: 'matic.nownodes.io', - sharedPreferences: sharedPreferences, nodes: nodes, type: WalletType.polygon, useSSL: true, ); updateWalletTypeNodesWithNewNode( newNodeUri: 'eth.nownodes.io', - sharedPreferences: sharedPreferences, nodes: nodes, type: WalletType.ethereum, useSSL: true, @@ -330,6 +332,35 @@ Future defaultSettingsMigration( useSSL: true, oldUri: ['rpc.ankr.com'], ); + break; + case 46: + _fixNodesUseSSLFlag(nodes); + updateWalletTypeNodesWithNewNode( + newNodeUri: 'litecoin.stackwallet.com:20063', + nodes: nodes, + type: WalletType.litecoin, + useSSL: true, + ); + updateWalletTypeNodesWithNewNode( + newNodeUri: 'electrum-ltc.bysh.me:50002', + nodes: nodes, + type: WalletType.litecoin, + useSSL: true, + ); + _changeDefaultNode( + nodes: nodes, + sharedPreferences: sharedPreferences, + type: WalletType.solana, + newDefaultUri: solanaDefaultNodeUri, + currentNodePreferenceKey: PreferencesKey.currentSolanaNodeIdKey, + useSSL: true, + oldUri: [ + 'rpc.ankr.com', + 'api.mainnet-beta.solana.com:443', + 'solana-rpc.publicnode.com:443', + ], + ); + break; default: break; } @@ -361,7 +392,8 @@ Future _changeDefaultNode({ required String newDefaultUri, required String currentNodePreferenceKey, required bool useSSL, - required List oldUri, // leave empty if you want to force replace the node regardless of the user's current node + required List + oldUri, // leave empty if you want to force replace the node regardless of the user's current node }) async { final currentNodeId = sharedPreferences.getInt(currentNodePreferenceKey); final currentNode = nodes.values.firstWhere((node) => node.key == currentNodeId); @@ -389,11 +421,10 @@ Future _changeDefaultNode({ /// Generic function for adding a new Node for a Wallet Type. Future updateWalletTypeNodesWithNewNode({ - required SharedPreferences sharedPreferences, required Box nodes, required WalletType type, required String newNodeUri, - required bool useSSL, + bool? useSSL, }) async { // If it already exists in the box of nodes, no need to add it annymore. if (nodes.values.any((node) => node.uriRaw == newNodeUri)) return; @@ -407,26 +438,6 @@ Future updateWalletTypeNodesWithNewNode({ ); } -Future _updateCakeXmrNode(Box nodes) async { - final node = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletMoneroUri); - - if (node != null) { - node.trusted = true; - node.useSSL = true; - await node.save(); - } -} - -void updateBtcElectrumNodeToUseSSL(Box nodes, SharedPreferences sharedPreferences) { - final btcElectrumNode = - nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletBitcoinUri); - - if (btcElectrumNode != null) { - btcElectrumNode.useSSL = true; - btcElectrumNode.save(); - } -} - void _deselectExchangeProvider(SharedPreferences sharedPreferences, String providerName) { final Map exchangeProvidersSelection = json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") @@ -445,8 +456,10 @@ void _fixNodesUseSSLFlag(Box nodes) { switch (node.uriRaw) { case cakeWalletLitecoinElectrumUri: case cakeWalletBitcoinElectrumUri: + case newCakeWalletBitcoinUri: + case newCakeWalletMoneroUri: node.useSSL = true; - break; + node.trusted = true; } } } @@ -580,15 +593,6 @@ Future validateBitcoinSavedTransactionPriority(SharedPreferences sharedPre } } -Future addOnionNode(Box nodes) async { - final onionNodeUri = "cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081"; - - // check if the user has this node before (added it manually) - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == onionNodeUri) == null) { - await nodes.add(Node(uri: onionNodeUri, type: WalletType.monero)); - } -} - Future replaceNodesMigration({required Box nodes}) async { final replaceNodes = { 'eu-node.cakewallet.io:18081': diff --git a/lib/entities/evm_transaction_error_fees_handler.dart b/lib/entities/evm_transaction_error_fees_handler.dart index 63f6e164d..b802f9883 100644 --- a/lib/entities/evm_transaction_error_fees_handler.dart +++ b/lib/entities/evm_transaction_error_fees_handler.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart'; + class EVMTransactionErrorFeesHandler { EVMTransactionErrorFeesHandler({ this.balanceWei, @@ -64,14 +66,14 @@ class EVMTransactionErrorFeesHandler { return EVMTransactionErrorFeesHandler( balanceWei: balanceWei.toString(), - balanceEth: balanceEth.toString().substring(0, 12), - balanceUsd: balanceUsd.toString().substring(0, 4), + balanceEth: balanceEth.toString().safeSubString(0, 12), + balanceUsd: balanceUsd.toString().safeSubString(0, 4), txCostWei: txCostWei.toString(), - txCostEth: txCostEth.toString().substring(0, 12), - txCostUsd: txCostUsd.toString().substring(0, 4), + txCostEth: txCostEth.toString().safeSubString(0, 12), + txCostUsd: txCostUsd.toString().safeSubString(0, 4), overshotWei: overshotWei.toString(), - overshotEth: overshotEth.toString().substring(0, 12), - overshotUsd: overshotUsd.toString().substring(0, 4), + overshotEth: overshotEth.toString().safeSubString(0, 12), + overshotUsd: overshotUsd.toString().safeSubString(0, 4), ); } else { // If any value is missing, return an error message diff --git a/lib/main.dart b/lib/main.dart index 510705105..fd25a1e9c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -215,7 +215,7 @@ Future initializeAppConfigs() async { secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, havenSeedStore: havenSeedStore, - initialMigrationVersion: 45, + initialMigrationVersion: 46, ); } diff --git a/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart b/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart index b8c1500dc..9fd70be05 100644 --- a/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart +++ b/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart @@ -70,6 +70,8 @@ class SeedVerificationStepView extends StatelessWidget { (option) { return GestureDetector( onTap: () async { + if (walletSeedViewModel.wrongEntries > 2) return; + final isCorrectWord = walletSeedViewModel.isChosenWordCorrect(option); final isSecondWrongEntry = walletSeedViewModel.wrongEntries >= 2; if (!isCorrectWord) { @@ -81,7 +83,9 @@ class SeedVerificationStepView extends StatelessWidget { ); if (isSecondWrongEntry) { - Navigator.pop(context); + if (context.mounted) { + Navigator.pop(context); + } } } }, diff --git a/lib/src/screens/wallet_connect/utils/string_parsing.dart b/lib/src/screens/wallet_connect/utils/string_parsing.dart index b9fdca7b2..0aed1b9e9 100644 --- a/lib/src/screens/wallet_connect/utils/string_parsing.dart +++ b/lib/src/screens/wallet_connect/utils/string_parsing.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:math'; import 'package:convert/convert.dart'; @@ -13,4 +14,8 @@ extension StringParsing on String { return this; } + + String safeSubString(int start, int end) { + return this.substring(0, min(this.toString().length, 12)); + } } diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 357a69fa6..66cbc61a0 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -219,9 +219,9 @@ class ExceptionHandler { // probably when the device was locked and then opened on Cake // this is solved by a restart of the app // just ignoring until we find a solution to this issue or migrate from flutter secure storage - "core/auth_service.dart:63", + "core/auth_service.dart:64", "core/key_service.dart:14", - "core/wallet_loading_service.dart:133", + "core/wallet_loading_service.dart:131", ]; static Future _addDeviceInfo(File file) async { diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 808657f66..387c66511 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -4,13 +4,11 @@ import 'dart:io' show Platform; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; -import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/entities/service_status.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -643,7 +641,7 @@ abstract class DashboardViewModelBase with Store { transactions.clear(); - transactions.addAll( + transactions = ObservableList.of( wallet.transactionHistory.transactions.values.map( (transaction) => TransactionListItem( transaction: transaction, @@ -705,7 +703,7 @@ abstract class DashboardViewModelBase with Store { monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id) .toList(); - transactions.addAll( + transactions = ObservableList.of( _accountTransactions.map( (transaction) => TransactionListItem( transaction: transaction, @@ -725,7 +723,7 @@ abstract class DashboardViewModelBase with Store { wow.wownero!.getCurrentAccount(wallet).id) .toList(); - transactions.addAll( + transactions = ObservableList.of( _accountTransactions.map( (transaction) => TransactionListItem( transaction: transaction, diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index f8599513c..78bc867db 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -680,7 +680,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } if (error is CreateAssociatedTokenAccountException) { - return S.current.solana_create_associated_token_account_exception; + return "${S.current.solana_create_associated_token_account_exception}\n\n${error.errorMessage}"; } if (error is SignSPLTokenTransactionRentException) { diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 221f1d9bf..e87b5a44e 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -106,7 +106,7 @@ dependencies: flutter_svg: ^2.0.9 polyseed: ^0.0.6 nostr_tools: ^1.0.9 - solana: ^0.30.1 + solana: ^0.31.0+1 ledger_flutter_plus: ^1.4.1 hashlib: ^1.19.2 diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index e17a509d7..5c316c54b 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -30,7 +30,6 @@ class SecretKey { SecretKey('twitterBearerToken', () => ''), SecretKey('anonPayReferralCode', () => ''), SecretKey('fiatApiKey', () => ''), - SecretKey('payfuraApiKey', () => ''), SecretKey('chatwootWebsiteToken', () => ''), SecretKey('exolixApiKey', () => ''), SecretKey('robinhoodApplicationId', () => ''), @@ -38,6 +37,7 @@ class SecretKey { SecretKey('walletConnectProjectId', () => ''), SecretKey('moralisApiKey', () => ''), SecretKey('ankrApiKey', () => ''), + SecretKey('chainStackApiKey', () => ''), SecretKey('quantexExchangeMarkup', () => ''), SecretKey('seeds', () => ''), SecretKey('testCakePayApiKey', () => ''), @@ -86,6 +86,7 @@ class SecretKey { static final solanaSecrets = [ SecretKey('ankrApiKey', () => ''), + SecretKey('chainStackApiKey', () => ''), ]; static final nanoSecrets = [