change Solana node (#1903)

* change Solana node * Fix reaching limit for fetching transactions

Omar Hatem committed Dec 27, 2024 at 00:42 UTC ed12ff6afeae9085aa343a89c5fb44520eea032d
13 files changed +90 -48
.github/workflows/automated_integration_test.yml
+2
@@ -173,6 +173,7 @@ jobs:
173 echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
174 echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
175 echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
176 + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart
177 echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
178 echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
179 echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
@@ -185,6 +186,7 @@ jobs:
186 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
187 echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
188 echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
189 + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
190 echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
191 echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
192 echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart
.github/workflows/pr_test_build_android.yml
+2
@@ -184,6 +184,7 @@ jobs:
184 echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
185 echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
186 echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
187 + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart
188 echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
189 echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
190 echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
@@ -197,6 +198,7 @@ jobs:
198 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
199 echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
200 echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
201 + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
202 echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
203 echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
204 echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart
.github/workflows/pr_test_build_linux.yml
+2
@@ -156,6 +156,7 @@ jobs:
156 echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
157 echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
158 echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
159 + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart
160 echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
161 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
162 echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
@@ -167,6 +168,7 @@ jobs:
168 echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
169 echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
170 echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
171 + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
172 echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
173 echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
174 echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart
assets/solana_node_list.yml
+3
@@ -7,4 +7,7 @@
7 -
8 uri: solana-rpc.publicnode.com:443
9 useSSL: true
10 +-
11 + uri: solana-mainnet.core.chainstack.com
12 + useSSL: true
13 is_default: true
\ No newline at end of file
cw_core/lib/exceptions.dart
+5 -1
@@ -47,7 +47,11 @@ class TransactionInputNotSupported implements Exception {}
47
48 class SignNativeTokenTransactionRentException implements Exception {}
49
50 -class CreateAssociatedTokenAccountException implements Exception {}
50 +class CreateAssociatedTokenAccountException implements Exception {
51 + final String errorMessage;
52 +
53 + CreateAssociatedTokenAccountException(this.errorMessage);
54 +}
55
56 class SignSPLTokenTransactionRentException implements Exception {}
57
cw_solana/lib/solana_client.dart
+48 -31
@@ -21,22 +21,23 @@ class SolanaWalletClient {
21
22 bool connect(Node node) {
23 try {
24 - Uri? rpcUri;
25 - String webSocketUrl;
26 - bool isModifiedNodeUri = false;
24 + Uri rpcUri = node.uri;
25 + String webSocketUrl = 'wss://${node.uriRaw}';
26
27 if (node.uriRaw == 'rpc.ankr.com') {
29 - isModifiedNodeUri = true;
28 String ankrApiKey = secrets.ankrApiKey;
29
30 rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey');
31 webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey';
34 - } else {
35 - webSocketUrl = 'wss://${node.uriRaw}';
32 + } else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') {
33 + String chainStackApiKey = secrets.chainStackApiKey;
34 +
35 + rpcUri = Uri.https(node.uriRaw, '/$chainStackApiKey');
36 + webSocketUrl = 'wss://${node.uriRaw}/$chainStackApiKey';
37 }
38
39 _client = SolanaClient(
39 - rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri,
40 + rpcUrl: rpcUri,
41 websocketUrl: Uri.parse(webSocketUrl),
42 timeout: const Duration(minutes: 2),
43 );
@@ -115,10 +116,14 @@ class SolanaWalletClient {
116 final message =
117 _getMessageForNativeTransaction(ownerKeypair, ownerKeypair.address, lamportsPerSol);
118
118 - final recentBlockhash = await _getRecentBlockhash(commitment);
119 + final latestBlockhash = await _getLatestBlockhash(commitment);
120
120 - final estimatedFee =
121 - _getFeeFromCompiledMessage(message, ownerKeypair.publicKey, recentBlockhash, commitment);
121 + final estimatedFee = _getFeeFromCompiledMessage(
122 + message,
123 + ownerKeypair.publicKey,
124 + latestBlockhash,
125 + commitment,
126 + );
127 return estimatedFee;
128 }
129
@@ -131,13 +136,25 @@ class SolanaWalletClient {
136 List<SolanaTransactionModel> transactions = [];
137
138 try {
134 - final response = await _client!.rpcClient.getTransactionsList(
135 - publicKey,
139 + final signatures = await _client!.rpcClient.getSignaturesForAddress(
140 + publicKey.toBase58(),
141 commitment: Commitment.confirmed,
137 - limit: 1000,
142 );
143
140 - for (final tx in response) {
144 + final List<TransactionDetails> transactionDetails = [];
145 + for (int i = 0; i < signatures.length; i += 20) {
146 + final response = await _client!.rpcClient.getMultipleTransactions(
147 + signatures.sublist(i, math.min(i + 20, signatures.length)),
148 + commitment: Commitment.confirmed,
149 + encoding: Encoding.jsonParsed,
150 + );
151 + transactionDetails.addAll(response);
152 +
153 + // to avoid reaching the node RPS limit
154 + await Future.delayed(Duration(milliseconds: 500));
155 + }
156 +
157 + for (final tx in transactionDetails) {
158 if (tx.transaction is ParsedTransaction) {
159 final parsedTx = (tx.transaction as ParsedTransaction);
160 final message = parsedTx.message;
@@ -310,16 +327,16 @@ class SolanaWalletClient {
327 }
328 }
329
313 - Future<RecentBlockhash> _getRecentBlockhash(Commitment commitment) async {
314 - final latestBlockhash =
330 + Future<LatestBlockhash> _getLatestBlockhash(Commitment commitment) async {
331 + final latestBlockHashResult =
332 await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value;
333
317 - final recentBlockhash = RecentBlockhash(
318 - blockhash: latestBlockhash.blockhash,
319 - feeCalculator: const FeeCalculator(lamportsPerSignature: 500),
334 + final latestBlockhash = LatestBlockhash(
335 + blockhash: latestBlockHashResult.blockhash,
336 + lastValidBlockHeight: latestBlockHashResult.lastValidBlockHeight,
337 );
338
322 - return recentBlockhash;
339 + return latestBlockhash;
340 }
341
342 Message _getMessageForNativeTransaction(
@@ -342,11 +359,11 @@ class SolanaWalletClient {
359 Future<double> _getFeeFromCompiledMessage(
360 Message message,
361 Ed25519HDPublicKey feePayer,
345 - RecentBlockhash recentBlockhash,
362 + LatestBlockhash latestBlockhash,
363 Commitment commitment,
364 ) async {
365 final compile = message.compile(
349 - recentBlockhash: recentBlockhash.blockhash,
366 + recentBlockhash: latestBlockhash.blockhash,
367 feePayer: feePayer,
368 );
369
@@ -391,12 +408,12 @@ class SolanaWalletClient {
408
409 final signers = [ownerKeypair];
410
394 - RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment);
411 + LatestBlockhash latestBlockhash = await _getLatestBlockhash(commitment);
412
413 final fee = await _getFeeFromCompiledMessage(
414 message,
415 signers.first.publicKey,
399 - recentBlockhash,
416 + latestBlockhash,
417 commitment,
418 );
419
@@ -422,14 +439,14 @@ class SolanaWalletClient {
439 message: updatedMessage,
440 signers: signers,
441 commitment: commitment,
425 - recentBlockhash: recentBlockhash,
442 + latestBlockhash: latestBlockhash,
443 );
444 } else {
445 signedTx = await _signTransactionInternal(
446 message: message,
447 signers: signers,
448 commitment: commitment,
432 - recentBlockhash: recentBlockhash,
449 + latestBlockhash: latestBlockhash,
450 );
451 }
452
@@ -507,12 +524,12 @@ class SolanaWalletClient {
524
525 final signers = [ownerKeypair];
526
510 - RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment);
527 + LatestBlockhash latestBlockhash = await _getLatestBlockhash(commitment);
528
529 final fee = await _getFeeFromCompiledMessage(
530 message,
531 signers.first.publicKey,
515 - recentBlockhash,
532 + latestBlockhash,
533 commitment,
534 );
535
@@ -530,7 +547,7 @@ class SolanaWalletClient {
547 message: message,
548 signers: signers,
549 commitment: commitment,
533 - recentBlockhash: recentBlockhash,
550 + latestBlockhash: latestBlockhash,
551 );
552
553 sendTx() async => await sendTransaction(
@@ -552,9 +569,9 @@ class SolanaWalletClient {
569 required Message message,
570 required List<Ed25519HDKeyPair> signers,
571 required Commitment commitment,
555 - required RecentBlockhash recentBlockhash,
572 + required LatestBlockhash latestBlockhash,
573 }) async {
557 - final signedTx = await signTransaction(recentBlockhash, message, signers);
574 + final signedTx = await signTransaction(latestBlockhash, message, signers);
575
576 return signedTx;
577 }
cw_solana/lib/solana_exceptions.dart
+1 -3
@@ -25,9 +25,7 @@ class SolanaSignNativeTokenTransactionRentException
25 extends SignNativeTokenTransactionRentException {}
26
27 class SolanaCreateAssociatedTokenAccountException extends CreateAssociatedTokenAccountException {
28 - SolanaCreateAssociatedTokenAccountException(this.exceptionMessage);
29 -
30 - final String exceptionMessage;
28 + SolanaCreateAssociatedTokenAccountException(super.errorMessage);
29 }
30
31 class SolanaSignSPLTokenTransactionRentException extends SignSPLTokenTransactionRentException {}
cw_solana/pubspec.yaml
+1 -1
@@ -11,7 +11,7 @@ environment:
11 dependencies:
12 flutter:
13 sdk: flutter
14 - solana: ^0.30.4
14 + solana: ^0.31.0+1
15 cw_core:
16 path: ../cw_core
17 http: ^1.1.0
lib/core/wallet_connect/web3wallet_service.dart
+8 -9
@@ -140,25 +140,24 @@ abstract class Web3WalletServiceBase with Store {
140 for (final cId in SolanaChainId.values) {
141 final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type);
142
143 - Uri? rpcUri;
144 - String webSocketUrl;
145 - bool isModifiedNodeUri = false;
143 + Uri rpcUri = node.uri;
144 + String webSocketUrl = 'wss://${node.uriRaw}';
145
146 if (node.uriRaw == 'rpc.ankr.com') {
148 - isModifiedNodeUri = true;
149 -
150 - //A better way to handle this instead of adding this to the general secrets?
147 String ankrApiKey = secrets.ankrApiKey;
148
149 rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey');
150 webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey';
155 - } else {
156 - webSocketUrl = 'wss://${node.uriRaw}';
151 + } else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') {
152 + String chainStackApiKey = secrets.chainStackApiKey;
153 +
154 + rpcUri = Uri.https(node.uriRaw, '/$chainStackApiKey');
155 + webSocketUrl = 'wss://${node.uriRaw}/$chainStackApiKey';
156 }
157
158 SolanaChainServiceImpl(
159 reference: cId,
161 - rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri,
160 + rpcUrl: rpcUri,
161 webSocketUrl: webSocketUrl,
162 wcKeyService: walletKeyService,
163 bottomSheetService: _bottomSheetHandler,
lib/entities/default_settings_migration.dart
+14 -1
@@ -40,7 +40,7 @@ const polygonDefaultNodeUri = 'polygon-bor.publicnode.com';
40 const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
41 const nanoDefaultNodeUri = 'nano.nownodes.io';
42 const nanoDefaultPowNodeUri = 'rpc.nano.to';
43 -const solanaDefaultNodeUri = 'solana-rpc.publicnode.com:443';
43 +const solanaDefaultNodeUri = 'solana-mainnet.core.chainstack.com';
44 const tronDefaultNodeUri = 'api.trongrid.io';
45 const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
46 const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
@@ -347,6 +347,19 @@ Future<void> defaultSettingsMigration(
347 type: WalletType.litecoin,
348 useSSL: true,
349 );
350 + _changeDefaultNode(
351 + nodes: nodes,
352 + sharedPreferences: sharedPreferences,
353 + type: WalletType.solana,
354 + newDefaultUri: solanaDefaultNodeUri,
355 + currentNodePreferenceKey: PreferencesKey.currentSolanaNodeIdKey,
356 + useSSL: true,
357 + oldUri: [
358 + 'rpc.ankr.com',
359 + 'api.mainnet-beta.solana.com:443',
360 + 'solana-rpc.publicnode.com:443',
361 + ],
362 + );
363 break;
364 default:
365 break;
lib/view_model/send/send_view_model.dart
+1 -1
@@ -680,7 +680,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
680 }
681
682 if (error is CreateAssociatedTokenAccountException) {
683 - return S.current.solana_create_associated_token_account_exception;
683 + return "${S.current.solana_create_associated_token_account_exception}\n\n${error.errorMessage}";
684 }
685
686 if (error is SignSPLTokenTransactionRentException) {
pubspec_base.yaml
+1 -1
@@ -106,7 +106,7 @@ dependencies:
106 flutter_svg: ^2.0.9
107 polyseed: ^0.0.6
108 nostr_tools: ^1.0.9
109 - solana: ^0.30.1
109 + solana: ^0.31.0+1
110 ledger_flutter_plus: ^1.4.1
111 hashlib: ^1.19.2
112
tool/utils/secret_key.dart
+2
@@ -38,6 +38,7 @@ class SecretKey {
38 SecretKey('walletConnectProjectId', () => ''),
39 SecretKey('moralisApiKey', () => ''),
40 SecretKey('ankrApiKey', () => ''),
41 + SecretKey('chainStackApiKey', () => ''),
42 SecretKey('quantexExchangeMarkup', () => ''),
43 SecretKey('seeds', () => ''),
44 SecretKey('testCakePayApiKey', () => ''),
@@ -86,6 +87,7 @@ class SecretKey {
87
88 static final solanaSecrets = [
89 SecretKey('ankrApiKey', () => ''),
90 + SecretKey('chainStackApiKey', () => ''),
91 ];
92
93 static final nanoSecrets = [