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
""";