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 = [