From ed12ff6afeae9085aa343a89c5fb44520eea032d Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 27 Dec 2024 00:42:36 +0200 Subject: [PATCH 01/10] change Solana node (#1903) * change Solana node * Fix reaching limit for fetching transactions --- .../workflows/automated_integration_test.yml | 2 + .github/workflows/pr_test_build_android.yml | 2 + .github/workflows/pr_test_build_linux.yml | 2 + assets/solana_node_list.yml | 3 + cw_core/lib/exceptions.dart | 6 +- cw_solana/lib/solana_client.dart | 79 +++++++++++-------- cw_solana/lib/solana_exceptions.dart | 4 +- cw_solana/pubspec.yaml | 2 +- .../wallet_connect/web3wallet_service.dart | 17 ++-- lib/entities/default_settings_migration.dart | 15 +++- lib/view_model/send/send_view_model.dart | 2 +- pubspec_base.yaml | 2 +- tool/utils/secret_key.dart | 2 + 13 files changed, 90 insertions(+), 48 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 51bc83ce0..9eba75cc0 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -173,6 +173,7 @@ jobs: 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 +186,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..951b20dab 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -184,6 +184,7 @@ jobs: 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 +198,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..89c4af8f2 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -156,6 +156,7 @@ jobs: 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 +168,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_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_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/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/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 9e06d25da..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'; @@ -347,6 +347,19 @@ Future defaultSettingsMigration( 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; 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..b7a581ff8 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -38,6 +38,7 @@ class SecretKey { SecretKey('walletConnectProjectId', () => ''), SecretKey('moralisApiKey', () => ''), SecretKey('ankrApiKey', () => ''), + SecretKey('chainStackApiKey', () => ''), SecretKey('quantexExchangeMarkup', () => ''), SecretKey('seeds', () => ''), SecretKey('testCakePayApiKey', () => ''), @@ -86,6 +87,7 @@ class SecretKey { static final solanaSecrets = [ SecretKey('ankrApiKey', () => ''), + SecretKey('chainStackApiKey', () => ''), ]; static final nanoSecrets = [ From 542920a5128a6a80faa0459dc72bbbe1afe92539 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 27 Dec 2024 03:31:48 +0200 Subject: [PATCH 02/10] Remove payfura (#1904) * change Solana node * Fix reaching limit for fetching transactions * Remove payfura --- .../workflows/automated_integration_test.yml | 1 - .github/workflows/pr_test_build_android.yml | 1 - .github/workflows/pr_test_build_linux.yml | 1 - lib/buy/payfura/payfura_buy_provider.dart | 24 ------------------- lib/di.dart | 6 ----- tool/utils/secret_key.dart | 1 - 6 files changed, 34 deletions(-) delete mode 100644 lib/buy/payfura/payfura_buy_provider.dart diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 9eba75cc0..b299c9340 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -171,7 +171,6 @@ 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 diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 951b20dab..cdd0e40b4 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -182,7 +182,6 @@ 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 diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 89c4af8f2..891327d1e 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -154,7 +154,6 @@ 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 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/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/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index b7a581ff8..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', () => ''), From b1c9be637f407d1f131d1ad269f6e0dbf01b59ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rafael=20x=C9=B1r?= Date: Fri, 27 Dec 2024 00:01:41 -0300 Subject: [PATCH 03/10] fix: wrong card for vendor country (#1905) * fix: wrong card for vendor country * Update lib/cake_pay/cake_pay_vendor.dart --------- Co-authored-by: Omar Hatem --- lib/cake_pay/cake_pay_api.dart | 4 ++-- lib/cake_pay/cake_pay_card.dart | 8 ++++++-- lib/cake_pay/cake_pay_service.dart | 2 +- lib/cake_pay/cake_pay_vendor.dart | 18 +++++++++++------- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/cake_pay/cake_pay_api.dart b/lib/cake_pay/cake_pay_api.dart index f9aa2f0f1..68aba3f3e 100644 --- a/lib/cake_pay/cake_pay_api.dart +++ b/lib/cake_pay/cake_pay_api.dart @@ -204,8 +204,8 @@ class CakePayApi { /// Get Vendors Future> getVendors({ required String apiKey, + required String country, int? page, - String? country, String? countryCode, String? search, List? vendorIds, @@ -247,7 +247,7 @@ class CakePayApi { } return (bodyJson['results'] as List) - .map((e) => CakePayVendor.fromJson(e as Map)) + .map((e) => CakePayVendor.fromJson(e as Map, country)) .toList(); } } diff --git a/lib/cake_pay/cake_pay_card.dart b/lib/cake_pay/cake_pay_card.dart index 26fa0c50b..d3f07e409 100644 --- a/lib/cake_pay/cake_pay_card.dart +++ b/lib/cake_pay/cake_pay_card.dart @@ -81,7 +81,11 @@ class CakePayCard { } static String fixEncoding(String text) { - final bytes = latin1.encode(text); - return utf8.decode(bytes, allowMalformed: true); + try { + final bytes = latin1.encode(text); + return utf8.decode(bytes, allowMalformed: true); + } catch (_) { + return text; + } } } diff --git a/lib/cake_pay/cake_pay_service.dart b/lib/cake_pay/cake_pay_service.dart index 768588775..9ba65df9a 100644 --- a/lib/cake_pay/cake_pay_service.dart +++ b/lib/cake_pay/cake_pay_service.dart @@ -29,8 +29,8 @@ class CakePayService { /// Get Vendors Future> getVendors({ + required String country, int? page, - String? country, String? countryCode, String? search, List? vendorIds, diff --git a/lib/cake_pay/cake_pay_vendor.dart b/lib/cake_pay/cake_pay_vendor.dart index c947fa882..564896654 100644 --- a/lib/cake_pay/cake_pay_vendor.dart +++ b/lib/cake_pay/cake_pay_vendor.dart @@ -7,7 +7,7 @@ class CakePayVendor { final String name; final bool unavailable; final String? cakeWarnings; - final List countries; + final String country; final CakePayCard? card; CakePayVendor({ @@ -15,19 +15,23 @@ class CakePayVendor { required this.name, required this.unavailable, this.cakeWarnings, - required this.countries, + required this.country, this.card, }); - factory CakePayVendor.fromJson(Map json) { + factory CakePayVendor.fromJson(Map json, String country) { final name = stripHtmlIfNeeded(json['name'] as String); final decodedName = fixEncoding(name); var cardsJson = json['cards'] as List?; - CakePayCard? firstCard; + CakePayCard? cardForVendor; if (cardsJson != null && cardsJson.isNotEmpty) { - firstCard = CakePayCard.fromJson(cardsJson.first as Map); + try { + cardForVendor = CakePayCard.fromJson(cardsJson + .where((element) => element['country'] == country) + .first as Map); + } catch (_) {} } return CakePayVendor( @@ -35,8 +39,8 @@ class CakePayVendor { name: decodedName, unavailable: json['unavailable'] as bool? ?? false, cakeWarnings: json['cake_warnings'] as String?, - countries: List.from(json['countries'] as List? ?? []), - card: firstCard, + country: country, + card: cardForVendor, ); } From c9a6abeea4a075611467e08e7134ccf8665c7f91 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Fri, 27 Dec 2024 04:54:47 +0100 Subject: [PATCH 04/10] Full balance (#1457) * fix: Confirm widget is still mounted * feat: Modify balance display to include full balance * fix: Modifying balance * chore: Feature cleanup * fix: Add frozen balance into consideration when taking available balance and add field to make full balance display only on bitcoin and litecoin wallets * fix: Adjust balance card to display correct available and unavailable balance, unavailable balance should only be displayed when there is one WIP * fix: Cleanup balance page and balance page view_model * chore: Revert formatting * fix: Remove full balance * fix: Remove full balance * fix: Remove full balance * chore: Rever formating [skip ci] * feat: Finalize display only available and unavailable balance * fix: Modify the way balance is displayed, activate frozen balance with label, remove unavailable/additional balance for bitcoin wallet type * fix: Issues coming from syncing with main * fix: Modify additional balance label * fix: Monero and Wownero balances display bug * fix: Resolve merge conflicts * feat: Activate CPFP for BTC, LTC and BCH, also fix issues with frozen balance display * - minor fix - remove unused functions * Fix conflicts --------- Co-authored-by: Omar Hatem Co-authored-by: tuxsudo --- cw_bitcoin/lib/electrum_balance.dart | 4 +- cw_bitcoin/lib/electrum_wallet.dart | 26 +- cw_core/lib/monero_balance.dart | 25 +- cw_core/lib/wownero_balance.dart | 24 +- cw_evm/lib/evm_erc20_balance.dart | 9 +- cw_monero/lib/monero_wallet.dart | 17 +- .../robots/dashboard_page_robot.dart | 2 +- lib/di.dart | 2 +- lib/src/screens/dashboard/dashboard_page.dart | 2 +- .../dashboard/desktop_dashboard_page.dart | 2 +- .../dashboard/pages/balance/balance_page.dart | 88 ++ .../pages/balance/balance_row_widget.dart | 654 +++++++++ .../pages/balance/crypto_balance_widget.dart | 424 ++++++ .../screens/dashboard/pages/balance_page.dart | 1164 ----------------- .../dashboard/balance_view_model.dart | 18 +- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_hy.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_vi.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 43 files changed, 1257 insertions(+), 1232 deletions(-) create mode 100644 lib/src/screens/dashboard/pages/balance/balance_page.dart create mode 100644 lib/src/screens/dashboard/pages/balance/balance_row_widget.dart create mode 100644 lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart delete mode 100644 lib/src/screens/dashboard/pages/balance_page.dart diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart index ebd2f06ae..37c34058b 100644 --- a/cw_bitcoin/lib/electrum_balance.dart +++ b/cw_bitcoin/lib/electrum_balance.dart @@ -39,7 +39,7 @@ class ElectrumBalance extends Balance { int secondUnconfirmed = 0; @override - String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen); + String get formattedAvailableBalance => bitcoinAmountToString(amount: ((confirmed + unconfirmed) - frozen) ); @override String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed); @@ -58,7 +58,7 @@ class ElectrumBalance extends Balance { @override String get formattedFullAvailableBalance => - bitcoinAmountToString(amount: confirmed + secondConfirmed - frozen); + bitcoinAmountToString(amount: (confirmed + unconfirmed) + secondConfirmed - frozen); String toJSON() => json.encode({ 'confirmed': confirmed, diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index eae830db1..3ab1505c9 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -2213,18 +2213,6 @@ abstract class ElectrumWalletBase var totalConfirmed = 0; var totalUnconfirmed = 0; - unspentCoinsInfo.values.forEach((info) { - unspentCoins.forEach((element) { - if (element.hash == info.hash && - element.vout == info.vout && - info.isFrozen && - element.bitcoinAddressRecord.address == info.address && - element.value == info.value) { - totalFrozen += element.value; - } - }); - }); - if (hasSilentPaymentsScanning) { // Add values from unspent coins that are not fetched by the address list // i.e. scanned silent payments @@ -2240,6 +2228,20 @@ abstract class ElectrumWalletBase }); } + unspentCoinsInfo.values.forEach((info) { + unspentCoins.forEach((element) { + if (element.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) return; + + if (element.hash == info.hash && + element.vout == info.vout && + info.isFrozen && + element.bitcoinAddressRecord.address == info.address && + element.value == info.value) { + totalFrozen += element.value; + } + }); + }); + final balances = await Future.wait(balanceFutures); if (balances.isNotEmpty && balances.first['confirmed'] == null) { diff --git a/cw_core/lib/monero_balance.dart b/cw_core/lib/monero_balance.dart index 9a63c407e..42c00b97e 100644 --- a/cw_core/lib/monero_balance.dart +++ b/cw_core/lib/monero_balance.dart @@ -3,36 +3,25 @@ import 'package:cw_core/monero_amount_format.dart'; class MoneroBalance extends Balance { MoneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0}) - : formattedFullBalance = moneroAmountToString(amount: frozenBalance + fullBalance), - formattedUnlockedBalance = moneroAmountToString(amount: unlockedBalance), - formattedLockedBalance = - moneroAmountToString(amount: frozenBalance + fullBalance - unlockedBalance), + : formattedUnconfirmedBalance = moneroAmountToString(amount: fullBalance - unlockedBalance), + formattedUnlockedBalance = moneroAmountToString(amount: unlockedBalance - frozenBalance), + formattedFrozenBalance = moneroAmountToString(amount: frozenBalance), super(unlockedBalance, fullBalance); - MoneroBalance.fromString( - {required this.formattedFullBalance, - required this.formattedUnlockedBalance, - this.formattedLockedBalance = '0.0'}) - : fullBalance = moneroParseAmount(amount: formattedFullBalance), - unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance), - frozenBalance = moneroParseAmount(amount: formattedLockedBalance), - super(moneroParseAmount(amount: formattedUnlockedBalance), - moneroParseAmount(amount: formattedFullBalance)); - final int fullBalance; final int unlockedBalance; final int frozenBalance; - final String formattedFullBalance; + final String formattedUnconfirmedBalance; final String formattedUnlockedBalance; - final String formattedLockedBalance; + final String formattedFrozenBalance; @override String get formattedUnAvailableBalance => - formattedLockedBalance == '0.0' ? '' : formattedLockedBalance; + formattedFrozenBalance == '0.0' ? '' : formattedFrozenBalance; @override String get formattedAvailableBalance => formattedUnlockedBalance; @override - String get formattedAdditionalBalance => formattedFullBalance; + String get formattedAdditionalBalance => formattedUnconfirmedBalance; } diff --git a/cw_core/lib/wownero_balance.dart b/cw_core/lib/wownero_balance.dart index 2820659f2..b04560a79 100644 --- a/cw_core/lib/wownero_balance.dart +++ b/cw_core/lib/wownero_balance.dart @@ -3,36 +3,26 @@ import 'package:cw_core/wownero_amount_format.dart'; class WowneroBalance extends Balance { WowneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0}) - : formattedFullBalance = wowneroAmountToString(amount: fullBalance), + : formattedUnconfirmedBalance = wowneroAmountToString(amount: fullBalance - unlockedBalance), formattedUnlockedBalance = wowneroAmountToString(amount: unlockedBalance - frozenBalance), - formattedLockedBalance = - wowneroAmountToString(amount: frozenBalance + fullBalance - unlockedBalance), + formattedFrozenBalance = + wowneroAmountToString(amount: frozenBalance), super(unlockedBalance, fullBalance); - WowneroBalance.fromString( - {required this.formattedFullBalance, - required this.formattedUnlockedBalance, - this.formattedLockedBalance = '0.0'}) - : fullBalance = wowneroParseAmount(amount: formattedFullBalance), - unlockedBalance = wowneroParseAmount(amount: formattedUnlockedBalance), - frozenBalance = wowneroParseAmount(amount: formattedLockedBalance), - super(wowneroParseAmount(amount: formattedUnlockedBalance), - wowneroParseAmount(amount: formattedFullBalance)); - final int fullBalance; final int unlockedBalance; final int frozenBalance; - final String formattedFullBalance; + final String formattedUnconfirmedBalance; final String formattedUnlockedBalance; - final String formattedLockedBalance; + final String formattedFrozenBalance; @override String get formattedUnAvailableBalance => - formattedLockedBalance == '0.0' ? '' : formattedLockedBalance; + formattedFrozenBalance == '0.0' ? '' : formattedFrozenBalance; @override String get formattedAvailableBalance => formattedUnlockedBalance; @override - String get formattedAdditionalBalance => formattedFullBalance; + String get formattedAdditionalBalance => formattedUnconfirmedBalance; } \ No newline at end of file diff --git a/cw_evm/lib/evm_erc20_balance.dart b/cw_evm/lib/evm_erc20_balance.dart index 1727d7962..8962f7053 100644 --- a/cw_evm/lib/evm_erc20_balance.dart +++ b/cw_evm/lib/evm_erc20_balance.dart @@ -11,13 +11,12 @@ class EVMChainERC20Balance extends Balance { final int exponent; @override - String get formattedAdditionalBalance { - final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString(); - return formattedBalance.substring(0, min(12, formattedBalance.length)); - } + String get formattedAdditionalBalance => _balance(); @override - String get formattedAvailableBalance { + String get formattedAvailableBalance => _balance(); + + String _balance() { final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString(); return formattedBalance.substring(0, min(12, formattedBalance.length)); } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 21d5b6d4b..4d2f95e47 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -751,11 +751,18 @@ abstract class MoneroWalletBase extends WalletBase - element.walletId == id && - element.accountIndex == walletAddresses.account!.id)) { - if (coin.isFrozen && !coin.isSending) frozenBalance += coin.value; - } + unspentCoinsInfo.values.forEach((info) { + unspentCoins.forEach((element) { + if (element.hash == info.hash && + element.vout == info.vout && + info.isFrozen && + element.value == info.value && info.walletId == id && + info.accountIndex == walletAddresses.account!.id) { + if (element.isFrozen && !element.isSending) frozenBalance+= element.value; + } + }); + }); + return frozenBalance; } diff --git a/integration_test/robots/dashboard_page_robot.dart b/integration_test/robots/dashboard_page_robot.dart index bc5f411ad..8e058d9b2 100644 --- a/integration_test/robots/dashboard_page_robot.dart +++ b/integration_test/robots/dashboard_page_robot.dart @@ -1,6 +1,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart'; -import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/pages/balance/crypto_balance_widget.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/lib/di.dart b/lib/di.dart index 91ec692ef..4458f8ebd 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -79,7 +79,7 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart'; import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart'; -import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart'; diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index 8c236404d..b1934f4a3 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -24,7 +24,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/action_button.dart'; -import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; diff --git a/lib/src/screens/dashboard/desktop_dashboard_page.dart b/lib/src/screens/dashboard/desktop_dashboard_page.dart index b25d0774b..c7cd67dfa 100644 --- a/lib/src/screens/dashboard/desktop_dashboard_page.dart +++ b/lib/src/screens/dashboard/desktop_dashboard_page.dart @@ -8,7 +8,7 @@ import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/version_comparator.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/router.dart' as Router; diff --git a/lib/src/screens/dashboard/pages/balance/balance_page.dart b/lib/src/screens/dashboard/pages/balance/balance_page.dart new file mode 100644 index 000000000..b53d2d56b --- /dev/null +++ b/lib/src/screens/dashboard/pages/balance/balance_page.dart @@ -0,0 +1,88 @@ +import 'package:cake_wallet/reactions/wallet_connect.dart'; +import 'package:cake_wallet/src/screens/dashboard/pages/balance/crypto_balance_widget.dart'; +import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class BalancePage extends StatelessWidget { + BalancePage({ + required this.dashboardViewModel, + required this.settingsStore, + required this.nftViewModel, + }); + + final DashboardViewModel dashboardViewModel; + final NFTViewModel nftViewModel; + final SettingsStore settingsStore; + + @override + Widget build(BuildContext context) { + return Observer( + builder: (context) { + final isEVMCompatible = isEVMCompatibleChain(dashboardViewModel.type); + return DefaultTabController( + length: isEVMCompatible ? 2 : 1, + child: Column( + children: [ + if (isEVMCompatible) + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.only(left: 8), + child: TabBar( + indicatorSize: TabBarIndicatorSize.label, + isScrollable: true, + physics: NeverScrollableScrollPhysics(), + labelStyle: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: + Theme.of(context).extension()!.pageTitleTextColor, + height: 1, + ), + unselectedLabelStyle: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: + Theme.of(context).extension()!.pageTitleTextColor, + height: 1, + ), + labelColor: + Theme.of(context).extension()!.pageTitleTextColor, + dividerColor: Colors.transparent, + indicatorColor: + Theme.of(context).extension()!.pageTitleTextColor, + unselectedLabelColor: Theme.of(context) + .extension()! + .pageTitleTextColor + .withOpacity(0.5), + tabAlignment: TabAlignment.start, + tabs: [ + Tab(text: 'My Crypto'), + Tab(text: 'My NFTs'), + ], + ), + ), + ), + Expanded( + child: TabBarView( + physics: NeverScrollableScrollPhysics(), + children: [ + CryptoBalanceWidget(dashboardViewModel: dashboardViewModel), + if (isEVMCompatible) NFTListingPage(nftViewModel: nftViewModel) + ], + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart b/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart new file mode 100644 index 000000000..e3cff4760 --- /dev/null +++ b/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart @@ -0,0 +1,654 @@ +import 'dart:math'; + +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/unspent_coin_type.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class BalanceRowWidget extends StatelessWidget { + BalanceRowWidget({ + required this.availableBalanceLabel, + required this.availableBalance, + required this.availableFiatBalance, + required this.additionalBalanceLabel, + required this.additionalBalance, + required this.additionalFiatBalance, + required this.secondAvailableBalanceLabel, + required this.secondAvailableBalance, + required this.secondAvailableFiatBalance, + required this.secondAdditionalBalanceLabel, + required this.secondAdditionalBalance, + required this.secondAdditionalFiatBalance, + required this.frozenBalance, + required this.frozenFiatBalance, + required this.currency, + required this.hasAdditionalBalance, + required this.hasSecondAvailableBalance, + required this.hasSecondAdditionalBalance, + required this.isTestnet, + required this.dashboardViewModel, + super.key, + }); + + final String availableBalanceLabel; + final String availableBalance; + final String availableFiatBalance; + final String additionalBalanceLabel; + final String additionalBalance; + final String additionalFiatBalance; + final String secondAvailableBalanceLabel; + final String secondAvailableBalance; + final String secondAvailableFiatBalance; + final String secondAdditionalBalanceLabel; + final String secondAdditionalBalance; + final String secondAdditionalFiatBalance; + final String frozenBalance; + final String frozenFiatBalance; + final CryptoCurrency currency; + final bool hasAdditionalBalance; + final bool hasSecondAvailableBalance; + final bool hasSecondAdditionalBalance; + final bool isTestnet; + final DashboardViewModel dashboardViewModel; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + margin: const EdgeInsets.only(left: 16, right: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.0), + border: Border.all( + color: Theme.of(context).extension()!.cardBorderColor, + width: 1, + ), + color: Theme.of(context).extension()!.syncedBackgroundColor, + ), + child: Container( + margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: hasAdditionalBalance + ? () => _showBalanceDescription( + context, S.of(context).available_balance_description) + : null, + child: Row( + children: [ + 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), + child: Icon(Icons.help_outline, + size: 16, + color: Theme.of(context) + .extension()! + .labelTextColor), + ), + ], + ), + ), + SizedBox(height: 6), + AutoSizeText(availableBalance, + style: TextStyle( + fontSize: 24, + fontFamily: 'Lato', + fontWeight: FontWeight.w900, + color: Theme.of(context) + .extension()! + .balanceAmountColor, + height: 1), + maxLines: 1, + textAlign: TextAlign.start), + SizedBox(height: 6), + if (isTestnet) + Text(S.of(context).testnet_coins_no_value, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: + Theme.of(context).extension()!.textColor, + height: 1)), + if (!isTestnet) + Text('${availableFiatBalance}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w500, + color: + Theme.of(context).extension()!.textColor, + height: 1)), + ], + ), + SizedBox( + width: min(MediaQuery.of(context).size.width * 0.2, 100), + child: Center( + child: Column( + children: [ + CakeImageWidget( + imageUrl: currency.iconPath, + height: 40, + width: 40, + displayOnError: Container( + height: 30.0, + width: 30.0, + child: Center( + child: Text( + currency.title.substring(0, min(currency.title.length, 2)), + style: TextStyle(fontSize: 11), + ), + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey.shade400, + ), + ), + ), + const SizedBox(height: 10), + Text( + currency.title, + style: TextStyle( + fontSize: 15, + fontFamily: 'Lato', + fontWeight: FontWeight.w800, + color: Theme.of(context) + .extension()! + .assetTitleColor, + height: 1, + ), + ), + ], + ), + ), + ), + ], + ), + ), + if (frozenBalance.isNotEmpty) + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: hasAdditionalBalance + ? () => _showBalanceDescription( + context, S.of(context).unavailable_balance_description) + : null, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 26), + Row( + children: [ + Text( + S.of(context).unavailable_balance, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: + Theme.of(context).extension()!.labelTextColor, + height: 1, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Icon(Icons.help_outline, + size: 16, + color: Theme.of(context) + .extension()! + .labelTextColor), + ), + ], + ), + SizedBox(height: 8), + AutoSizeText( + frozenBalance, + style: TextStyle( + fontSize: 20, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: + Theme.of(context).extension()!.balanceAmountColor, + height: 1, + ), + maxLines: 1, + textAlign: TextAlign.center, + ), + SizedBox(height: 4), + if (!isTestnet) + Text( + frozenFiatBalance, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).extension()!.textColor, + height: 1, + ), + ), + ], + ), + ), + if (hasAdditionalBalance) + GestureDetector( + onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24), + Text( + '${additionalBalanceLabel}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).extension()!.labelTextColor, + height: 1, + ), + ), + SizedBox(height: 8), + AutoSizeText( + additionalBalance, + style: TextStyle( + fontSize: 20, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).extension()!.assetTitleColor, + height: 1, + ), + maxLines: 1, + textAlign: TextAlign.center, + ), + SizedBox(height: 4), + if (!isTestnet) + Text( + '${additionalFiatBalance}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).extension()!.textColor, + height: 1, + ), + ), + ], + ), + ), + ], + ), + ), + ), + if (hasSecondAdditionalBalance || hasSecondAvailableBalance) ...[ + SizedBox(height: 10), + Container( + margin: const EdgeInsets.only(left: 16, right: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.0), + border: Border.all( + color: Theme.of(context).extension()!.cardBorderColor, + width: 1, + ), + color: Theme.of(context).extension()!.syncedBackgroundColor, + ), + child: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16), + child: Stack( + children: [ + if (currency == CryptoCurrency.ltc) + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + padding: EdgeInsets.only(right: 16, top: 0), + child: Column( + children: [ + Container( + child: ImageIcon( + AssetImage('assets/images/mweb_logo.png'), + color: Theme.of(context) + .extension()! + .assetTitleColor, + size: 40, + ), + ), + const SizedBox(height: 10), + Text( + 'MWEB', + style: TextStyle( + fontSize: 15, + fontFamily: 'Lato', + fontWeight: FontWeight.w800, + color: Theme.of(context) + .extension()! + .assetTitleColor, + height: 1, + ), + ), + ], + ), + ), + ], + ), + if (hasSecondAvailableBalance) + GestureDetector( + onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => launchUrl( + Uri.parse( + "https://docs.cakewallet.com/cryptos/litecoin.html#mweb"), + mode: LaunchMode.externalApplication, + ), + child: Row( + children: [ + Text( + '${secondAvailableBalanceLabel}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .labelTextColor, + height: 1, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Icon(Icons.help_outline, + size: 16, + color: Theme.of(context) + .extension()! + .labelTextColor), + ) + ], + ), + ), + SizedBox(height: 8), + AutoSizeText( + secondAvailableBalance, + style: TextStyle( + fontSize: 24, + fontFamily: 'Lato', + fontWeight: FontWeight.w900, + color: Theme.of(context) + .extension()! + .assetTitleColor, + height: 1, + ), + maxLines: 1, + textAlign: TextAlign.center, + ), + SizedBox(height: 6), + if (!isTestnet) + Text( + '${secondAvailableFiatBalance}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w500, + color: Theme.of(context) + .extension()! + .textColor, + height: 1, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + Container( + margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16), + child: Stack( + children: [ + if (hasSecondAdditionalBalance) + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24), + Text( + '${secondAdditionalBalanceLabel}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .labelTextColor, + height: 1, + ), + ), + SizedBox(height: 8), + AutoSizeText( + secondAdditionalBalance, + style: TextStyle( + fontSize: 20, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .assetTitleColor, + height: 1, + ), + maxLines: 1, + textAlign: TextAlign.center, + ), + SizedBox(height: 4), + if (!isTestnet) + Text( + '${secondAdditionalFiatBalance}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .textColor, + height: 1, + ), + ), + ], + ), + ], + ), + ], + ), + ), + IntrinsicHeight( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Semantics( + label: S.of(context).litecoin_mweb_pegin, + child: OutlinedButton( + onPressed: () { + final mwebAddress = + bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet); + PaymentRequest? paymentRequest = null; + if ((mwebAddress?.isNotEmpty ?? false)) { + paymentRequest = PaymentRequest.fromUri( + Uri.parse("litecoin:${mwebAddress}")); + } + Navigator.pushNamed( + context, + Routes.send, + arguments: { + 'paymentRequest': paymentRequest, + 'coinTypeToSpendFrom': UnspentCoinType.nonMweb, + }, + ); + }, + style: OutlinedButton.styleFrom( + backgroundColor: Colors.grey.shade400.withAlpha(50), + side: BorderSide( + color: Colors.grey.shade400.withAlpha(50), width: 0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Container( + padding: EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + height: 30, + width: 30, + 'assets/images/received.png', + color: Theme.of(context) + .extension()! + .balanceAmountColor, + ), + const SizedBox(width: 8), + Text( + S.of(context).litecoin_mweb_pegin, + style: TextStyle( + color: Theme.of(context) + .extension()! + .textColor, + ), + ), + ], + ), + ), + ), + ), + ), + SizedBox(width: 24), + Expanded( + child: Semantics( + label: S.of(context).litecoin_mweb_pegout, + child: OutlinedButton( + onPressed: () { + final litecoinAddress = + bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet); + PaymentRequest? paymentRequest = null; + if ((litecoinAddress?.isNotEmpty ?? false)) { + paymentRequest = PaymentRequest.fromUri( + Uri.parse("litecoin:${litecoinAddress}")); + } + Navigator.pushNamed( + context, + Routes.send, + arguments: { + 'paymentRequest': paymentRequest, + 'coinTypeToSpendFrom': UnspentCoinType.mweb, + }, + ); + }, + style: OutlinedButton.styleFrom( + backgroundColor: Colors.grey.shade400.withAlpha(50), + side: BorderSide( + color: Colors.grey.shade400.withAlpha(50), width: 0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Container( + padding: EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + height: 30, + width: 30, + 'assets/images/upload.png', + color: Theme.of(context) + .extension()! + .balanceAmountColor, + ), + const SizedBox(width: 8), + Text( + S.of(context).litecoin_mweb_pegout, + style: TextStyle( + color: Theme.of(context) + .extension()! + .textColor, + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + SizedBox(height: 16), + ], + ), + ), + ), + ], + ], + ); + } + + void _showBalanceDescription(BuildContext context, String content) { + showPopUp(context: context, builder: (_) => InformationPage(information: content)); + } +} diff --git a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart new file mode 100644 index 000000000..0bdf388d3 --- /dev/null +++ b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart @@ -0,0 +1,424 @@ +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/dashboard/pages/balance/balance_row_widget.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; +import 'package:cake_wallet/src/widgets/introducing_card.dart'; +import 'package:cake_wallet/src/widgets/standard_switch.dart'; +import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class CryptoBalanceWidget extends StatelessWidget { + const CryptoBalanceWidget({ + super.key, + required this.dashboardViewModel, + }); + + final DashboardViewModel dashboardViewModel; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + 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( + walletName: dashboardViewModel.name, accountName: dashboardViewModel.subname) + : Column( + children: [ + SizedBox(height: 16), + Container( + margin: const EdgeInsets.only(left: 24, bottom: 16), + child: Observer( + builder: (_) { + return Row( + children: [ + Text( + dashboardViewModel.balanceViewModel.asset, + style: TextStyle( + fontSize: 24, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context) + .extension()! + .pageTitleTextColor, + height: 1, + ), + maxLines: 1, + textAlign: TextAlign.center, + ), + if (dashboardViewModel.wallet.isHardwareWallet) + Padding( + padding: const EdgeInsets.all(8.0), + child: Image.asset( + 'assets/images/hardware_wallet/ledger_nano_x.png', + width: 24, + color: Theme.of(context) + .extension()! + .pageTitleTextColor, + ), + ), + if (dashboardViewModel + .balanceViewModel.isHomeScreenSettingsEnabled) + InkWell( + onTap: () => Navigator.pushNamed(context, Routes.homeSettings, + arguments: dashboardViewModel.balanceViewModel), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.asset( + 'assets/images/home_screen_settings_icon.png', + color: Theme.of(context) + .extension()! + .pageTitleTextColor, + ), + ), + ), + ], + ); + }, + ), + ), + ], + )), + Observer( + builder: (_) { + if (dashboardViewModel.balanceViewModel.isShowCard && FeatureFlag.isCakePayEnabled) { + return IntroducingCard( + title: S.of(context).introducing_cake_pay, + subTitle: S.of(context).cake_pay_learn_more, + borderColor: Theme.of(context).extension()!.cardBorderColor, + closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard); + } + return Container(); + }, + ), + Observer(builder: (_) { + if (!dashboardViewModel.showRepWarning) { + return const SizedBox(); + } + return Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: DashBoardRoundedCardWidget( + title: S.of(context).rep_warning, + subTitle: S.of(context).rep_warning_sub, + onTap: () => Navigator.of(context).pushNamed(Routes.changeRep), + onClose: () { + dashboardViewModel.settingsStore.shouldShowRepWarning = false; + }, + ), + ); + }), + Observer( + builder: (_) { + return ListView.separated( + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)), + itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length, + itemBuilder: (__, index) { + final balance = + dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index); + return Observer(builder: (_) { + return BalanceRowWidget( + dashboardViewModel: dashboardViewModel, + availableBalanceLabel: + '${dashboardViewModel.balanceViewModel.availableBalanceLabel}', + availableBalance: balance.availableBalance, + availableFiatBalance: balance.fiatAvailableBalance, + additionalBalanceLabel: + '${dashboardViewModel.balanceViewModel.additionalBalanceLabel}', + additionalBalance: balance.additionalBalance, + additionalFiatBalance: balance.fiatAdditionalBalance, + frozenBalance: balance.frozenBalance, + frozenFiatBalance: balance.fiatFrozenBalance, + currency: balance.asset, + hasAdditionalBalance: + dashboardViewModel.balanceViewModel.hasAdditionalBalance, + hasSecondAdditionalBalance: + dashboardViewModel.balanceViewModel.hasSecondAdditionalBalance, + hasSecondAvailableBalance: + dashboardViewModel.balanceViewModel.hasSecondAvailableBalance, + secondAdditionalBalance: balance.secondAdditionalBalance, + secondAdditionalFiatBalance: balance.fiatSecondAdditionalBalance, + secondAvailableBalance: balance.secondAvailableBalance, + secondAvailableFiatBalance: balance.fiatSecondAvailableBalance, + secondAdditionalBalanceLabel: + '${dashboardViewModel.balanceViewModel.secondAdditionalBalanceLabel}', + secondAvailableBalanceLabel: + '${dashboardViewModel.balanceViewModel.secondAvailableBalanceLabel}', + isTestnet: dashboardViewModel.isTestnet, + ); + }); + }, + ); + }, + ), + 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: "This wallet has encountered an issue", + subTitle: "Here are the things that you should note:\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( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: DashBoardRoundedCardWidget( + customBorder: 30, + title: S.of(context).silent_payments, + subTitle: S.of(context).enable_silent_payments_scanning, + hint: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => launchUrl( + Uri.parse( + "https://docs.cakewallet.com/cryptos/bitcoin#silent-payments"), + mode: LaunchMode.externalApplication, + ), + child: Row( + children: [ + Text( + S.of(context).what_is_silent_payments, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .labelTextColor, + height: 1, + ), + softWrap: true, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Icon(Icons.help_outline, + size: 16, + color: Theme.of(context) + .extension()! + .labelTextColor), + ) + ], + ), + ), + Observer( + builder: (_) => StandardSwitch( + value: dashboardViewModel.silentPaymentsScanningActive, + onTaped: () => _toggleSilentPaymentsScanning(context), + ), + ) + ], + ), + ], + ), + onTap: () => _toggleSilentPaymentsScanning(context), + icon: Icon( + Icons.lock, + color: + Theme.of(context).extension()!.pageTitleTextColor, + size: 50, + ), + ), + ), + ], + if (dashboardViewModel.showMwebCard) ...[ + SizedBox(height: 10), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: DashBoardRoundedCardWidget( + customBorder: 30, + title: S.of(context).litecoin_mweb, + subTitle: S.of(context).litecoin_mweb_description, + hint: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => launchUrl( + Uri.parse("https://docs.cakewallet.com/cryptos/litecoin/#mweb"), + mode: LaunchMode.externalApplication, + ), + child: Text( + S.of(context).learn_more, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: + Theme.of(context).extension()!.labelTextColor, + height: 1, + ), + softWrap: true, + ), + ), + SizedBox(height: 8), + Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () => _dismissMweb(context), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).primaryColor, + ), + child: Text( + S.of(context).litecoin_mweb_dismiss, + style: TextStyle(color: Colors.white), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: ElevatedButton( + onPressed: () => _enableMweb(context), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + ), + child: Text( + S.of(context).enable, + maxLines: 1, + ), + ), + ), + ], + ), + ], + ), + onTap: () => {}, + icon: Container( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: ImageIcon( + AssetImage('assets/images/mweb_logo.png'), + color: Color.fromARGB(255, 11, 70, 129), + size: 40, + ), + ), + ), + ), + ], + ], + ); + }), + ], + ), + ); + } + + Future _toggleSilentPaymentsScanning(BuildContext context) async { + final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive; + final newValue = !isSilentPaymentsScanningActive; + + dashboardViewModel.silentPaymentsScanningActive = newValue; + + final needsToSwitch = !isSilentPaymentsScanningActive && + await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false; + + if (needsToSwitch) { + return showPopUp( + context: context, + builder: (BuildContext context) => AlertWithTwoActions( + alertTitle: S.of(context).change_current_node_title, + alertContent: S.of(context).confirm_silent_payments_switch_node, + rightButtonText: S.of(context).confirm, + leftButtonText: S.of(context).cancel, + actionRightButton: () { + dashboardViewModel.setSilentPaymentsScanning(newValue); + Navigator.of(context).pop(); + }, + actionLeftButton: () { + dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive; + Navigator.of(context).pop(); + }, + )); + } + + return dashboardViewModel.setSilentPaymentsScanning(newValue); + } + + Future _enableMweb(BuildContext context) async { + if (!dashboardViewModel.hasEnabledMwebBefore) { + await showPopUp( + context: context, + builder: (BuildContext context) => AlertWithOneAction( + alertTitle: S.of(context).alert_notice, + alertContent: S.of(context).litecoin_mweb_warning, + buttonText: S.of(context).understand, + buttonAction: () { + Navigator.of(context).pop(); + }, + )); + } + dashboardViewModel.setMwebEnabled(); + } + + Future _dismissMweb(BuildContext context) async { + await showPopUp( + context: context, + builder: (BuildContext context) => AlertWithOneAction( + alertTitle: S.of(context).alert_notice, + alertContent: S.of(context).litecoin_mweb_enable_later, + buttonText: S.of(context).understand, + buttonAction: () { + Navigator.of(context).pop(); + }, + )); + dashboardViewModel.dismissMweb(); + } +} diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart deleted file mode 100644 index a71a6288b..000000000 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ /dev/null @@ -1,1164 +0,0 @@ -import 'dart:math'; - -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/reactions/wallet_connect.dart'; -import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart'; -import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; -import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; -import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart'; -import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; -import 'package:cake_wallet/src/widgets/introducing_card.dart'; -import 'package:cake_wallet/src/widgets/standard_switch.dart'; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; -import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; -import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; -import 'package:cake_wallet/utils/feature_flag.dart'; -import 'package:cake_wallet/utils/payment_request.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/unspent_coin_type.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class BalancePage extends StatelessWidget { - BalancePage({ - required this.dashboardViewModel, - required this.settingsStore, - required this.nftViewModel, - }); - - final DashboardViewModel dashboardViewModel; - final NFTViewModel nftViewModel; - final SettingsStore settingsStore; - - @override - Widget build(BuildContext context) { - return Observer( - builder: (context) { - final isEVMCompatible = isEVMCompatibleChain(dashboardViewModel.type); - return DefaultTabController( - length: isEVMCompatible ? 2 : 1, - child: Column( - children: [ - if (isEVMCompatible) - Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.only(left: 8), - child: TabBar( - indicatorSize: TabBarIndicatorSize.label, - isScrollable: true, - physics: NeverScrollableScrollPhysics(), - labelStyle: TextStyle( - fontSize: 18, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: - Theme.of(context).extension()!.pageTitleTextColor, - height: 1, - ), - unselectedLabelStyle: TextStyle( - fontSize: 18, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: - Theme.of(context).extension()!.pageTitleTextColor, - height: 1, - ), - labelColor: - Theme.of(context).extension()!.pageTitleTextColor, - dividerColor: Colors.transparent, - indicatorColor: - Theme.of(context).extension()!.pageTitleTextColor, - unselectedLabelColor: Theme.of(context) - .extension()! - .pageTitleTextColor - .withOpacity(0.5), - tabAlignment: TabAlignment.start, - tabs: [ - Tab(text: 'My Crypto'), - Tab(text: 'My NFTs'), - ], - ), - ), - ), - Expanded( - child: TabBarView( - physics: NeverScrollableScrollPhysics(), - children: [ - CryptoBalanceWidget(dashboardViewModel: dashboardViewModel), - if (isEVMCompatible) NFTListingPage(nftViewModel: nftViewModel) - ], - ), - ), - ], - ), - ); - }, - ); - } -} - -class CryptoBalanceWidget extends StatelessWidget { - const CryptoBalanceWidget({ - super.key, - required this.dashboardViewModel, - }); - - final DashboardViewModel dashboardViewModel; - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - 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( - walletName: dashboardViewModel.name, - accountName: dashboardViewModel.subname) - : Column( - children: [ - SizedBox(height: 16), - Container( - margin: const EdgeInsets.only(left: 24, bottom: 16), - child: Observer( - builder: (_) { - return Row( - children: [ - Text( - dashboardViewModel.balanceViewModel.asset, - style: TextStyle( - fontSize: 24, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context) - .extension()! - .pageTitleTextColor, - height: 1, - ), - maxLines: 1, - textAlign: TextAlign.center, - ), - if (dashboardViewModel.wallet.isHardwareWallet) - Padding( - padding: const EdgeInsets.all(8.0), - child: Image.asset( - 'assets/images/hardware_wallet/ledger_nano_x.png', - width: 24, - color: Theme.of(context) - .extension()! - .pageTitleTextColor, - ), - ), - if (dashboardViewModel - .balanceViewModel.isHomeScreenSettingsEnabled) - InkWell( - onTap: () => Navigator.pushNamed( - context, Routes.homeSettings, - arguments: dashboardViewModel.balanceViewModel), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Image.asset( - 'assets/images/home_screen_settings_icon.png', - color: Theme.of(context) - .extension()! - .pageTitleTextColor, - ), - ), - ), - ], - ); - }, - ), - ), - ], - )), - Observer( - builder: (_) { - if (dashboardViewModel.balanceViewModel.isShowCard && - FeatureFlag.isCakePayEnabled) { - return IntroducingCard( - title: S.of(context).introducing_cake_pay, - subTitle: S.of(context).cake_pay_learn_more, - borderColor: Theme.of(context).extension()!.cardBorderColor, - closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard); - } - return Container(); - }, - ), - Observer(builder: (_) { - if (!dashboardViewModel.showRepWarning) { - return const SizedBox(); - } - return Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), - child: DashBoardRoundedCardWidget( - title: S.of(context).rep_warning, - subTitle: S.of(context).rep_warning_sub, - onTap: () => Navigator.of(context).pushNamed(Routes.changeRep), - onClose: () { - dashboardViewModel.settingsStore.shouldShowRepWarning = false; - }, - ), - ); - }), - Observer( - builder: (_) { - return ListView.separated( - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)), - itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length, - itemBuilder: (__, index) { - final balance = - dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index); - return Observer(builder: (_) { - return BalanceRowWidget( - dashboardViewModel: dashboardViewModel, - availableBalanceLabel: - '${dashboardViewModel.balanceViewModel.availableBalanceLabel}', - availableBalance: balance.availableBalance, - availableFiatBalance: balance.fiatAvailableBalance, - additionalBalanceLabel: - '${dashboardViewModel.balanceViewModel.additionalBalanceLabel}', - additionalBalance: balance.additionalBalance, - additionalFiatBalance: balance.fiatAdditionalBalance, - frozenBalance: balance.frozenBalance, - frozenFiatBalance: balance.fiatFrozenBalance, - currency: balance.asset, - hasAdditionalBalance: - dashboardViewModel.balanceViewModel.hasAdditionalBalance, - hasSecondAdditionalBalance: - dashboardViewModel.balanceViewModel.hasSecondAdditionalBalance, - hasSecondAvailableBalance: - dashboardViewModel.balanceViewModel.hasSecondAvailableBalance, - secondAdditionalBalance: balance.secondAdditionalBalance, - secondAdditionalFiatBalance: balance.fiatSecondAdditionalBalance, - secondAvailableBalance: balance.secondAvailableBalance, - secondAvailableFiatBalance: balance.fiatSecondAvailableBalance, - secondAdditionalBalanceLabel: - '${dashboardViewModel.balanceViewModel.secondAdditionalBalanceLabel}', - secondAvailableBalanceLabel: - '${dashboardViewModel.balanceViewModel.secondAvailableBalanceLabel}', - isTestnet: dashboardViewModel.isTestnet, - ); - }); - }, - ); - }, - ), - 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: "This wallet has encountered an issue", - subTitle: "Here are the things that you should note:\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( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), - child: DashBoardRoundedCardWidget( - customBorder: 30, - title: S.of(context).silent_payments, - subTitle: S.of(context).enable_silent_payments_scanning, - hint: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => launchUrl( - Uri.parse( - "https://docs.cakewallet.com/cryptos/bitcoin#silent-payments"), - mode: LaunchMode.externalApplication, - ), - child: Row( - children: [ - Text( - S.of(context).what_is_silent_payments, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .labelTextColor, - height: 1, - ), - softWrap: true, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Icon(Icons.help_outline, - size: 16, - color: Theme.of(context) - .extension()! - .labelTextColor), - ) - ], - ), - ), - Observer( - builder: (_) => StandardSwitch( - value: dashboardViewModel.silentPaymentsScanningActive, - onTaped: () => _toggleSilentPaymentsScanning(context), - ), - ) - ], - ), - ], - ), - onTap: () => _toggleSilentPaymentsScanning(context), - icon: Icon( - Icons.lock, - color: - Theme.of(context).extension()!.pageTitleTextColor, - size: 50, - ), - ), - ), - ], - if (dashboardViewModel.showMwebCard) ...[ - SizedBox(height: 10), - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), - child: DashBoardRoundedCardWidget( - customBorder: 30, - title: S.of(context).litecoin_mweb, - subTitle: S.of(context).litecoin_mweb_description, - hint: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => launchUrl( - Uri.parse( - "https://docs.cakewallet.com/cryptos/litecoin/#mweb"), - mode: LaunchMode.externalApplication, - ), - child: Text( - S.of(context).learn_more, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .labelTextColor, - height: 1, - ), - softWrap: true, - ), - ), - SizedBox(height: 8), - Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: () => _dismissMweb(context), - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - ), - child: Text( - S.of(context).litecoin_mweb_dismiss, - style: TextStyle(color: Colors.white), - ), - ), - ), - const SizedBox(width: 8), - Expanded( - child: ElevatedButton( - onPressed: () => _enableMweb(context), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Colors.black, - ), - child: Text( - S.of(context).enable, - maxLines: 1, - ), - ), - ), - ], - ), - ], - ), - onTap: () => {}, - icon: Container( - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - child: ImageIcon( - AssetImage('assets/images/mweb_logo.png'), - color: Color.fromARGB(255, 11, 70, 129), - size: 40, - ), - ), - ), - ), - ], - ], - ); - }), - ], - ), - ); - } - - Future _toggleSilentPaymentsScanning(BuildContext context) async { - final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive; - final newValue = !isSilentPaymentsScanningActive; - - dashboardViewModel.silentPaymentsScanningActive = newValue; - - final needsToSwitch = !isSilentPaymentsScanningActive && - await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false; - - if (needsToSwitch) { - return showPopUp( - context: context, - builder: (BuildContext context) => AlertWithTwoActions( - alertTitle: S.of(context).change_current_node_title, - alertContent: S.of(context).confirm_silent_payments_switch_node, - rightButtonText: S.of(context).confirm, - leftButtonText: S.of(context).cancel, - actionRightButton: () { - dashboardViewModel.setSilentPaymentsScanning(newValue); - Navigator.of(context).pop(); - }, - actionLeftButton: () { - dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive; - Navigator.of(context).pop(); - }, - )); - } - - return dashboardViewModel.setSilentPaymentsScanning(newValue); - } - - Future _enableMweb(BuildContext context) async { - if (!dashboardViewModel.hasEnabledMwebBefore) { - await showPopUp( - context: context, - builder: (BuildContext context) => AlertWithOneAction( - alertTitle: S.of(context).alert_notice, - alertContent: S.of(context).litecoin_mweb_warning, - buttonText: S.of(context).understand, - buttonAction: () { - Navigator.of(context).pop(); - }, - )); - } - dashboardViewModel.setMwebEnabled(); - } - - Future _dismissMweb(BuildContext context) async { - await showPopUp( - context: context, - builder: (BuildContext context) => AlertWithOneAction( - alertTitle: S.of(context).alert_notice, - alertContent: S.of(context).litecoin_mweb_enable_later, - buttonText: S.of(context).understand, - buttonAction: () { - Navigator.of(context).pop(); - }, - )); - dashboardViewModel.dismissMweb(); - } -} - -class BalanceRowWidget extends StatelessWidget { - BalanceRowWidget({ - required this.availableBalanceLabel, - required this.availableBalance, - required this.availableFiatBalance, - required this.additionalBalanceLabel, - required this.additionalBalance, - required this.additionalFiatBalance, - required this.secondAvailableBalanceLabel, - required this.secondAvailableBalance, - required this.secondAvailableFiatBalance, - required this.secondAdditionalBalanceLabel, - required this.secondAdditionalBalance, - required this.secondAdditionalFiatBalance, - required this.frozenBalance, - required this.frozenFiatBalance, - required this.currency, - required this.hasAdditionalBalance, - required this.hasSecondAvailableBalance, - required this.hasSecondAdditionalBalance, - required this.isTestnet, - required this.dashboardViewModel, - super.key, - }); - - final String availableBalanceLabel; - final String availableBalance; - final String availableFiatBalance; - final String additionalBalanceLabel; - final String additionalBalance; - final String additionalFiatBalance; - final String secondAvailableBalanceLabel; - final String secondAvailableBalance; - final String secondAvailableFiatBalance; - final String secondAdditionalBalanceLabel; - final String secondAdditionalBalance; - final String secondAdditionalFiatBalance; - final String frozenBalance; - final String frozenFiatBalance; - final CryptoCurrency currency; - final bool hasAdditionalBalance; - final bool hasSecondAvailableBalance; - final bool hasSecondAdditionalBalance; - final bool isTestnet; - final DashboardViewModel dashboardViewModel; - - // void _showBalanceDescription(BuildContext context) { - // showPopUp( - // context: context, - // builder: (_) => - // InformationPage(information: S.of(context).available_balance_description), - // ); - // } - - @override - Widget build(BuildContext context) { - return Column(children: [ - Container( - margin: const EdgeInsets.only(left: 16, right: 16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30.0), - border: Border.all( - color: Theme.of(context).extension()!.cardBorderColor, - width: 1, - ), - color: Theme.of(context).extension()!.syncedBackgroundColor, - ), - child: Container( - margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: hasAdditionalBalance - ? () => _showBalanceDescription( - context, S.of(context).available_balance_description) - : null, - child: Row( - children: [ - 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), - child: Icon(Icons.help_outline, - size: 16, - color: Theme.of(context) - .extension()! - .labelTextColor), - ), - ], - ), - ), - SizedBox(height: 6), - AutoSizeText(availableBalance, - style: TextStyle( - fontSize: 24, - fontFamily: 'Lato', - fontWeight: FontWeight.w900, - color: Theme.of(context) - .extension()! - .balanceAmountColor, - height: 1), - maxLines: 1, - textAlign: TextAlign.start), - SizedBox(height: 6), - if (isTestnet) - Text(S.of(context).testnet_coins_no_value, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context).extension()!.textColor, - height: 1)), - if (!isTestnet) - Text('${availableFiatBalance}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.textColor, - height: 1)), - - ], - ), - - SizedBox( - width: min(MediaQuery.of(context).size.width * 0.2, 100), - child: Center( - child: Column( - children: [ - CakeImageWidget( - imageUrl: currency.iconPath, - height: 40, - width: 40, - displayOnError: Container( - height: 30.0, - width: 30.0, - child: Center( - child: Text( - currency.title.substring(0, min(currency.title.length, 2)), - style: TextStyle(fontSize: 11), - ), - ), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.grey.shade400, - ), - ), - ), - const SizedBox(height: 10), - Text( - currency.title, - style: TextStyle( - fontSize: 15, - fontFamily: 'Lato', - fontWeight: FontWeight.w800, - color: - Theme.of(context).extension()!.assetTitleColor, - height: 1, - ), - ), - ], - ), - ), - ), - ], - ), - ), - if (frozenBalance.isNotEmpty) - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: hasAdditionalBalance - ? () => _showBalanceDescription( - context, S.of(context).unavailable_balance_description) - : null, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 26), - Row( - children: [ - Text( - S.of(context).unavailable_balance, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: - Theme.of(context).extension()!.labelTextColor, - height: 1, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Icon(Icons.help_outline, - size: 16, - color: Theme.of(context) - .extension()! - .labelTextColor), - ), - ], - ), - SizedBox(height: 8), - AutoSizeText( - frozenBalance, - style: TextStyle( - fontSize: 20, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: - Theme.of(context).extension()!.balanceAmountColor, - height: 1, - ), - maxLines: 1, - textAlign: TextAlign.center, - ), - SizedBox(height: 4), - if (!isTestnet) - Text( - frozenFiatBalance, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context).extension()!.textColor, - height: 1, - ), - ), - ], - ), - ), - if (hasAdditionalBalance) - GestureDetector( - onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24), - Text( - '${additionalBalanceLabel}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context).extension()!.labelTextColor, - height: 1, - ), - ), - SizedBox(height: 8), - AutoSizeText( - additionalBalance, - style: TextStyle( - fontSize: 20, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context).extension()!.assetTitleColor, - height: 1, - ), - maxLines: 1, - textAlign: TextAlign.center, - ), - SizedBox(height: 4), - if (!isTestnet) - Text( - '${additionalFiatBalance}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context).extension()!.textColor, - height: 1, - ), - ), - ], - ), - ), - ], - ), - ), - ), - if (hasSecondAdditionalBalance || hasSecondAvailableBalance) ...[ - SizedBox(height: 10), - Container( - margin: const EdgeInsets.only(left: 16, right: 16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30.0), - border: Border.all( - color: Theme.of(context).extension()!.cardBorderColor, - width: 1, - ), - color: Theme.of(context).extension()!.syncedBackgroundColor, - ), - child: Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16), - child: Stack( - children: [ - if (currency == CryptoCurrency.ltc) - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - padding: EdgeInsets.only(right: 16, top: 0), - child: Column( - children: [ - Container( - child: ImageIcon( - AssetImage('assets/images/mweb_logo.png'), - color: Theme.of(context) - .extension()! - .assetTitleColor, - size: 40, - ), - ), - const SizedBox(height: 10), - Text( - 'MWEB', - style: TextStyle( - fontSize: 15, - fontFamily: 'Lato', - fontWeight: FontWeight.w800, - color: Theme.of(context) - .extension()! - .assetTitleColor, - height: 1, - ), - ), - ], - ), - ), - ], - ), - if (hasSecondAvailableBalance) - GestureDetector( - onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(), - child: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => launchUrl( - Uri.parse( - "https://docs.cakewallet.com/cryptos/litecoin.html#mweb"), - mode: LaunchMode.externalApplication, - ), - child: Row( - children: [ - Text( - '${secondAvailableBalanceLabel}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .labelTextColor, - height: 1, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Icon(Icons.help_outline, - size: 16, - color: Theme.of(context) - .extension()! - .labelTextColor), - ) - ], - ), - ), - SizedBox(height: 8), - AutoSizeText( - secondAvailableBalance, - style: TextStyle( - fontSize: 24, - fontFamily: 'Lato', - fontWeight: FontWeight.w900, - color: Theme.of(context) - .extension()! - .assetTitleColor, - height: 1, - ), - maxLines: 1, - textAlign: TextAlign.center, - ), - SizedBox(height: 6), - if (!isTestnet) - Text( - '${secondAvailableFiatBalance}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.w500, - color: Theme.of(context) - .extension()! - .textColor, - height: 1, - ), - ), - ], - ), - ], - ), - ), - ], - ), - ), - Container( - margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16), - child: Stack( - children: [ - if (hasSecondAdditionalBalance) - Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24), - Text( - '${secondAdditionalBalanceLabel}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .labelTextColor, - height: 1, - ), - ), - SizedBox(height: 8), - AutoSizeText( - secondAdditionalBalance, - style: TextStyle( - fontSize: 20, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .assetTitleColor, - height: 1, - ), - maxLines: 1, - textAlign: TextAlign.center, - ), - SizedBox(height: 4), - if (!isTestnet) - Text( - '${secondAdditionalFiatBalance}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .textColor, - height: 1, - ), - ), - ], - ), - ], - ), - ], - ), - ), - IntrinsicHeight( - child: Container( - padding: EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Semantics( - label: S.of(context).litecoin_mweb_pegin, - child: OutlinedButton( - onPressed: () { - final mwebAddress = - bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet); - PaymentRequest? paymentRequest = null; - if ((mwebAddress?.isNotEmpty ?? false)) { - paymentRequest = - PaymentRequest.fromUri(Uri.parse("litecoin:${mwebAddress}")); - } - Navigator.pushNamed( - context, - Routes.send, - arguments: { - 'paymentRequest': paymentRequest, - 'coinTypeToSpendFrom': UnspentCoinType.nonMweb, - }, - ); - }, - style: OutlinedButton.styleFrom( - backgroundColor: Colors.grey.shade400 - .withAlpha(50), - side: BorderSide(color: Colors.grey.shade400 - .withAlpha(50), width: 0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - ), - child: Container( - padding: EdgeInsets.symmetric(vertical: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - height: 30, - width: 30, - 'assets/images/received.png', - color: Theme.of(context) - .extension()! - .balanceAmountColor, - ), - const SizedBox(width: 8), - Text( - S.of(context).litecoin_mweb_pegin, - style: TextStyle( - color: Theme.of(context) - .extension()! - .textColor, - ), - ), - ], - ), - ), - ), - ), - ), - SizedBox(width: 24), - Expanded( - child: Semantics( - label: S.of(context).litecoin_mweb_pegout, - child: OutlinedButton( - onPressed: () { - final litecoinAddress = - bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet); - PaymentRequest? paymentRequest = null; - if ((litecoinAddress?.isNotEmpty ?? false)) { - paymentRequest = PaymentRequest.fromUri( - Uri.parse("litecoin:${litecoinAddress}")); - } - Navigator.pushNamed( - context, - Routes.send, - arguments: { - 'paymentRequest': paymentRequest, - 'coinTypeToSpendFrom': UnspentCoinType.mweb, - }, - ); - }, - style: OutlinedButton.styleFrom( - backgroundColor: Colors.grey.shade400 - .withAlpha(50), - side: BorderSide(color: Colors.grey.shade400 - .withAlpha(50), width: 0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - ), - child: Container( - padding: EdgeInsets.symmetric(vertical: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - height: 30, - width: 30, - 'assets/images/upload.png', - color: Theme.of(context) - .extension()! - .balanceAmountColor, - ), - const SizedBox(width: 8), - Text( - S.of(context).litecoin_mweb_pegout, - style: TextStyle( - color: Theme.of(context) - .extension()! - .textColor, - ), - ), - ], - ), - ), - ), - ), - ), - ], - ), - ), - ), - SizedBox(height: 16), - ], - ), - ), - ), - ], - ]); - } - - void _showBalanceDescription(BuildContext context, String content) { - showPopUp(context: context, builder: (_) => InformationPage(information: content)); - } -} diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 075cf6b75..0c4407e60 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -158,17 +158,17 @@ abstract class BalanceViewModelBase with Store { case WalletType.banano: case WalletType.solana: case WalletType.tron: + case WalletType.bitcoin: + case WalletType.litecoin: + case WalletType.bitcoinCash: + case WalletType.none: return S.current.xmr_available_balance; - default: - return S.current.confirmed; } } @computed String get additionalBalanceLabel { switch (wallet.type) { - case WalletType.monero: - case WalletType.wownero: case WalletType.haven: case WalletType.ethereum: case WalletType.polygon: @@ -357,7 +357,12 @@ abstract class BalanceViewModelBase with Store { bool mwebEnabled = false; @computed - bool get hasAdditionalBalance => _hasAdditionalBalanceForWalletType(wallet.type); + bool get hasAdditionalBalance { + bool isWalletTypeActivated = _hasAdditionalBalanceForWalletType(wallet.type); + bool isNotZeroAmount = additionalBalance != "0.0"; + + return isWalletTypeActivated && isNotZeroAmount; + } @computed bool get hasSecondAdditionalBalance => @@ -373,6 +378,9 @@ abstract class BalanceViewModelBase with Store { case WalletType.polygon: case WalletType.solana: case WalletType.tron: + case WalletType.bitcoin: + case WalletType.bitcoinCash: + case WalletType.litecoin: return false; default: return true; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 3a91d2cb0..28b35c35e 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -330,6 +330,7 @@ "freeze": "تجميد", "frequently_asked_questions": "الأسئلة الشائعة", "frozen": "مجمدة", + "frozen_balance": "التوازن المجمد", "full_balance": "الرصيد الكامل", "gas_exceeds_allowance": "الغاز المطلوب بالمعاملة يتجاوز البدل.", "generate_name": "توليد الاسم", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 9d86f74e4..64cd7c61f 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -330,6 +330,7 @@ "freeze": "Замразяване", "frequently_asked_questions": "Често задавани въпроси", "frozen": "Замразени", + "frozen_balance": "Замразен баланс", "full_balance": "Пълен баланс", "gas_exceeds_allowance": "Газът, изискван от транзакцията, надвишава надбавката.", "generate_name": "Генериране на име", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 25a4845d9..7d458e5af 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -330,6 +330,7 @@ "freeze": "Zmrazit", "frequently_asked_questions": "Často kladené otázky", "frozen": "Zmraženo", + "frozen_balance": "Zmrazená rovnováha", "full_balance": "Celkový zůstatek", "gas_exceeds_allowance": "Plyn vyžadovaný transakcí přesahuje příspěvek.", "generate_name": "Generovat jméno", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index cf44ab05d..0b2d22f62 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -330,6 +330,7 @@ "freeze": "Einfrieren", "frequently_asked_questions": "Häufig gestellte Fragen", "frozen": "Gefroren", + "frozen_balance": "Gefrorenes Gleichgewicht", "full_balance": "Gesamtguthaben", "gas_exceeds_allowance": "Die durch Transaktion erforderliche Gas übertrifft die Zulage.", "generate_name": "Namen generieren", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 69ba98f34..7da5e0fa1 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -330,6 +330,7 @@ "freeze": "Freeze", "frequently_asked_questions": "Frequently asked questions", "frozen": "Frozen", + "frozen_balance": "Frozen Balance", "full_balance": "Full Balance", "gas_exceeds_allowance": "Gas required by transaction exceeds allowance.", "generate_name": "Generate Name", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 3ab98c5f5..724f691c9 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -330,6 +330,7 @@ "freeze": "Congelar", "frequently_asked_questions": "Preguntas frecuentes", "frozen": "Congelada", + "frozen_balance": "Equilibrio congelado", "full_balance": "Balance completo", "gas_exceeds_allowance": "El gas requerido por la transacción excede la asignación.", "generate_name": "Generar nombre", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 50fb6c3db..85c6e2646 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -330,6 +330,7 @@ "freeze": "Geler", "frequently_asked_questions": "Foire aux questions", "frozen": "Gelées", + "frozen_balance": "Équilibre gelé", "full_balance": "Solde Complet", "gas_exceeds_allowance": "Le gaz requis par la transaction dépasse l'allocation.", "generate_name": "Générer un nom", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index a532a54b7..cb457f07a 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -330,6 +330,7 @@ "freeze": "Daskare", "frequently_asked_questions": "Tambayoyin da ake yawan yi", "frozen": "Daskararre", + "frozen_balance": "Daidaituwa mai sanyi", "full_balance": "DUKAN KUDI", "gas_exceeds_allowance": "Gas da ake buƙata ta hanyar ma'amala ya wuce izini.", "generate_name": "Ƙirƙirar Suna", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index b20291df7..2eaa53e87 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -330,6 +330,7 @@ "freeze": "फ्रीज", "frequently_asked_questions": "अक्सर पूछे जाने वाले प्रश्न", "frozen": "जमा हुआ", + "frozen_balance": "जमे हुए संतुलन", "full_balance": "पूर्ण संतुलन", "gas_exceeds_allowance": "लेनदेन द्वारा आवश्यक गैस भत्ता से अधिक है।", "generate_name": "नाम जनरेट करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 9b228d843..303009403 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -330,6 +330,7 @@ "freeze": "Zamrznuti", "frequently_asked_questions": "Često postavljana pitanja", "frozen": "Smrznuto", + "frozen_balance": "Smrznuta ravnoteža", "full_balance": "Pun iznos", "gas_exceeds_allowance": "Plin potreban transakcijom premašuje dodatak.", "generate_name": "Generiraj ime", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 668c99a14..4b03eb2dd 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -330,6 +330,7 @@ "freeze": "Կասեցնել", "frequently_asked_questions": "Հաճախ տրվող հարցեր", "frozen": "Կասեցված", + "frozen_balance": "Սառեցված հավասարակշռություն", "full_balance": "Լրիվ մնացորդ", "gas_exceeds_allowance": "Գործարքով պահանջվող գազը գերազանցում է նպաստը:", "generate_name": "Գեներացնել անուն", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 3102ea2e0..d142ab41b 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -330,6 +330,7 @@ "freeze": "Freeze", "frequently_asked_questions": "Pertanyaan yang sering diajukan", "frozen": "Dibekukan", + "frozen_balance": "Keseimbangan beku", "full_balance": "Saldo Penuh", "gas_exceeds_allowance": "Gas yang dibutuhkan oleh transaksi melebihi tunjangan.", "generate_name": "Hasilkan Nama", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index cbeaa191d..ae26f2d13 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -331,6 +331,7 @@ "freeze": "Congelare", "frequently_asked_questions": "Domande frequenti", "frozen": "Congelato", + "frozen_balance": "Equilibrio congelato", "full_balance": "Saldo Completo", "gas_exceeds_allowance": "Il gas richiesto dalla transazione supera l'indennità.", "generate_name": "Genera nome", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 95ae7b672..041265697 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -330,6 +330,7 @@ "freeze": "氷結", "frequently_asked_questions": "よくある質問", "frozen": "凍った", + "frozen_balance": "凍結バランス", "full_balance": "フルバランス", "gas_exceeds_allowance": "取引に必要なガスは、手当を超えています。", "generate_name": "名前の生成", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 4e9a7cff7..7fb3bab95 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -330,6 +330,7 @@ "freeze": "얼다", "frequently_asked_questions": "자주 묻는 질문", "frozen": "겨울 왕국", + "frozen_balance": "냉동 균형", "full_balance": "풀 밸런스", "gas_exceeds_allowance": "거래에 필요한 가스는 수당을 초과합니다.", "generate_name": "이름 생성", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index c73db85dd..1498403e0 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -330,6 +330,7 @@ "freeze": "အေးခဲ", "frequently_asked_questions": "မေးလေ့ရှိသောမေးခွန်းများ", "frozen": "ဖြူဖြူ", + "frozen_balance": "လက်ကျန်ငွေ", "full_balance": "Balance အပြည့်", "gas_exceeds_allowance": "ငွေပေးငွေယူမှလိုအပ်သောဓာတ်ငွေ့ထောက်ပံ့ကြေးကျော်လွန်။", "generate_name": "အမည်ဖန်တီးပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 367b8f625..d63b85a3e 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -330,6 +330,7 @@ "freeze": "Bevriezen", "frequently_asked_questions": "Veelgestelde vragen", "frozen": "Bevroren", + "frozen_balance": "Bevroren balans", "full_balance": "Volledig saldo", "gas_exceeds_allowance": "Gas vereist door transactie overschrijdt de vergoeding.", "generate_name": "Naam genereren", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index ad797bb24..0e9c53310 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -330,6 +330,7 @@ "freeze": "Zamróź", "frequently_asked_questions": "Często zadawane pytania", "frozen": "Zamrożone", + "frozen_balance": "Mrożona równowaga", "full_balance": "Pełne saldo", "gas_exceeds_allowance": "Gaz wymagany przez transakcję przekracza dodatek.", "generate_name": "Wygeneruj nazwę", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index d9df7e2ee..2653adde4 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -330,6 +330,7 @@ "freeze": "Congelar", "frequently_asked_questions": "Perguntas frequentes", "frozen": "Congeladas", + "frozen_balance": "Equilíbrio congelado", "full_balance": "Saldo total", "gas_exceeds_allowance": "O gás exigido pela transação excede o subsídio.", "generate_name": "Gerar nome", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index d9468f148..af7759316 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -330,6 +330,7 @@ "freeze": "Заморозить", "frequently_asked_questions": "Часто задаваемые вопросы", "frozen": "Заморожено", + "frozen_balance": "Замороженный баланс", "full_balance": "Весь баланс", "gas_exceeds_allowance": "Газ, требуемый в результате транзакции, превышает пособие.", "generate_name": "Создать имя", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 649d59d59..43ef057ad 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -330,6 +330,7 @@ "freeze": "ดักจับ", "frequently_asked_questions": "คำถามที่พบบ่อย", "frozen": "ถูกดักจับ", + "frozen_balance": "สมดุลแช่แข็ง", "full_balance": "ยอดคงเหลือทั้งหมด", "gas_exceeds_allowance": "ก๊าซที่ต้องการโดยการทำธุรกรรมเกินค่าเผื่อ", "generate_name": "สร้างชื่อ", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 33b4d23d6..1e845550d 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -330,6 +330,7 @@ "freeze": "I-freeze", "frequently_asked_questions": "Mga madalas itanong", "frozen": "Frozen", + "frozen_balance": "Frozen na balanse", "full_balance": "Buong Balanse", "gas_exceeds_allowance": "Ang gas na kinakailangan ng transaksyon ay lumampas sa allowance.", "generate_name": "Bumuo ng pangalan", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 7b27ded8a..48567f883 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -330,6 +330,7 @@ "freeze": "Dondur", "frequently_asked_questions": "Sıkça sorulan sorular", "frozen": "Dondurulmuş", + "frozen_balance": "Dondurulmuş denge", "full_balance": "Tüm bakiye", "gas_exceeds_allowance": "İşlemin gerektirdiği gaz ödeneği aşar.", "generate_name": "İsim Oluştur", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index cdf169a5a..5d26be28d 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -330,6 +330,7 @@ "freeze": "Заморозити", "frequently_asked_questions": "Часті запитання", "frozen": "Заморожено", + "frozen_balance": "Заморожений баланс", "full_balance": "Весь баланс", "gas_exceeds_allowance": "Газ, необхідний транзакціям, перевищує надбавку.", "generate_name": "Згенерувати назву", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 5213cbaeb..f9ca35bff 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -330,6 +330,7 @@ "freeze": "منجمد", "frequently_asked_questions": "اکثر پوچھے گئے سوالات", "frozen": "منجمد", + "frozen_balance": "منجمد توازن", "full_balance": "مکمل بیلنس", "gas_exceeds_allowance": "لین دین کے ذریعہ درکار گیس الاؤنس سے زیادہ ہے۔", "generate_name": "نام پیدا کریں۔", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 963c3002a..17c7cfc8d 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -329,6 +329,7 @@ "freeze": "Đóng băng", "frequently_asked_questions": "Các câu hỏi thường gặp", "frozen": "Đã đóng băng", + "frozen_balance": "Cân bằng đông lạnh", "full_balance": "Số dư đầy đủ", "gas_exceeds_allowance": "Gas theo yêu cầu của giao dịch vượt quá trợ cấp.", "generate_name": "Tạo tên", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index dd9c16347..8bd6b6a34 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -331,6 +331,7 @@ "freeze": "Tì pa", "frequently_asked_questions": "Àwọn ìbéèrè la máa ń béèrè", "frozen": "Ó l'a tì pa", + "frozen_balance": "Iwontunwonsi ti o tutu", "full_balance": "Ìyókù owó kíkún", "gas_exceeds_allowance": "Gaasi ti a beere nipasẹ idunadura ju lọ.", "generate_name": "Ṣẹda Orukọ", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 2d70c2325..483b10050 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -330,6 +330,7 @@ "freeze": "凍結", "frequently_asked_questions": "常见问题", "frozen": "凍結的", + "frozen_balance": "冷冻平衡", "full_balance": "全部余额", "gas_exceeds_allowance": "交易要求的气体超出了津贴。", "generate_name": "生成名称", From 4bba9f6ddb4a1c76f74b9bc5f5855e1469795c3a Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Sat, 28 Dec 2024 00:18:46 +0200 Subject: [PATCH 05/10] Solana enhancements (#1907) * fix: Confirm widget is still mounted * feat: Modify balance display to include full balance * fix: Modifying balance * chore: Feature cleanup * fix: Add frozen balance into consideration when taking available balance and add field to make full balance display only on bitcoin and litecoin wallets * fix: Adjust balance card to display correct available and unavailable balance, unavailable balance should only be displayed when there is one WIP * fix: Cleanup balance page and balance page view_model * chore: Revert formatting * fix: Remove full balance * fix: Remove full balance * fix: Remove full balance * chore: Rever formating [skip ci] * feat: Finalize display only available and unavailable balance * fix: Modify the way balance is displayed, activate frozen balance with label, remove unavailable/additional balance for bitcoin wallet type * fix: Issues coming from syncing with main * fix: Modify additional balance label * fix: Monero and Wownero balances display bug * fix: Resolve merge conflicts * feat: Activate CPFP for BTC, LTC and BCH, also fix issues with frozen balance display * - minor fix - remove unused functions * Fix conflicts * Temporarily remove misused function Ignore creating associated account for receiver (testing) * revert associated recipient account removal * Migrate eth and polygon nodes to new urls and https --------- Co-authored-by: Blazebrain Co-authored-by: tuxsudo --- assets/ethereum_server_list.yml | 4 ++- assets/polygon_node_list.yml | 4 ++- cw_solana/lib/solana_client.dart | 22 +++++++------ lib/entities/default_settings_migration.dart | 34 ++++++++++++++++++-- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/assets/ethereum_server_list.yml b/assets/ethereum_server_list.yml index 965638471..ed425c3c7 100644 --- a/assets/ethereum_server_list.yml +++ b/assets/ethereum_server_list.yml @@ -1,5 +1,7 @@ - - uri: ethereum.publicnode.com + uri: ethereum-rpc.publicnode.com + useSSL: true + isDefault: true - uri: eth.llamarpc.com - diff --git a/assets/polygon_node_list.yml b/assets/polygon_node_list.yml index 63878bc0c..3b2cdcdc3 100644 --- a/assets/polygon_node_list.yml +++ b/assets/polygon_node_list.yml @@ -1,7 +1,9 @@ - uri: polygon-rpc.com - - uri: polygon-bor.publicnode.com + uri: polygon-bor-rpc.publicnode.com + useSSL: true + isDefault: true - uri: polygon.llamarpc.com - diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 9447aad38..2207822bb 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -379,16 +379,18 @@ class SolanaWalletClient { required double solBalance, required double fee, }) async { - final rent = - await _client!.getMinimumBalanceForMintRentExemption(commitment: Commitment.confirmed); - - final rentInSol = (rent / lamportsPerSol).toDouble(); - - final remnant = solBalance - (inputAmount + fee); - - if (remnant > rentInSol) return true; - - return false; + return true; + // TODO: this is not doing what the name inclines + // final rent = + // await _client!.getMinimumBalanceForMintRentExemption(commitment: Commitment.confirmed); + // + // final rentInSol = (rent / lamportsPerSol).toDouble(); + // + // final remnant = solBalance - (inputAmount + fee); + // + // if (remnant > rentInSol) return true; + // + // return false; } Future _signNativeTokenTransaction({ diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 96638621a..63e70ce4d 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -35,8 +35,8 @@ const publicBitcoinTestnetElectrumUri = '$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort'; const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const havenDefaultNodeUri = 'nodes.havenprotocol.org:443'; -const ethereumDefaultNodeUri = 'ethereum.publicnode.com'; -const polygonDefaultNodeUri = 'polygon-bor.publicnode.com'; +const ethereumDefaultNodeUri = 'ethereum-rpc.publicnode.com'; +const polygonDefaultNodeUri = 'polygon-bor-rpc.publicnode.com'; const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002'; const nanoDefaultNodeUri = 'nano.nownodes.io'; const nanoDefaultPowNodeUri = 'rpc.nano.to'; @@ -360,6 +360,18 @@ Future defaultSettingsMigration( 'solana-rpc.publicnode.com:443', ], ); + _updateNode( + nodes: nodes, + currentUri: "ethereum.publicnode.com", + newUri: "ethereum-rpc.publicnode.com", + useSSL: true, + ); + _updateNode( + nodes: nodes, + currentUri: "polygon-bor.publicnode.com", + newUri: "polygon-bor-rpc.publicnode.com", + useSSL: true, + ); break; default: break; @@ -375,6 +387,24 @@ Future defaultSettingsMigration( await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version); } +void _updateNode({ + required Box nodes, + required String currentUri, + String? newUri, + bool? useSSL, +}) { + for (Node node in nodes.values) { + if (node.uriRaw == currentUri) { + if (newUri != null) { + node.uriRaw = newUri; + } + if (useSSL != null) { + node.useSSL = useSSL; + } + } + } +} + Future _backupHavenSeeds(Box havenSeedStore) async { final future = haven?.backupHavenSeeds(havenSeedStore); if (future != null) { From 2fe2f58cb44ed75008d0c104ef6e011dd20d1a0e Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Sat, 28 Dec 2024 07:15:59 +0200 Subject: [PATCH 06/10] fix available balance for monero/wownero --- cw_core/lib/monero_balance.dart | 2 +- cw_core/lib/wownero_balance.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cw_core/lib/monero_balance.dart b/cw_core/lib/monero_balance.dart index 42c00b97e..34f51faf9 100644 --- a/cw_core/lib/monero_balance.dart +++ b/cw_core/lib/monero_balance.dart @@ -4,7 +4,7 @@ import 'package:cw_core/monero_amount_format.dart'; class MoneroBalance extends Balance { MoneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0}) : formattedUnconfirmedBalance = moneroAmountToString(amount: fullBalance - unlockedBalance), - formattedUnlockedBalance = moneroAmountToString(amount: unlockedBalance - frozenBalance), + formattedUnlockedBalance = moneroAmountToString(amount: unlockedBalance), formattedFrozenBalance = moneroAmountToString(amount: frozenBalance), super(unlockedBalance, fullBalance); diff --git a/cw_core/lib/wownero_balance.dart b/cw_core/lib/wownero_balance.dart index b04560a79..916fa90bc 100644 --- a/cw_core/lib/wownero_balance.dart +++ b/cw_core/lib/wownero_balance.dart @@ -4,7 +4,7 @@ import 'package:cw_core/wownero_amount_format.dart'; class WowneroBalance extends Balance { WowneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0}) : formattedUnconfirmedBalance = wowneroAmountToString(amount: fullBalance - unlockedBalance), - formattedUnlockedBalance = wowneroAmountToString(amount: unlockedBalance - frozenBalance), + formattedUnlockedBalance = wowneroAmountToString(amount: unlockedBalance), formattedFrozenBalance = wowneroAmountToString(amount: frozenBalance), super(unlockedBalance, fullBalance); From 4cdee649d1802c2316cdac5e1e7c55baa7fdf8c4 Mon Sep 17 00:00:00 2001 From: Serhii Date: Sat, 28 Dec 2024 08:06:37 +0200 Subject: [PATCH 07/10] fix cakepay text encoding (#1902) * fix text encoding * fix initial encoding --- lib/cake_pay/cake_pay_api.dart | 3 ++- lib/cake_pay/cake_pay_card.dart | 27 +++++---------------------- lib/cake_pay/cake_pay_vendor.dart | 10 +--------- 3 files changed, 8 insertions(+), 32 deletions(-) diff --git a/lib/cake_pay/cake_pay_api.dart b/lib/cake_pay/cake_pay_api.dart index 68aba3f3e..5f1a350c0 100644 --- a/lib/cake_pay/cake_pay_api.dart +++ b/lib/cake_pay/cake_pay_api.dart @@ -230,6 +230,7 @@ class CakePayApi { var headers = { 'accept': 'application/json; charset=UTF-8', + 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Api-Key $apiKey', }; @@ -240,7 +241,7 @@ class CakePayApi { 'Failed to fetch vendors: statusCode - ${response.statusCode}, queryParams -$queryParams, response - ${response.body}'); } - final bodyJson = json.decode(response.body); + final bodyJson = json.decode(utf8.decode(response.bodyBytes)); if (bodyJson is List && bodyJson.isEmpty) { return []; diff --git a/lib/cake_pay/cake_pay_card.dart b/lib/cake_pay/cake_pay_card.dart index d3f07e409..82ba179e6 100644 --- a/lib/cake_pay/cake_pay_card.dart +++ b/lib/cake_pay/cake_pay_card.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:cake_wallet/entities/fiat_currency.dart'; class CakePayCard { @@ -38,17 +36,11 @@ class CakePayCard { }); factory CakePayCard.fromJson(Map json) { + final name = stripHtmlIfNeeded(json['name'] as String? ?? ''); - final decodedName = fixEncoding(name); - final description = stripHtmlIfNeeded(json['description'] as String? ?? ''); - final decodedDescription = fixEncoding(description); - final termsAndConditions = stripHtmlIfNeeded(json['terms_and_conditions'] as String? ?? ''); - final decodedTermsAndConditions = fixEncoding(termsAndConditions); - final howToUse = stripHtmlIfNeeded(json['how_to_use'] as String? ?? ''); - final decodedHowToUse = fixEncoding(howToUse); final fiatCurrency = FiatCurrency.deserialize(raw: json['currency_code'] as String? ?? ''); @@ -59,10 +51,10 @@ class CakePayCard { return CakePayCard( id: json['id'] as int? ?? 0, - name: decodedName, - description: decodedDescription, - termsAndConditions: decodedTermsAndConditions, - howToUse: decodedHowToUse, + name: name, + description: description, + termsAndConditions: termsAndConditions, + howToUse: howToUse, expiryAndValidity: json['expiry_and_validity'] as String?, cardImageUrl: json['card_image_url'] as String?, country: json['country'] as String?, @@ -79,13 +71,4 @@ class CakePayCard { static String stripHtmlIfNeeded(String text) { return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' '); } - - static String fixEncoding(String text) { - try { - final bytes = latin1.encode(text); - return utf8.decode(bytes, allowMalformed: true); - } catch (_) { - return text; - } - } } diff --git a/lib/cake_pay/cake_pay_vendor.dart b/lib/cake_pay/cake_pay_vendor.dart index 564896654..8ad305da0 100644 --- a/lib/cake_pay/cake_pay_vendor.dart +++ b/lib/cake_pay/cake_pay_vendor.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'cake_pay_card.dart'; class CakePayVendor { @@ -21,7 +19,6 @@ class CakePayVendor { factory CakePayVendor.fromJson(Map json, String country) { final name = stripHtmlIfNeeded(json['name'] as String); - final decodedName = fixEncoding(name); var cardsJson = json['cards'] as List?; CakePayCard? cardForVendor; @@ -36,7 +33,7 @@ class CakePayVendor { return CakePayVendor( id: json['id'] as int, - name: decodedName, + name: name, unavailable: json['unavailable'] as bool? ?? false, cakeWarnings: json['cake_warnings'] as String?, country: country, @@ -47,9 +44,4 @@ class CakePayVendor { static String stripHtmlIfNeeded(String text) { return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' '); } - - static String fixEncoding(String text) { - final bytes = latin1.encode(text); - return utf8.decode(bytes, allowMalformed: true); - } } From 214fc7113c4a865029a38942cb0dadca9947dbe7 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Sun, 29 Dec 2024 23:48:53 +0200 Subject: [PATCH 08/10] Fix electrum unspent coins error (#1912) * Refresh unspent coins before creating a transaction * disable seed verification in debug mode [skip ci] --- cw_bitcoin/lib/electrum_wallet.dart | 4 +++- .../seed/seed_verification/seed_verification_page.dart | 3 ++- lib/utils/feature_flag.dart | 4 +++- lib/view_model/wallet_seed_view_model.dart | 7 +++++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 3ab1505c9..9a7e45d05 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'dart:isolate'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/litecoin_wallet.dart'; @@ -991,6 +990,9 @@ abstract class ElectrumWalletBase @override Future createTransaction(Object credentials) async { try { + // start by updating unspent coins + await updateAllUnspents(); + final outputs = []; final transactionCredentials = credentials as BitcoinTransactionCredentials; final hasMultiDestination = transactionCredentials.outputs.length > 1; diff --git a/lib/src/screens/seed/seed_verification/seed_verification_page.dart b/lib/src/screens/seed/seed_verification/seed_verification_page.dart index 755cb2aae..ac03768ca 100644 --- a/lib/src/screens/seed/seed_verification/seed_verification_page.dart +++ b/lib/src/screens/seed/seed_verification/seed_verification_page.dart @@ -20,7 +20,8 @@ class SeedVerificationPage extends BasePage { builder: (context) { return Padding( padding: const EdgeInsets.all(16.0), - child: walletSeedViewModel.isVerificationComplete + child: walletSeedViewModel.isVerificationComplete || + walletSeedViewModel.verificationIndices.isEmpty ? SeedVerificationSuccessView( imageColor: titleColor(context), ) diff --git a/lib/utils/feature_flag.dart b/lib/utils/feature_flag.dart index efde5208d..593e0f216 100644 --- a/lib/utils/feature_flag.dart +++ b/lib/utils/feature_flag.dart @@ -1,7 +1,9 @@ +import 'package:flutter/foundation.dart'; + class FeatureFlag { static const bool isCakePayEnabled = false; static const bool isExolixEnabled = true; static const bool isInAppTorEnabled = false; static const bool isBackgroundSyncEnabled = false; - static const int verificationWordsCount = 2; + static const int verificationWordsCount = kDebugMode ? 0 : 2; } \ No newline at end of file diff --git a/lib/view_model/wallet_seed_view_model.dart b/lib/view_model/wallet_seed_view_model.dart index 5355c856d..53c76ed10 100644 --- a/lib/view_model/wallet_seed_view_model.dart +++ b/lib/view_model/wallet_seed_view_model.dart @@ -29,6 +29,7 @@ abstract class WalletSeedViewModelBase with Store { List get seedSplit => seed.split(RegExp(r'\s+')); int get columnCount => seedSplit.length <= 16 ? 2 : 3; + double get columnAspectRatio => seedSplit.length <= 16 ? 1.8 : 2.8; /// The indices of the seed to be verified. @@ -60,8 +61,10 @@ abstract class WalletSeedViewModelBase with Store { bool isVerificationComplete = false; void setupSeedVerification() { - generateRandomIndices(); - generateOptions(); + if (verificationWordCount != 0) { + generateRandomIndices(); + generateOptions(); + } } /// Generate the indices of the seeds to be verified. From 831a6d9f9a4449c2971b00e3297f9715e962cdd3 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Mon, 30 Dec 2024 11:46:09 -0500 Subject: [PATCH 09/10] Cw 872 nano enhancements (#1909) * fix headers on all api calls * fix duplicate nano nodes on fresh install, api key fixes * fix liveness indicators + false positive responses to queries --- cw_core/lib/node.dart | 52 ++++++++++++++++---- cw_nano/lib/nano_client.dart | 36 +++++++------- lib/entities/default_settings_migration.dart | 2 +- 3 files changed, 62 insertions(+), 28 deletions(-) diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index aa7d27254..7d0c2411f 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -99,8 +99,8 @@ class Node extends HiveObject with Keyable { case WalletType.polygon: case WalletType.solana: case WalletType.tron: - return Uri.parse( - "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}"); + return Uri.parse( + "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}"); case WalletType.none: throw Exception('Unexpected type ${type.toString()} for Node uri'); } @@ -152,6 +152,7 @@ class Node extends HiveObject with Keyable { return requestMoneroNode(); case WalletType.nano: case WalletType.banano: + return requestNanoNode(); case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: @@ -198,14 +199,16 @@ class Node extends HiveObject with Keyable { ); client.close(); - if (( - response.body.contains("400 Bad Request") // Some other generic error - || response.body.contains("plain HTTP request was sent to HTTPS port") // Cloudflare - || response.headers["location"] != null // Generic reverse proxy - || response.body.contains("301 Moved Permanently") // Poorly configured generic reverse proxy - ) && !(useSSL??false) - ) { - + if ((response.body.contains("400 Bad Request") // Some other generic error + || + response.body.contains("plain HTTP request was sent to HTTPS port") // Cloudflare + || + response.headers["location"] != null // Generic reverse proxy + || + response.body + .contains("301 Moved Permanently") // Poorly configured generic reverse proxy + ) && + !(useSSL ?? false)) { final oldUseSSL = useSSL; useSSL = true; try { @@ -271,6 +274,35 @@ class Node extends HiveObject with Keyable { } } + Future requestNanoNode() async { + try { + final response = await http.post( + uri, + headers: { + "Content-Type": "application/json", + "nano-app": "cake-wallet" + }, + body: jsonEncode( + { + "action": "account_balance", + "account": "nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579", + }, + ), + ); + final data = await jsonDecode(response.body); + if (response.statusCode != 200 || + data["error"] != null || + data["balance"] == null || + data["receivable"] == null) { + throw Exception( + "Error while trying to get balance! ${data["error"] != null ? data["error"] : ""}"); + } + return true; + } catch (_) { + return false; + } + } + Future requestEthereumServer() async { try { final response = await http.get( diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart index 8b62273da..b63c634ee 100644 --- a/cw_nano/lib/nano_client.dart +++ b/cw_nano/lib/nano_client.dart @@ -54,12 +54,12 @@ class NanoClient { } } - Map getHeaders() { + Map getHeaders(String host) { final headers = Map.from(CAKE_HEADERS); - if (_node!.uri.host == "rpc.nano.to") { + if (host == "rpc.nano.to") { headers["key"] = nano_secrets.nano2ApiKey; } - if (_node!.uri.host == "nano.nownodes.io") { + if (host == "nano.nownodes.io") { headers["api-key"] = nano_secrets.nanoNowNodesApiKey; } return headers; @@ -68,7 +68,7 @@ class NanoClient { Future getBalance(String address) async { final response = await http.post( _node!.uri, - headers: getHeaders(), + headers: getHeaders(_node!.uri.host), body: jsonEncode( { "action": "account_balance", @@ -95,7 +95,7 @@ class NanoClient { try { final response = await http.post( _node!.uri, - headers: getHeaders(), + headers: getHeaders(_node!.uri.host), body: jsonEncode( { "action": "account_info", @@ -116,7 +116,7 @@ class NanoClient { try { final response = await http.post( _node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(_node!.uri.host), body: jsonEncode( { "action": "block_info", @@ -183,7 +183,7 @@ class NanoClient { Future requestWork(String hash) async { final response = await http.post( _powNode!.uri, - headers: getHeaders(), + headers: getHeaders(_powNode!.uri.host), body: json.encode( { "action": "work_generate", @@ -226,7 +226,7 @@ class NanoClient { final processResponse = await http.post( _node!.uri, - headers: getHeaders(), + headers: getHeaders(_node!.uri.host), body: processBody, ); @@ -425,7 +425,7 @@ class NanoClient { }); final processResponse = await http.post( _node!.uri, - headers: getHeaders(), + headers: getHeaders(_node!.uri.host), body: processBody, ); @@ -441,7 +441,7 @@ class NanoClient { required String privateKey, }) async { final receivableResponse = await http.post(_node!.uri, - headers: getHeaders(), + headers: getHeaders(_node!.uri.host), body: jsonEncode({ "action": "receivable", "account": destinationAddress, @@ -493,7 +493,7 @@ class NanoClient { Future> fetchTransactions(String address) async { try { final response = await http.post(_node!.uri, - headers: getHeaders(), + headers: getHeaders(_node!.uri.host), body: jsonEncode({ "action": "account_history", "account": address, @@ -509,15 +509,16 @@ class NanoClient { .map((transaction) => NanoTransactionModel.fromJson(transaction)) .toList(); } catch (e) { - printV(e); - return []; + printV("error fetching transactions: $e"); + rethrow; } } Future> getN2Reps() async { + final uri = Uri.parse(N2_REPS_ENDPOINT); final response = await http.post( - Uri.parse(N2_REPS_ENDPOINT), - headers: CAKE_HEADERS, + uri, + headers: getHeaders(uri.host), body: jsonEncode({"action": "reps"}), ); try { @@ -531,9 +532,10 @@ class NanoClient { } Future getRepScore(String rep) async { + final uri = Uri.parse(N2_REPS_ENDPOINT); final response = await http.post( - Uri.parse(N2_REPS_ENDPOINT), - headers: CAKE_HEADERS, + uri, + headers: getHeaders(uri.host), body: jsonEncode({ "action": "rep_info", "account": rep, diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 63e70ce4d..25140f106 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -505,7 +505,7 @@ Future updateNanoNodeList({required Box nodes}) async { ]; // add new nodes: for (final node in nodeList) { - if (listOfNewEndpoints.contains(node.uriRaw)) { + if (listOfNewEndpoints.contains(node.uriRaw) && !nodes.values.contains(node)) { await nodes.add(node); } } From a2b3eeff15d5548d800854829e42b4b6f17140b1 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Mon, 30 Dec 2024 11:58:51 -0500 Subject: [PATCH 10/10] switch to sp node in bg service --- lib/entities/background_tasks.dart | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/entities/background_tasks.dart b/lib/entities/background_tasks.dart index 4a1420af5..cf8c09292 100644 --- a/lib/entities/background_tasks.dart +++ b/lib/entities/background_tasks.dart @@ -13,6 +13,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_core/node.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; @@ -222,26 +223,26 @@ Future onStart(ServiceInstance service) async { // get all bitcoin wallets and add them: final List bitcoinWallets = walletListViewModel.wallets.where((element) => element.type == WalletType.bitcoin).toList(); - bool spSupported = true; for (int i = 0; i < bitcoinWallets.length; i++) { try { - if (!spSupported) continue; final wallet = await walletLoadingService.load(bitcoinWallets[i].type, bitcoinWallets[i].name); - final node = settingsStore.getCurrentNode(WalletType.bitcoin); + var node = settingsStore.getCurrentNode(WalletType.bitcoin); await wallet.connectToNode(node: node); bool nodeSupportsSP = await (wallet as ElectrumWallet).getNodeSupportsSilentPayments(); if (!nodeSupportsSP) { - printV("Configured node does not support silent payments, skipping wallet"); - setWalletNotification( - flutterLocalNotificationsPlugin, - title: initialNotificationTitle, - content: spNodeNotificationMessage, - walletNum: syncingWallets.length + 1, - ); - spSupported = false; - continue; + // printV("Configured node does not support silent payments, skipping wallet"); + // setWalletNotification( + // flutterLocalNotificationsPlugin, + // title: initialNotificationTitle, + // content: spNodeNotificationMessage, + // walletNum: syncingWallets.length + 1, + // ); + // spSupported = false; + // continue; + node = Node(uri: "electrs.cakewallet.com:50001"); + await wallet.connectToNode(node: node); } await wallet.stopSync();