From ed12ff6afeae9085aa343a89c5fb44520eea032d Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 27 Dec 2024 00:42:36 +0200 Subject: [PATCH] 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 = [