CW-596-Solana-Bug-Fixes (#1340)

* fix: Generic bug fixes across solana * fix: Remove back and forth parsing * fix: Add check to cut flow when estimated fee is higher than wallet balance * Update error message for fees exception * Remove logs --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Adegoke David committed Mar 29, 2024 at 19:55 UTC a9b8c03e55ba8e49e22d67496f4d4ee8c2a790a2
13 files changed +295 -116
cw_evm/lib/evm_chain_client.dart
+4 -4
@@ -82,7 +82,7 @@ abstract class EVMChainClient {
82 Future<PendingEVMChainTransaction> signTransaction({
83 required EthPrivateKey privateKey,
84 required String toAddress,
85 - required String amount,
85 + required BigInt amount,
86 required int gas,
87 required EVMChainTransactionPriority priority,
88 required CryptoCurrency currency,
@@ -103,7 +103,7 @@ abstract class EVMChainClient {
103 from: privateKey.address,
104 to: EthereumAddress.fromHex(toAddress),
105 maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
106 - amount: isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
106 + amount: isEVMCompatibleChain ? EtherAmount.inWei(amount) : EtherAmount.zero(),
107 data: data != null ? hexToBytes(data) : null,
108 );
109
@@ -124,7 +124,7 @@ abstract class EVMChainClient {
124 _sendTransaction = () async {
125 await erc20.transfer(
126 EthereumAddress.fromHex(toAddress),
127 - BigInt.parse(amount),
127 + amount,
128 credentials: privateKey,
129 transaction: transaction,
130 );
@@ -133,7 +133,7 @@ abstract class EVMChainClient {
133
134 return PendingEVMChainTransaction(
135 signedTransaction: signedTransaction,
136 - amount: amount,
136 + amount: amount.toString(),
137 fee: BigInt.from(gas) * (await price).getInWei,
138 sendTransaction: _sendTransaction,
139 exponent: exponent,
cw_evm/lib/evm_chain_exceptions.dart
+11
@@ -9,3 +9,14 @@ class EVMChainTransactionCreationException implements Exception {
9 @override
10 String toString() => exceptionMessage;
11 }
12 +
13 +
14 +class EVMChainTransactionFeesException implements Exception {
15 + final String exceptionMessage;
16 +
17 + EVMChainTransactionFeesException()
18 + : exceptionMessage = 'Current balance is less than the estimated fees for this transaction.';
19 +
20 + @override
21 + String toString() => exceptionMessage;
22 +}
cw_evm/lib/evm_chain_wallet.dart
+21 -12
@@ -234,7 +234,7 @@ abstract class EVMChainWalletBase
234 final CryptoCurrency transactionCurrency =
235 balance.keys.firstWhere((element) => element.title == _credentials.currency.title);
236
237 - final _erc20Balance = balance[transactionCurrency]!;
237 + final erc20Balance = balance[transactionCurrency]!;
238 BigInt totalAmount = BigInt.zero;
239 int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
240 num amountToEVMChainMultiplier = pow(10, exponent);
@@ -249,7 +249,7 @@ abstract class EVMChainWalletBase
249 outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
250 totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
251
252 - if (_erc20Balance.balance < totalAmount) {
252 + if (erc20Balance.balance < totalAmount) {
253 throw EVMChainTransactionCreationException(transactionCurrency);
254 }
255 } else {
@@ -258,18 +258,27 @@ abstract class EVMChainWalletBase
258 // then no need to subtract the fees from the amount if send all
259 final BigInt allAmount;
260 if (transactionCurrency is Erc20Token) {
261 - allAmount = _erc20Balance.balance;
261 + allAmount = erc20Balance.balance;
262 } else {
263 - allAmount = _erc20Balance.balance -
264 - BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
263 + final estimatedFee = BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
264 +
265 + if (estimatedFee > erc20Balance.balance) {
266 + throw EVMChainTransactionFeesException();
267 + }
268 +
269 + allAmount = erc20Balance.balance - estimatedFee;
270 + }
271 +
272 + if (output.sendAll) {
273 + totalAmount = allAmount;
274 + } else {
275 + final totalOriginalAmount =
276 + EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0);
277 +
278 + totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
279 }
266 - final totalOriginalAmount =
267 - EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0);
268 - totalAmount = output.sendAll
269 - ? allAmount
270 - : BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
280
272 - if (_erc20Balance.balance < totalAmount) {
281 + if (erc20Balance.balance < totalAmount) {
282 throw EVMChainTransactionCreationException(transactionCurrency);
283 }
284 }
@@ -279,7 +288,7 @@ abstract class EVMChainWalletBase
288 toAddress: _credentials.outputs.first.isParsedAddress
289 ? _credentials.outputs.first.extractedAddress!
290 : _credentials.outputs.first.address,
282 - amount: totalAmount.toString(),
291 + amount: totalAmount,
292 gas: _estimatedGas!,
293 priority: _credentials.priority!,
294 currency: transactionCurrency,
cw_solana/lib/solana_client.dart
+117 -59
@@ -96,16 +96,30 @@ class SolanaWalletClient {
96 return SolanaBalance(totalBalance);
97 }
98
99 - Future<double> getGasForMessage(String message) async {
99 + Future<double> getFeeForMessage(String message, Commitment commitment) async {
100 try {
101 - final gasPrice = await _client!.rpcClient.getFeeForMessage(message) ?? 0;
102 - final fee = gasPrice / lamportsPerSol;
101 + final feeForMessage =
102 + await _client!.rpcClient.getFeeForMessage(message, commitment: commitment);
103 + final fee = (feeForMessage ?? 0.0) / lamportsPerSol;
104 return fee;
105 } catch (_) {
105 - return 0;
106 + return 0.0;
107 }
108 }
109
110 + Future<double> getEstimatedFee(Ed25519HDKeyPair ownerKeypair) async {
111 + const commitment = Commitment.confirmed;
112 +
113 + final message =
114 + _getMessageForNativeTransaction(ownerKeypair, ownerKeypair.address, lamportsPerSol);
115 +
116 + final recentBlockhash = await _getRecentBlockhash(commitment);
117 +
118 + final estimatedFee =
119 + _getFeeFromCompiledMessage(message, ownerKeypair.publicKey, recentBlockhash, commitment);
120 + return estimatedFee;
121 + }
122 +
123 /// Load the Address's transactions into the account
124 Future<List<SolanaTransactionModel>> fetchTransactions(
125 Ed25519HDPublicKey publicKey, {
@@ -257,24 +271,15 @@ class SolanaWalletClient {
271 Future<PendingSolanaTransaction> signSolanaTransaction({
272 required String tokenTitle,
273 required int tokenDecimals,
260 - String? tokenMint,
274 required double inputAmount,
275 required String destinationAddress,
276 required Ed25519HDKeyPair ownerKeypair,
277 + required bool isSendAll,
278 + String? tokenMint,
279 List<String> references = const [],
280 }) async {
281 const commitment = Commitment.confirmed;
282
268 - final latestBlockhash =
269 - await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value;
270 -
271 - final recentBlockhash = RecentBlockhash(
272 - blockhash: latestBlockhash.blockhash,
273 - feeCalculator: const FeeCalculator(
274 - lamportsPerSignature: 500,
275 - ),
276 - );
277 -
283 if (tokenTitle == CryptoCurrency.sol.title) {
284 final pendingNativeTokenTransaction = await _signNativeTokenTransaction(
285 tokenTitle: tokenTitle,
@@ -282,8 +287,8 @@ class SolanaWalletClient {
287 inputAmount: inputAmount,
288 destinationAddress: destinationAddress,
289 ownerKeypair: ownerKeypair,
285 - recentBlockhash: recentBlockhash,
290 commitment: commitment,
291 + isSendAll: isSendAll,
292 );
293 return pendingNativeTokenTransaction;
294 } else {
@@ -294,49 +299,107 @@ class SolanaWalletClient {
299 inputAmount: inputAmount,
300 destinationAddress: destinationAddress,
301 ownerKeypair: ownerKeypair,
297 - recentBlockhash: recentBlockhash,
302 commitment: commitment,
303 );
304 return pendingSPLTokenTransaction;
305 }
306 }
307
308 + Future<RecentBlockhash> _getRecentBlockhash(Commitment commitment) async {
309 + final latestBlockhash =
310 + await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value;
311 +
312 + final recentBlockhash = RecentBlockhash(
313 + blockhash: latestBlockhash.blockhash,
314 + feeCalculator: const FeeCalculator(lamportsPerSignature: 500),
315 + );
316 +
317 + return recentBlockhash;
318 + }
319 +
320 + Message _getMessageForNativeTransaction(
321 + Ed25519HDKeyPair ownerKeypair,
322 + String destinationAddress,
323 + int lamports,
324 + ) {
325 + final instructions = [
326 + SystemInstruction.transfer(
327 + fundingAccount: ownerKeypair.publicKey,
328 + recipientAccount: Ed25519HDPublicKey.fromBase58(destinationAddress),
329 + lamports: lamports,
330 + ),
331 + ];
332 +
333 + final message = Message(instructions: instructions);
334 + return message;
335 + }
336 +
337 + Future<double> _getFeeFromCompiledMessage(
338 + Message message,
339 + Ed25519HDPublicKey feePayer,
340 + RecentBlockhash recentBlockhash,
341 + Commitment commitment,
342 + ) async {
343 + final compile = message.compile(
344 + recentBlockhash: recentBlockhash.blockhash,
345 + feePayer: feePayer,
346 + );
347 +
348 + final base64Message = base64Encode(compile.toByteArray().toList());
349 +
350 + final fee = await getFeeForMessage(base64Message, commitment);
351 +
352 + return fee;
353 + }
354 +
355 Future<PendingSolanaTransaction> _signNativeTokenTransaction({
356 required String tokenTitle,
357 required int tokenDecimals,
358 required double inputAmount,
359 required String destinationAddress,
360 required Ed25519HDKeyPair ownerKeypair,
310 - required RecentBlockhash recentBlockhash,
361 required Commitment commitment,
362 + required bool isSendAll,
363 }) async {
364 // Convert SOL to lamport
365 int lamports = (inputAmount * lamportsPerSol).toInt();
366
316 - final instructions = [
317 - SystemInstruction.transfer(
318 - fundingAccount: ownerKeypair.publicKey,
319 - recipientAccount: Ed25519HDPublicKey.fromBase58(destinationAddress),
320 - lamports: lamports,
321 - ),
322 - ];
367 + Message message = _getMessageForNativeTransaction(ownerKeypair, destinationAddress, lamports);
368
324 - final message = Message(instructions: instructions);
369 final signers = [ownerKeypair];
370
327 - final signedTx = await _signTransactionInternal(
328 - message: message,
329 - signers: signers,
330 - commitment: commitment,
331 - recentBlockhash: recentBlockhash,
332 - );
371 + RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment);
372
373 final fee = await _getFeeFromCompiledMessage(
374 message,
336 - recentBlockhash,
375 signers.first.publicKey,
376 + recentBlockhash,
377 + commitment,
378 );
379
380 + SignedTx signedTx;
381 + if (isSendAll) {
382 + final feeInLamports = (fee * lamportsPerSol).toInt();
383 + final updatedLamports = lamports - feeInLamports;
384 +
385 + final updatedMessage =
386 + _getMessageForNativeTransaction(ownerKeypair, destinationAddress, updatedLamports);
387 +
388 + signedTx = await _signTransactionInternal(
389 + message: updatedMessage,
390 + signers: signers,
391 + commitment: commitment,
392 + recentBlockhash: recentBlockhash,
393 + );
394 + } else {
395 + signedTx = await _signTransactionInternal(
396 + message: message,
397 + signers: signers,
398 + commitment: commitment,
399 + recentBlockhash: recentBlockhash,
400 + );
401 + }
402 +
403 sendTx() async => await sendTransaction(
404 signedTransaction: signedTx,
405 commitment: commitment,
@@ -360,7 +423,6 @@ class SolanaWalletClient {
423 required double inputAmount,
424 required String destinationAddress,
425 required Ed25519HDKeyPair ownerKeypair,
363 - required RecentBlockhash recentBlockhash,
426 required Commitment commitment,
427 }) async {
428 final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress);
@@ -408,8 +470,18 @@ class SolanaWalletClient {
470 );
471
472 final message = Message(instructions: [instruction]);
473 +
474 final signers = [ownerKeypair];
475
476 + RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment);
477 +
478 + final fee = await _getFeeFromCompiledMessage(
479 + message,
480 + signers.first.publicKey,
481 + recentBlockhash,
482 + commitment,
483 + );
484 +
485 final signedTx = await _signTransactionInternal(
486 message: message,
487 signers: signers,
@@ -417,12 +489,6 @@ class SolanaWalletClient {
489 recentBlockhash: recentBlockhash,
490 );
491
420 - final fee = await _getFeeFromCompiledMessage(
421 - message,
422 - recentBlockhash,
423 - signers.first.publicKey,
424 - );
425 -
492 sendTx() async => await sendTransaction(
493 signedTransaction: signedTx,
494 commitment: commitment,
@@ -438,19 +504,6 @@ class SolanaWalletClient {
504 return pendingTransaction;
505 }
506
441 - Future<double> _getFeeFromCompiledMessage(
442 - Message message, RecentBlockhash recentBlockhash, Ed25519HDPublicKey feePayer) async {
443 - final compile = message.compile(
444 - recentBlockhash: recentBlockhash.blockhash,
445 - feePayer: feePayer,
446 - );
447 -
448 - final base64Message = base64Encode(compile.toByteArray().toList());
449 -
450 - final fee = await getGasForMessage(base64Message);
451 - return fee;
452 - }
453 -
507 Future<SignedTx> _signTransactionInternal({
508 required Message message,
509 required List<Ed25519HDKeyPair> signers,
@@ -466,13 +519,18 @@ class SolanaWalletClient {
519 required SignedTx signedTransaction,
520 required Commitment commitment,
521 }) async {
469 - final signature = await _client!.rpcClient.sendTransaction(
470 - signedTransaction.encode(),
471 - preflightCommitment: commitment,
472 - );
522 + try {
523 + final signature = await _client!.rpcClient.sendTransaction(
524 + signedTransaction.encode(),
525 + preflightCommitment: commitment,
526 + );
527
474 - _client!.waitForSignatureStatus(signature, status: commitment);
528 + _client!.waitForSignatureStatus(signature, status: commitment);
529
476 - return signature;
530 + return signature;
531 + } catch (e) {
532 + print('Error while sending transaction: ${e.toString()}');
533 + throw Exception(e);
534 + }
535 }
536 }
cw_solana/lib/solana_wallet.dart
+42 -14
@@ -75,6 +75,9 @@ abstract class SolanaWalletBase
75
76 late SolanaWalletClient _client;
77
78 + @observable
79 + double? estimatedFee;
80 +
81 Timer? _transactionsUpdateTimer;
82
83 late final Box<SPLToken> splTokensBox;
@@ -171,6 +174,14 @@ abstract class SolanaWalletBase
174 }
175 }
176
177 + Future<void> _getEstimatedFees() async {
178 + try {
179 + estimatedFee = await _client.getEstimatedFee(_walletKeyPair!);
180 + } catch (e) {
181 + estimatedFee = 0.0;
182 + }
183 + }
184 +
185 @override
186 Future<PendingTransaction> createTransaction(Object credentials) async {
187 final solCredentials = credentials as SolanaTransactionCredentials;
@@ -188,6 +199,8 @@ abstract class SolanaWalletBase
199
200 double totalAmount = 0.0;
201
202 + bool isSendAll = false;
203 +
204 if (hasMultiDestination) {
205 if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
206 throw SolanaTransactionWrongBalanceException(transactionCurrency);
@@ -204,9 +217,15 @@ abstract class SolanaWalletBase
217 } else {
218 final output = outputs.first;
219
207 - final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
220 + isSendAll = output.sendAll;
221 +
222 + if (isSendAll) {
223 + totalAmount = walletBalanceForCurrency;
224 + } else {
225 + final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
226
209 - totalAmount = output.sendAll ? walletBalanceForCurrency : totalOriginalAmount;
227 + totalAmount = totalOriginalAmount;
228 + }
229
230 if (walletBalanceForCurrency < totalAmount) {
231 throw SolanaTransactionWrongBalanceException(transactionCurrency);
@@ -228,6 +247,7 @@ abstract class SolanaWalletBase
247 destinationAddress: solCredentials.outputs.first.isParsedAddress
248 ? solCredentials.outputs.first.extractedAddress!
249 : solCredentials.outputs.first.address,
250 + isSendAll: isSendAll,
251 );
252
253 return pendingSolanaTransaction;
@@ -269,7 +289,10 @@ abstract class SolanaWalletBase
289 Future<void> _updateSPLTokenTransactions() async {
290 List<SolanaTransactionModel> splTokenTransactions = [];
291
272 - for (var token in balance.keys) {
292 + // Make a copy of keys to avoid concurrent modification
293 + var tokenKeys = List<CryptoCurrency>.from(balance.keys);
294 +
295 + for (var token in tokenKeys) {
296 if (token is SPLToken) {
297 final tokenTxs = await _client.getSPLTokenTransfers(
298 token.mintAddress,
@@ -326,6 +349,7 @@ abstract class SolanaWalletBase
349 _updateBalance(),
350 _updateNativeSOLTransactions(),
351 _updateSPLTokenTransactions(),
352 + _getEstimatedFees(),
353 ]);
354
355 syncStatus = SyncedSyncStatus();
@@ -433,18 +457,22 @@ abstract class SolanaWalletBase
457 final mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress);
458
459 // Fetch token's metadata account
436 - final token = await solanaClient!.rpcClient.getMetadata(mint: mintPublicKey);
460 + try {
461 + final token = await solanaClient!.rpcClient.getMetadata(mint: mintPublicKey);
462 +
463 + if (token == null) {
464 + return null;
465 + }
466
438 - if (token == null) {
467 + return SPLToken.fromMetadata(
468 + name: token.name,
469 + mint: token.mint,
470 + symbol: token.symbol,
471 + mintAddress: mintAddress,
472 + );
473 + } catch (e) {
474 return null;
475 }
441 -
442 - return SPLToken.fromMetadata(
443 - name: token.name,
444 - mint: token.mint,
445 - symbol: token.symbol,
446 - mintAddress: mintAddress,
447 - );
476 }
477
478 @override
@@ -475,9 +503,9 @@ abstract class SolanaWalletBase
503 }
504
505 _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 20), (_) {
478 - _updateSPLTokenTransactions();
479 - _updateNativeSOLTransactions();
506 _updateBalance();
507 + _updateNativeSOLTransactions();
508 + _updateSPLTokenTransactions();
509 });
510 }
511
cw_solana/lib/solana_wallet_service.dart
+26 -9
@@ -32,6 +32,7 @@ class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
32
33 await wallet.init();
34 wallet.addInitialTokens();
35 + await wallet.save();
36 return wallet;
37 }
38
@@ -46,16 +47,31 @@ class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
47 Future<SolanaWallet> openWallet(String name, String password) async {
48 final walletInfo =
49 walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
49 - final wallet = await SolanaWalletBase.open(
50 - name: name,
51 - password: password,
52 - walletInfo: walletInfo,
53 - );
50
55 - await wallet.init();
56 - await wallet.save();
57 -
58 - return wallet;
51 + try {
52 + final wallet = await SolanaWalletBase.open(
53 + name: name,
54 + password: password,
55 + walletInfo: walletInfo,
56 + );
57 +
58 + await wallet.init();
59 + await wallet.save();
60 + saveBackup(name);
61 + return wallet;
62 + } catch (_) {
63 + await restoreWalletFilesFromBackup(name);
64 +
65 + final wallet = await SolanaWalletBase.open(
66 + name: name,
67 + password: password,
68 + walletInfo: walletInfo,
69 + );
70 +
71 + await wallet.init();
72 + await wallet.save();
73 + return wallet;
74 + }
75 }
76
77 @override
@@ -110,6 +126,7 @@ class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
126 password: password, name: currentName, walletInfo: currentWalletInfo);
127
128 await currentWallet.renameWalletFiles(newName);
129 + await saveBackup(newName);
130
131 final newWalletInfo = currentWalletInfo;
132 newWalletInfo.id = WalletBase.idFor(newName, getType());
lib/solana/cw_solana.dart
+21 -2
@@ -74,8 +74,22 @@ class CWSolana extends Solana {
74 }
75
76 @override
77 - Future<void> addSPLToken(WalletBase wallet, CryptoCurrency token) async =>
78 - await (wallet as SolanaWallet).addSPLToken(token as SPLToken);
77 + Future<void> addSPLToken(
78 + WalletBase wallet,
79 + CryptoCurrency token,
80 + String contractAddress,
81 + ) async {
82 + final splToken = SPLToken(
83 + name: token.name,
84 + symbol: token.title,
85 + mintAddress: contractAddress,
86 + decimal: token.decimals,
87 + mint: token.name.toUpperCase(),
88 + enabled: token.enabled,
89 + );
90 +
91 + await (wallet as SolanaWallet).addSPLToken(splToken);
92 + }
93
94 @override
95 Future<void> deleteSPLToken(WalletBase wallet, CryptoCurrency token) async =>
@@ -115,4 +129,9 @@ class CWSolana extends Solana {
129
130 return null;
131 }
132 +
133 + @override
134 + double? getEstimateFees(WalletBase wallet) {
135 + return (wallet as SolanaWallet).estimatedFee;
136 + }
137 }
lib/src/screens/dashboard/edit_token_page.dart
+7 -5
@@ -195,12 +195,14 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
195 onPressed: () async {
196 if (_formKey.currentState!.validate() &&
197 (!_showDisclaimer || _disclaimerChecked)) {
198 - await widget.homeSettingsViewModel.addToken(Erc20Token(
199 - name: _tokenNameController.text,
200 - symbol: _tokenSymbolController.text,
198 + await widget.homeSettingsViewModel.addToken(
199 + token: CryptoCurrency(
200 + name: _tokenNameController.text,
201 + title: _tokenSymbolController.text.toUpperCase(),
202 + decimals: int.parse(_tokenDecimalController.text),
203 + ),
204 contractAddress: _contractAddressController.text,
202 - decimal: int.parse(_tokenDecimalController.text),
203 - ));
205 + );
206 if (context.mounted) {
207 Navigator.pop(context);
208 }
lib/src/screens/send/widgets/send_card.dart
+4 -3
@@ -323,8 +323,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
323 ? sendViewModel.allAmountValidator
324 : sendViewModel.amountValidator,
325 ),
326 - if (!sendViewModel.isBatchSending &&
327 - sendViewModel.shouldDisplaySendALL)
326 + if (!sendViewModel.isBatchSending)
327 Positioned(
328 top: 2,
329 right: 0,
@@ -456,7 +455,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
455 if (sendViewModel.hasFees)
456 Observer(
457 builder: (_) => GestureDetector(
459 - onTap: () => _setTransactionPriority(context),
458 + onTap: sendViewModel.hasFeesPriority
459 + ? () => _setTransactionPriority(context)
460 + : () {},
461 child: Container(
462 padding: EdgeInsets.only(top: 24),
463 child: Row(
lib/view_model/dashboard/home_settings_view_model.dart
+26 -5
@@ -44,17 +44,37 @@ abstract class HomeSettingsViewModelBase with Store {
44 @action
45 void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value;
46
47 - Future<void> addToken(CryptoCurrency token) async {
47 + Future<void> addToken({
48 + required String contractAddress,
49 + required CryptoCurrency token,
50 + }) async {
51 if (_balanceViewModel.wallet.type == WalletType.ethereum) {
49 - await ethereum!.addErc20Token(_balanceViewModel.wallet, token);
52 + final erc20token = Erc20Token(
53 + name: token.name,
54 + symbol: token.title,
55 + decimal: token.decimals,
56 + contractAddress: contractAddress,
57 + );
58 +
59 + await ethereum!.addErc20Token(_balanceViewModel.wallet, erc20token);
60 }
61
62 if (_balanceViewModel.wallet.type == WalletType.polygon) {
53 - await polygon!.addErc20Token(_balanceViewModel.wallet, token);
63 + final polygonToken = Erc20Token(
64 + name: token.name,
65 + symbol: token.title,
66 + decimal: token.decimals,
67 + contractAddress: contractAddress,
68 + );
69 + await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken);
70 }
71
72 if (_balanceViewModel.wallet.type == WalletType.solana) {
57 - await solana!.addSPLToken(_balanceViewModel.wallet, token);
73 + await solana!.addSPLToken(
74 + _balanceViewModel.wallet,
75 + token,
76 + contractAddress,
77 + );
78 }
79
80 _updateTokensList();
@@ -117,7 +137,8 @@ abstract class HomeSettingsViewModelBase with Store {
137 }
138
139 if (_balanceViewModel.wallet.type == WalletType.solana) {
120 - solana!.addSPLToken(_balanceViewModel.wallet, token);
140 + final address = solana!.getTokenAddress(token);
141 + solana!.addSPLToken(_balanceViewModel.wallet, token, address);
142 }
143
144 _refreshTokensList();
lib/view_model/send/output.dart
+5
@@ -6,6 +6,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
6 import 'package:cake_wallet/haven/haven.dart';
7 import 'package:cake_wallet/polygon/polygon.dart';
8 import 'package:cake_wallet/reactions/wallet_connect.dart';
9 +import 'package:cake_wallet/solana/solana.dart';
10 import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
11 import 'package:cw_core/crypto_currency.dart';
12 import 'package:flutter/material.dart';
@@ -116,6 +117,10 @@ abstract class OutputBase with Store {
117 @computed
118 double get estimatedFee {
119 try {
120 + if (_wallet.type == WalletType.solana) {
121 + return solana!.getEstimateFees(_wallet) ?? 0.0;
122 + }
123 +
124 final fee = _wallet.calculateEstimatedFee(
125 _settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
126
lib/view_model/send/send_view_model.dart
+5 -2
@@ -106,8 +106,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
106 @computed
107 bool get isBatchSending => outputs.length > 1;
108
109 - bool get shouldDisplaySendALL => walletType != WalletType.solana;
110 -
109 @computed
110 String get pendingTransactionFiatAmount {
111 if (pendingTransaction == null) {
@@ -208,6 +206,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
206 @computed
207 bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano;
208
209 + @computed
210 + bool get hasFeesPriority =>
211 + wallet.type != WalletType.nano &&
212 + wallet.type != WalletType.banano &&
213 + wallet.type != WalletType.solana;
214 @observable
215 CryptoCurrency selectedCryptoCurrency;
216
tool/configure.dart
+6 -1
@@ -948,7 +948,11 @@ abstract class Solana {
948 required CryptoCurrency currency,
949 });
950 List<CryptoCurrency> getSPLTokenCurrencies(WalletBase wallet);
951 - Future<void> addSPLToken(WalletBase wallet, CryptoCurrency token);
951 + Future<void> addSPLToken(
952 + WalletBase wallet,
953 + CryptoCurrency token,
954 + String contractAddress,
955 + );
956 Future<void> deleteSPLToken(WalletBase wallet, CryptoCurrency token);
957 Future<CryptoCurrency?> getSPLToken(WalletBase wallet, String contractAddress);
958
@@ -956,6 +960,7 @@ abstract class Solana {
960 double getTransactionAmountRaw(TransactionInfo transactionInfo);
961 String getTokenAddress(CryptoCurrency asset);
962 List<int>? getValidationLength(CryptoCurrency type);
963 + double? getEstimateFees(WalletBase wallet);
964 }
965
966 """;