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>
This commit is contained in:
Adegoke David 2024-03-29 19:55:29 +01:00 committed by GitHub
parent 698c222291
commit a9b8c03e55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 297 additions and 118 deletions

View file

@ -82,7 +82,7 @@ abstract class EVMChainClient {
Future<PendingEVMChainTransaction> signTransaction({ Future<PendingEVMChainTransaction> signTransaction({
required EthPrivateKey privateKey, required EthPrivateKey privateKey,
required String toAddress, required String toAddress,
required String amount, required BigInt amount,
required int gas, required int gas,
required EVMChainTransactionPriority priority, required EVMChainTransactionPriority priority,
required CryptoCurrency currency, required CryptoCurrency currency,
@ -103,7 +103,7 @@ abstract class EVMChainClient {
from: privateKey.address, from: privateKey.address,
to: EthereumAddress.fromHex(toAddress), to: EthereumAddress.fromHex(toAddress),
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip), maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
amount: isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), amount: isEVMCompatibleChain ? EtherAmount.inWei(amount) : EtherAmount.zero(),
data: data != null ? hexToBytes(data) : null, data: data != null ? hexToBytes(data) : null,
); );
@ -124,7 +124,7 @@ abstract class EVMChainClient {
_sendTransaction = () async { _sendTransaction = () async {
await erc20.transfer( await erc20.transfer(
EthereumAddress.fromHex(toAddress), EthereumAddress.fromHex(toAddress),
BigInt.parse(amount), amount,
credentials: privateKey, credentials: privateKey,
transaction: transaction, transaction: transaction,
); );
@ -133,7 +133,7 @@ abstract class EVMChainClient {
return PendingEVMChainTransaction( return PendingEVMChainTransaction(
signedTransaction: signedTransaction, signedTransaction: signedTransaction,
amount: amount, amount: amount.toString(),
fee: BigInt.from(gas) * (await price).getInWei, fee: BigInt.from(gas) * (await price).getInWei,
sendTransaction: _sendTransaction, sendTransaction: _sendTransaction,
exponent: exponent, exponent: exponent,

View file

@ -9,3 +9,14 @@ class EVMChainTransactionCreationException implements Exception {
@override @override
String toString() => exceptionMessage; String toString() => exceptionMessage;
} }
class EVMChainTransactionFeesException implements Exception {
final String exceptionMessage;
EVMChainTransactionFeesException()
: exceptionMessage = 'Current balance is less than the estimated fees for this transaction.';
@override
String toString() => exceptionMessage;
}

View file

@ -234,7 +234,7 @@ abstract class EVMChainWalletBase
final CryptoCurrency transactionCurrency = final CryptoCurrency transactionCurrency =
balance.keys.firstWhere((element) => element.title == _credentials.currency.title); balance.keys.firstWhere((element) => element.title == _credentials.currency.title);
final _erc20Balance = balance[transactionCurrency]!; final erc20Balance = balance[transactionCurrency]!;
BigInt totalAmount = BigInt.zero; BigInt totalAmount = BigInt.zero;
int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18; int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
num amountToEVMChainMultiplier = pow(10, exponent); num amountToEVMChainMultiplier = pow(10, exponent);
@ -249,7 +249,7 @@ abstract class EVMChainWalletBase
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier); totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
if (_erc20Balance.balance < totalAmount) { if (erc20Balance.balance < totalAmount) {
throw EVMChainTransactionCreationException(transactionCurrency); throw EVMChainTransactionCreationException(transactionCurrency);
} }
} else { } else {
@ -258,18 +258,27 @@ abstract class EVMChainWalletBase
// then no need to subtract the fees from the amount if send all // then no need to subtract the fees from the amount if send all
final BigInt allAmount; final BigInt allAmount;
if (transactionCurrency is Erc20Token) { if (transactionCurrency is Erc20Token) {
allAmount = _erc20Balance.balance; allAmount = erc20Balance.balance;
} else { } else {
allAmount = _erc20Balance.balance - final estimatedFee = BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
}
final totalOriginalAmount =
EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0);
totalAmount = output.sendAll
? allAmount
: BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
if (_erc20Balance.balance < totalAmount) { if (estimatedFee > erc20Balance.balance) {
throw EVMChainTransactionFeesException();
}
allAmount = erc20Balance.balance - estimatedFee;
}
if (output.sendAll) {
totalAmount = allAmount;
} else {
final totalOriginalAmount =
EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0);
totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
}
if (erc20Balance.balance < totalAmount) {
throw EVMChainTransactionCreationException(transactionCurrency); throw EVMChainTransactionCreationException(transactionCurrency);
} }
} }
@ -279,7 +288,7 @@ abstract class EVMChainWalletBase
toAddress: _credentials.outputs.first.isParsedAddress toAddress: _credentials.outputs.first.isParsedAddress
? _credentials.outputs.first.extractedAddress! ? _credentials.outputs.first.extractedAddress!
: _credentials.outputs.first.address, : _credentials.outputs.first.address,
amount: totalAmount.toString(), amount: totalAmount,
gas: _estimatedGas!, gas: _estimatedGas!,
priority: _credentials.priority!, priority: _credentials.priority!,
currency: transactionCurrency, currency: transactionCurrency,

View file

@ -96,16 +96,30 @@ class SolanaWalletClient {
return SolanaBalance(totalBalance); return SolanaBalance(totalBalance);
} }
Future<double> getGasForMessage(String message) async { Future<double> getFeeForMessage(String message, Commitment commitment) async {
try { try {
final gasPrice = await _client!.rpcClient.getFeeForMessage(message) ?? 0; final feeForMessage =
final fee = gasPrice / lamportsPerSol; await _client!.rpcClient.getFeeForMessage(message, commitment: commitment);
final fee = (feeForMessage ?? 0.0) / lamportsPerSol;
return fee; return fee;
} catch (_) { } catch (_) {
return 0; return 0.0;
} }
} }
Future<double> getEstimatedFee(Ed25519HDKeyPair ownerKeypair) async {
const commitment = Commitment.confirmed;
final message =
_getMessageForNativeTransaction(ownerKeypair, ownerKeypair.address, lamportsPerSol);
final recentBlockhash = await _getRecentBlockhash(commitment);
final estimatedFee =
_getFeeFromCompiledMessage(message, ownerKeypair.publicKey, recentBlockhash, commitment);
return estimatedFee;
}
/// Load the Address's transactions into the account /// Load the Address's transactions into the account
Future<List<SolanaTransactionModel>> fetchTransactions( Future<List<SolanaTransactionModel>> fetchTransactions(
Ed25519HDPublicKey publicKey, { Ed25519HDPublicKey publicKey, {
@ -257,24 +271,15 @@ class SolanaWalletClient {
Future<PendingSolanaTransaction> signSolanaTransaction({ Future<PendingSolanaTransaction> signSolanaTransaction({
required String tokenTitle, required String tokenTitle,
required int tokenDecimals, required int tokenDecimals,
String? tokenMint,
required double inputAmount, required double inputAmount,
required String destinationAddress, required String destinationAddress,
required Ed25519HDKeyPair ownerKeypair, required Ed25519HDKeyPair ownerKeypair,
required bool isSendAll,
String? tokenMint,
List<String> references = const [], List<String> references = const [],
}) async { }) async {
const commitment = Commitment.confirmed; const commitment = Commitment.confirmed;
final latestBlockhash =
await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value;
final recentBlockhash = RecentBlockhash(
blockhash: latestBlockhash.blockhash,
feeCalculator: const FeeCalculator(
lamportsPerSignature: 500,
),
);
if (tokenTitle == CryptoCurrency.sol.title) { if (tokenTitle == CryptoCurrency.sol.title) {
final pendingNativeTokenTransaction = await _signNativeTokenTransaction( final pendingNativeTokenTransaction = await _signNativeTokenTransaction(
tokenTitle: tokenTitle, tokenTitle: tokenTitle,
@ -282,8 +287,8 @@ class SolanaWalletClient {
inputAmount: inputAmount, inputAmount: inputAmount,
destinationAddress: destinationAddress, destinationAddress: destinationAddress,
ownerKeypair: ownerKeypair, ownerKeypair: ownerKeypair,
recentBlockhash: recentBlockhash,
commitment: commitment, commitment: commitment,
isSendAll: isSendAll,
); );
return pendingNativeTokenTransaction; return pendingNativeTokenTransaction;
} else { } else {
@ -294,25 +299,29 @@ class SolanaWalletClient {
inputAmount: inputAmount, inputAmount: inputAmount,
destinationAddress: destinationAddress, destinationAddress: destinationAddress,
ownerKeypair: ownerKeypair, ownerKeypair: ownerKeypair,
recentBlockhash: recentBlockhash,
commitment: commitment, commitment: commitment,
); );
return pendingSPLTokenTransaction; return pendingSPLTokenTransaction;
} }
} }
Future<PendingSolanaTransaction> _signNativeTokenTransaction({ Future<RecentBlockhash> _getRecentBlockhash(Commitment commitment) async {
required String tokenTitle, final latestBlockhash =
required int tokenDecimals, await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value;
required double inputAmount,
required String destinationAddress,
required Ed25519HDKeyPair ownerKeypair,
required RecentBlockhash recentBlockhash,
required Commitment commitment,
}) async {
// Convert SOL to lamport
int lamports = (inputAmount * lamportsPerSol).toInt();
final recentBlockhash = RecentBlockhash(
blockhash: latestBlockhash.blockhash,
feeCalculator: const FeeCalculator(lamportsPerSignature: 500),
);
return recentBlockhash;
}
Message _getMessageForNativeTransaction(
Ed25519HDKeyPair ownerKeypair,
String destinationAddress,
int lamports,
) {
final instructions = [ final instructions = [
SystemInstruction.transfer( SystemInstruction.transfer(
fundingAccount: ownerKeypair.publicKey, fundingAccount: ownerKeypair.publicKey,
@ -322,21 +331,75 @@ class SolanaWalletClient {
]; ];
final message = Message(instructions: instructions); final message = Message(instructions: instructions);
return message;
}
Future<double> _getFeeFromCompiledMessage(
Message message,
Ed25519HDPublicKey feePayer,
RecentBlockhash recentBlockhash,
Commitment commitment,
) async {
final compile = message.compile(
recentBlockhash: recentBlockhash.blockhash,
feePayer: feePayer,
);
final base64Message = base64Encode(compile.toByteArray().toList());
final fee = await getFeeForMessage(base64Message, commitment);
return fee;
}
Future<PendingSolanaTransaction> _signNativeTokenTransaction({
required String tokenTitle,
required int tokenDecimals,
required double inputAmount,
required String destinationAddress,
required Ed25519HDKeyPair ownerKeypair,
required Commitment commitment,
required bool isSendAll,
}) async {
// Convert SOL to lamport
int lamports = (inputAmount * lamportsPerSol).toInt();
Message message = _getMessageForNativeTransaction(ownerKeypair, destinationAddress, lamports);
final signers = [ownerKeypair]; final signers = [ownerKeypair];
final signedTx = await _signTransactionInternal( RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment);
message: message,
signers: signers,
commitment: commitment,
recentBlockhash: recentBlockhash,
);
final fee = await _getFeeFromCompiledMessage( final fee = await _getFeeFromCompiledMessage(
message, message,
recentBlockhash,
signers.first.publicKey, signers.first.publicKey,
recentBlockhash,
commitment,
); );
SignedTx signedTx;
if (isSendAll) {
final feeInLamports = (fee * lamportsPerSol).toInt();
final updatedLamports = lamports - feeInLamports;
final updatedMessage =
_getMessageForNativeTransaction(ownerKeypair, destinationAddress, updatedLamports);
signedTx = await _signTransactionInternal(
message: updatedMessage,
signers: signers,
commitment: commitment,
recentBlockhash: recentBlockhash,
);
} else {
signedTx = await _signTransactionInternal(
message: message,
signers: signers,
commitment: commitment,
recentBlockhash: recentBlockhash,
);
}
sendTx() async => await sendTransaction( sendTx() async => await sendTransaction(
signedTransaction: signedTx, signedTransaction: signedTx,
commitment: commitment, commitment: commitment,
@ -360,7 +423,6 @@ class SolanaWalletClient {
required double inputAmount, required double inputAmount,
required String destinationAddress, required String destinationAddress,
required Ed25519HDKeyPair ownerKeypair, required Ed25519HDKeyPair ownerKeypair,
required RecentBlockhash recentBlockhash,
required Commitment commitment, required Commitment commitment,
}) async { }) async {
final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress); final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress);
@ -408,8 +470,18 @@ class SolanaWalletClient {
); );
final message = Message(instructions: [instruction]); final message = Message(instructions: [instruction]);
final signers = [ownerKeypair]; final signers = [ownerKeypair];
RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment);
final fee = await _getFeeFromCompiledMessage(
message,
signers.first.publicKey,
recentBlockhash,
commitment,
);
final signedTx = await _signTransactionInternal( final signedTx = await _signTransactionInternal(
message: message, message: message,
signers: signers, signers: signers,
@ -417,12 +489,6 @@ class SolanaWalletClient {
recentBlockhash: recentBlockhash, recentBlockhash: recentBlockhash,
); );
final fee = await _getFeeFromCompiledMessage(
message,
recentBlockhash,
signers.first.publicKey,
);
sendTx() async => await sendTransaction( sendTx() async => await sendTransaction(
signedTransaction: signedTx, signedTransaction: signedTx,
commitment: commitment, commitment: commitment,
@ -438,19 +504,6 @@ class SolanaWalletClient {
return pendingTransaction; return pendingTransaction;
} }
Future<double> _getFeeFromCompiledMessage(
Message message, RecentBlockhash recentBlockhash, Ed25519HDPublicKey feePayer) async {
final compile = message.compile(
recentBlockhash: recentBlockhash.blockhash,
feePayer: feePayer,
);
final base64Message = base64Encode(compile.toByteArray().toList());
final fee = await getGasForMessage(base64Message);
return fee;
}
Future<SignedTx> _signTransactionInternal({ Future<SignedTx> _signTransactionInternal({
required Message message, required Message message,
required List<Ed25519HDKeyPair> signers, required List<Ed25519HDKeyPair> signers,
@ -466,13 +519,18 @@ class SolanaWalletClient {
required SignedTx signedTransaction, required SignedTx signedTransaction,
required Commitment commitment, required Commitment commitment,
}) async { }) async {
final signature = await _client!.rpcClient.sendTransaction( try {
signedTransaction.encode(), final signature = await _client!.rpcClient.sendTransaction(
preflightCommitment: commitment, signedTransaction.encode(),
); preflightCommitment: commitment,
);
_client!.waitForSignatureStatus(signature, status: commitment); _client!.waitForSignatureStatus(signature, status: commitment);
return signature; return signature;
} catch (e) {
print('Error while sending transaction: ${e.toString()}');
throw Exception(e);
}
} }
} }

View file

@ -75,6 +75,9 @@ abstract class SolanaWalletBase
late SolanaWalletClient _client; late SolanaWalletClient _client;
@observable
double? estimatedFee;
Timer? _transactionsUpdateTimer; Timer? _transactionsUpdateTimer;
late final Box<SPLToken> splTokensBox; late final Box<SPLToken> splTokensBox;
@ -171,6 +174,14 @@ abstract class SolanaWalletBase
} }
} }
Future<void> _getEstimatedFees() async {
try {
estimatedFee = await _client.getEstimatedFee(_walletKeyPair!);
} catch (e) {
estimatedFee = 0.0;
}
}
@override @override
Future<PendingTransaction> createTransaction(Object credentials) async { Future<PendingTransaction> createTransaction(Object credentials) async {
final solCredentials = credentials as SolanaTransactionCredentials; final solCredentials = credentials as SolanaTransactionCredentials;
@ -188,6 +199,8 @@ abstract class SolanaWalletBase
double totalAmount = 0.0; double totalAmount = 0.0;
bool isSendAll = false;
if (hasMultiDestination) { if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
throw SolanaTransactionWrongBalanceException(transactionCurrency); throw SolanaTransactionWrongBalanceException(transactionCurrency);
@ -204,9 +217,15 @@ abstract class SolanaWalletBase
} else { } else {
final output = outputs.first; final output = outputs.first;
final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0'); isSendAll = output.sendAll;
totalAmount = output.sendAll ? walletBalanceForCurrency : totalOriginalAmount; if (isSendAll) {
totalAmount = walletBalanceForCurrency;
} else {
final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
totalAmount = totalOriginalAmount;
}
if (walletBalanceForCurrency < totalAmount) { if (walletBalanceForCurrency < totalAmount) {
throw SolanaTransactionWrongBalanceException(transactionCurrency); throw SolanaTransactionWrongBalanceException(transactionCurrency);
@ -228,6 +247,7 @@ abstract class SolanaWalletBase
destinationAddress: solCredentials.outputs.first.isParsedAddress destinationAddress: solCredentials.outputs.first.isParsedAddress
? solCredentials.outputs.first.extractedAddress! ? solCredentials.outputs.first.extractedAddress!
: solCredentials.outputs.first.address, : solCredentials.outputs.first.address,
isSendAll: isSendAll,
); );
return pendingSolanaTransaction; return pendingSolanaTransaction;
@ -269,7 +289,10 @@ abstract class SolanaWalletBase
Future<void> _updateSPLTokenTransactions() async { Future<void> _updateSPLTokenTransactions() async {
List<SolanaTransactionModel> splTokenTransactions = []; List<SolanaTransactionModel> splTokenTransactions = [];
for (var token in balance.keys) { // Make a copy of keys to avoid concurrent modification
var tokenKeys = List<CryptoCurrency>.from(balance.keys);
for (var token in tokenKeys) {
if (token is SPLToken) { if (token is SPLToken) {
final tokenTxs = await _client.getSPLTokenTransfers( final tokenTxs = await _client.getSPLTokenTransfers(
token.mintAddress, token.mintAddress,
@ -326,6 +349,7 @@ abstract class SolanaWalletBase
_updateBalance(), _updateBalance(),
_updateNativeSOLTransactions(), _updateNativeSOLTransactions(),
_updateSPLTokenTransactions(), _updateSPLTokenTransactions(),
_getEstimatedFees(),
]); ]);
syncStatus = SyncedSyncStatus(); syncStatus = SyncedSyncStatus();
@ -433,18 +457,22 @@ abstract class SolanaWalletBase
final mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress); final mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress);
// Fetch token's metadata account // Fetch token's metadata account
final token = await solanaClient!.rpcClient.getMetadata(mint: mintPublicKey); try {
final token = await solanaClient!.rpcClient.getMetadata(mint: mintPublicKey);
if (token == null) { if (token == null) {
return null;
}
return SPLToken.fromMetadata(
name: token.name,
mint: token.mint,
symbol: token.symbol,
mintAddress: mintAddress,
);
} catch (e) {
return null; return null;
} }
return SPLToken.fromMetadata(
name: token.name,
mint: token.mint,
symbol: token.symbol,
mintAddress: mintAddress,
);
} }
@override @override
@ -475,9 +503,9 @@ abstract class SolanaWalletBase
} }
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 20), (_) { _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 20), (_) {
_updateSPLTokenTransactions();
_updateNativeSOLTransactions();
_updateBalance(); _updateBalance();
_updateNativeSOLTransactions();
_updateSPLTokenTransactions();
}); });
} }

