mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
fix: solana wallet bugs and security issues (#3484)
* fix android CI * fix: duplicate outgoing tx for jup swaps and stuck pending state * fix solana security risk by handling duplicate token symbols in wallet transactions * feat: implement additional cost handling for pending transactions in Solana wallet. * fix: Items on security audit list for solana wallet * refactor: streamline fee payer index handling and improve error logging * fix: verifySignature for solana and handle wrong mint on default token * fix: token decimals defaulting to zero and breaking amount parsing in solana * fix: use token mask in tx history * refactor: apply lint to modified code * fix: merge conflicts * fix: use Money and mint decimals when parsing sol swaps * test: add unit tests for SPL token amount handling and parsing * test: add more tests * fix: update decimal handling for fetched token and remove unused fields
This commit is contained in:
@@ -631,6 +631,7 @@ class CakePayBuyCardPage extends BasePage {
|
||||
amountValue: _sendViewModel.amountParsingProxy
|
||||
.asDisplayStringWithSymbol(_sendViewModel.pendingTransaction!.amount),
|
||||
quantity: 'QTY: ${cakePayBuyCardViewModel.quantity}',
|
||||
explanation: _sendViewModel.pendingTransactionAdditionalCostNotice,
|
||||
fiatAmountValue: _sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(bottomSheetContext).send_fee,
|
||||
feeValue: _sendViewModel.amountParsingProxy
|
||||
|
||||
@@ -138,6 +138,10 @@ class JupiterExchangeProvider extends ExchangeProvider {
|
||||
if (!_isSolanaCurrency(from) || !_isSolanaCurrency(to)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if (amount <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
final inputMint = _getTokenMint(from);
|
||||
final outputMint = _getTokenMint(to);
|
||||
|
||||
|
||||
@@ -100,9 +100,9 @@ class HistoryTile extends StatelessWidget {
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: pending ? 0.5 : 1,
|
||||
child: CakeImageWidget(
|
||||
child: TokenImageWidget(
|
||||
imageUrl: asset?.iconPath ?? "",
|
||||
width: 34,
|
||||
size: 34,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
|
||||
@@ -201,6 +201,7 @@ class SendTransactionDetails extends StatelessWidget {
|
||||
Widget _buildMainContent(BuildContext context) {
|
||||
return Observer(builder: (context) {
|
||||
final transaction = sendViewModel.pendingTransaction;
|
||||
final additionalCostNotice = sendViewModel.pendingTransactionAdditionalCostNotice;
|
||||
|
||||
final currencySymbol =
|
||||
sendViewModel.amountParsingProxy.getCryptoSymbol(sendViewModel.selectedCryptoCurrency);
|
||||
@@ -398,6 +399,26 @@ class SendTransactionDetails extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (additionalCostNotice != null) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
additionalCostNotice,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (sendViewModel.isElectrumWallet) ...[
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
|
||||
@@ -130,11 +130,11 @@ class CWSolana extends Solana {
|
||||
|
||||
final token = (wallet as SolanaWallet).splTokenBySymbol(transaction.amount.currency.symbol);
|
||||
|
||||
if (token == null) {
|
||||
throw StateError('No SPL token for symbol ${transaction.amount.currency.symbol}');
|
||||
if (token != null) {
|
||||
return token;
|
||||
}
|
||||
|
||||
return token;
|
||||
return transaction.amount.currency as CryptoCurrency;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -276,6 +276,8 @@ class CWSolana extends Solana {
|
||||
'Jupiter swap returned unknown status: $status. Error: $errorMessage. Code: $errorCode',
|
||||
);
|
||||
}
|
||||
} on JupiterSwapFailedException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
throw Exception('Failed to execute Jupiter swap: $e');
|
||||
}
|
||||
@@ -420,7 +422,9 @@ class CWSolana extends Solana {
|
||||
if (discoveredMints.isNotEmpty) {
|
||||
await wallet.updateSPLTokenTransactions(specificMints: discoveredMints);
|
||||
}
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
printV("Error discovering wallet tokens: ${e.toString()}");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -328,6 +328,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
amount: S.of(bottomSheetContext).send_amount,
|
||||
amountValue: sendVM.amountParsingProxy
|
||||
.getDisplayCryptoAmount(amountValue, sendVM.selectedCryptoCurrency),
|
||||
explanation: sendVM.pendingTransactionAdditionalCostNotice,
|
||||
fiatAmountValue: fiatAmountValue,
|
||||
fee: isEVMCompatibleChain(sendVM.walletType)
|
||||
? S.of(bottomSheetContext).send_estimated_fee
|
||||
|
||||
@@ -336,6 +336,18 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
@observable
|
||||
PendingTransaction? pendingTransaction;
|
||||
|
||||
String? get pendingTransactionAdditionalCostNotice {
|
||||
final additionalCost = pendingTransaction?.additionalCost;
|
||||
|
||||
if (additionalCost == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return S.current.recipient_account_creation_fee(
|
||||
_appStore.amountParsingProxy.asDisplayStringWithSymbol(additionalCost),
|
||||
);
|
||||
}
|
||||
|
||||
@computed
|
||||
String get balance {
|
||||
if (walletType == WalletType.litecoin && coinTypeToSpendFrom == UnspentCoinType.mweb) {
|
||||
@@ -1179,11 +1191,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name),
|
||||
DateTime.now().add(Duration(minutes: 1)).toIso8601String());
|
||||
} catch (e) {
|
||||
if (e is JupiterSwapFailedException) {
|
||||
await _updateSolanaTrade(signature: e.signature, isSuccess: false);
|
||||
}
|
||||
state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
|
||||
await _updateSolanaTrade(signature: '', isSuccess: false);
|
||||
|
||||
final failedSignature = e is JupiterSwapFailedException ? e.signature : "";
|
||||
|
||||
await _updateSolanaTrade(signature: failedSignature, isSuccess: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1509,6 +1521,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
return S.current.solana_no_associated_token_account_exception;
|
||||
}
|
||||
|
||||
if (error is AmbiguousTokenSymbolException) {
|
||||
return S.current.ambiguous_token_symbol_exception(error.symbol);
|
||||
}
|
||||
|
||||
if (errorMessage.contains('found no record of a prior credit')) {
|
||||
return S.current.insufficient_funds_for_tx;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user