View file

@ -32,6 +32,7 @@ class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
await wallet.init(); await wallet.init();
wallet.addInitialTokens(); wallet.addInitialTokens();
await wallet.save();
return wallet; return wallet;
} }
@ -46,16 +47,31 @@ class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
Future<SolanaWallet> openWallet(String name, String password) async { Future<SolanaWallet> openWallet(String name, String password) async {
final walletInfo = final walletInfo =
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = await SolanaWalletBase.open(
name: name,
password: password,
walletInfo: walletInfo,
);
await wallet.init(); try {
await wallet.save(); final wallet = await SolanaWalletBase.open(
name: name,
password: password,
walletInfo: walletInfo,
);
return wallet; await wallet.init();
await wallet.save();
saveBackup(name);
return wallet;
} catch (_) {
await restoreWalletFilesFromBackup(name);
final wallet = await SolanaWalletBase.open(
name: name,
password: password,
walletInfo: walletInfo,
);
await wallet.init();
await wallet.save();
return wallet;
}
} }
@override @override
@ -110,6 +126,7 @@ class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
password: password, name: currentName, walletInfo: currentWalletInfo); password: password, name: currentName, walletInfo: currentWalletInfo);
await currentWallet.renameWalletFiles(newName); await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
final newWalletInfo = currentWalletInfo; final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.id = WalletBase.idFor(newName, getType());

View file

@ -74,8 +74,22 @@ class CWSolana extends Solana {
} }
@override @override
Future<void> addSPLToken(WalletBase wallet, CryptoCurrency token) async => Future<void> addSPLToken(
await (wallet as SolanaWallet).addSPLToken(token as SPLToken); WalletBase wallet,
CryptoCurrency token,
String contractAddress,
) async {
final splToken = SPLToken(
name: token.name,
symbol: token.title,
mintAddress: contractAddress,
decimal: token.decimals,
mint: token.name.toUpperCase(),
enabled: token.enabled,
);
await (wallet as SolanaWallet).addSPLToken(splToken);
}
@override @override
Future<void> deleteSPLToken(WalletBase wallet, CryptoCurrency token) async => Future<void> deleteSPLToken(WalletBase wallet, CryptoCurrency token) async =>
@ -115,4 +129,9 @@ class CWSolana extends Solana {
return null; return null;
} }
@override
double? getEstimateFees(WalletBase wallet) {
return (wallet as SolanaWallet).estimatedFee;
}
} }

View file

@ -195,12 +195,14 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
onPressed: () async { onPressed: () async {
if (_formKey.currentState!.validate() && if (_formKey.currentState!.validate() &&
(!_showDisclaimer || _disclaimerChecked)) { (!_showDisclaimer || _disclaimerChecked)) {
await widget.homeSettingsViewModel.addToken(Erc20Token( await widget.homeSettingsViewModel.addToken(
name: _tokenNameController.text, token: CryptoCurrency(
symbol: _tokenSymbolController.text, name: _tokenNameController.text,
title: _tokenSymbolController.text.toUpperCase(),
decimals: int.parse(_tokenDecimalController.text),
),
contractAddress: _contractAddressController.text, contractAddress: _contractAddressController.text,
decimal: int.parse(_tokenDecimalController.text), );
));
if (context.mounted) { if (context.mounted) {
Navigator.pop(context); Navigator.pop(context);
} }

View file

@ -323,8 +323,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
? sendViewModel.allAmountValidator ? sendViewModel.allAmountValidator
: sendViewModel.amountValidator, : sendViewModel.amountValidator,
), ),
if (!sendViewModel.isBatchSending && if (!sendViewModel.isBatchSending)
sendViewModel.shouldDisplaySendALL)
Positioned( Positioned(
top: 2, top: 2,
right: 0, right: 0,
@ -456,7 +455,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
if (sendViewModel.hasFees) if (sendViewModel.hasFees)
Observer( Observer(
builder: (_) => GestureDetector( builder: (_) => GestureDetector(
onTap: () => _setTransactionPriority(context), onTap: sendViewModel.hasFeesPriority
? () => _setTransactionPriority(context)
: () {},
child: Container( child: Container(
padding: EdgeInsets.only(top: 24), padding: EdgeInsets.only(top: 24),
child: Row( child: Row(

View file

@ -44,17 +44,37 @@ abstract class HomeSettingsViewModelBase with Store {
@action @action
void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value; void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value;
Future<void> addToken(CryptoCurrency token) async { Future<void> addToken({
required String contractAddress,
required CryptoCurrency token,
}) async {
if (_balanceViewModel.wallet.type == WalletType.ethereum) { if (_balanceViewModel.wallet.type == WalletType.ethereum) {
await ethereum!.addErc20Token(_balanceViewModel.wallet, token); final erc20token = Erc20Token(
name: token.name,
symbol: token.title,
decimal: token.decimals,
contractAddress: contractAddress,
);
await ethereum!.addErc20Token(_balanceViewModel.wallet, erc20token);
} }
if (_balanceViewModel.wallet.type == WalletType.polygon) { if (_balanceViewModel.wallet.type == WalletType.polygon) {
await polygon!.addErc20Token(_balanceViewModel.wallet, token); final polygonToken = Erc20Token(
name: token.name,
symbol: token.title,
decimal: token.decimals,
contractAddress: contractAddress,
);
await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken);
} }
if (_balanceViewModel.wallet.type == WalletType.solana) { if (_balanceViewModel.wallet.type == WalletType.solana) {
await solana!.addSPLToken(_balanceViewModel.wallet, token); await solana!.addSPLToken(
_balanceViewModel.wallet,
token,
contractAddress,
);
} }
_updateTokensList(); _updateTokensList();
@ -117,7 +137,8 @@ abstract class HomeSettingsViewModelBase with Store {
} }
if (_balanceViewModel.wallet.type == WalletType.solana) { if (_balanceViewModel.wallet.type == WalletType.solana) {
solana!.addSPLToken(_balanceViewModel.wallet, token); final address = solana!.getTokenAddress(token);
solana!.addSPLToken(_balanceViewModel.wallet, token, address);
} }
_refreshTokensList(); _refreshTokensList();

View file

@ -6,6 +6,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart'; import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -116,6 +117,10 @@ abstract class OutputBase with Store {
@computed @computed
double get estimatedFee { double get estimatedFee {
try { try {
if (_wallet.type == WalletType.solana) {
return solana!.getEstimateFees(_wallet) ?? 0.0;
}
final fee = _wallet.calculateEstimatedFee( final fee = _wallet.calculateEstimatedFee(
_settingsStore.priority[_wallet.type]!, formattedCryptoAmount); _settingsStore.priority[_wallet.type]!, formattedCryptoAmount);

View file

@ -106,8 +106,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@computed @computed
bool get isBatchSending => outputs.length > 1; bool get isBatchSending => outputs.length > 1;
bool get shouldDisplaySendALL => walletType != WalletType.solana;
@computed @computed
String get pendingTransactionFiatAmount { String get pendingTransactionFiatAmount {
if (pendingTransaction == null) { if (pendingTransaction == null) {
@ -208,6 +206,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@computed @computed
bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano; bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano;
@computed
bool get hasFeesPriority =>
wallet.type != WalletType.nano &&
wallet.type != WalletType.banano &&
wallet.type != WalletType.solana;
@observable @observable
CryptoCurrency selectedCryptoCurrency; CryptoCurrency selectedCryptoCurrency;

View file

@ -948,7 +948,11 @@ abstract class Solana {
required CryptoCurrency currency, required CryptoCurrency currency,
}); });
List<CryptoCurrency> getSPLTokenCurrencies(WalletBase wallet); List<CryptoCurrency> getSPLTokenCurrencies(WalletBase wallet);
Future<void> addSPLToken(WalletBase wallet, CryptoCurrency token); Future<void> addSPLToken(
WalletBase wallet,
CryptoCurrency token,
String contractAddress,
);
Future<void> deleteSPLToken(WalletBase wallet, CryptoCurrency token); Future<void> deleteSPLToken(WalletBase wallet, CryptoCurrency token);
Future<CryptoCurrency?> getSPLToken(WalletBase wallet, String contractAddress); Future<CryptoCurrency?> getSPLToken(WalletBase wallet, String contractAddress);
@ -956,6 +960,7 @@ abstract class Solana {
double getTransactionAmountRaw(TransactionInfo transactionInfo); double getTransactionAmountRaw(TransactionInfo transactionInfo);
String getTokenAddress(CryptoCurrency asset); String getTokenAddress(CryptoCurrency asset);
List<int>? getValidationLength(CryptoCurrency type); List<int>? getValidationLength(CryptoCurrency type);
double? getEstimateFees(WalletBase wallet);
} }
"""; """;