diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 8e78fd42f..dba04a9df 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -531,9 +531,13 @@ abstract class SolanaWalletBase } Future updateTokenBalance({List? tokenMints}) async { - balance[CryptoCurrency.sol] = await _fetchSOLBalance(); - - await _updateSplTokenBalancesInternal(tokenMints: tokenMints); + // Fetch SOL and SPL token balances in parallel for better performance + await Future.wait([ + _fetchSOLBalance().then((solBalance) { + balance[CryptoCurrency.sol] = solBalance; + }), + _updateSplTokenBalancesInternal(tokenMints: tokenMints), + ]); await save(); } diff --git a/lib/exchange/provider/jupiter_exchange_provider.dart b/lib/exchange/provider/jupiter_exchange_provider.dart index a2bb69095..a10920b12 100644 --- a/lib/exchange/provider/jupiter_exchange_provider.dart +++ b/lib/exchange/provider/jupiter_exchange_provider.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:cake_wallet/.secrets.g.dart' as secrets; -import 'package:cake_wallet/exchange/exchange_pair.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/limits.dart'; import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; @@ -23,9 +22,9 @@ class JupiterExchangeProvider extends ExchangeProvider { static const List _notSupported = []; static final List _supportedCurrencies = CryptoCurrency.all - .where((c) => c.tag == 'SOL' || c == CryptoCurrency.sol) - .where((c) => !_notSupported.contains(c)) - .toList(); + .where((c) => c.tag == 'SOL' || c == CryptoCurrency.sol) + .where((c) => !_notSupported.contains(c)) + .toList(); static const _baseUrl = 'api.jup.ag'; static const _orderPath = '/ultra/v1/order'; diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart index cbfd5286b..d78943867 100644 --- a/lib/src/screens/transaction_details/transaction_details_page.dart +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -55,7 +55,11 @@ class TransactionDetailsPage extends BasePage { return GestureDetector( key: item.key, onTap: () { - Clipboard.setData(ClipboardData(text: item.value)); + final textToCopy = item.title.toLowerCase() == + S.of(context).transaction_details_transaction_id.toLowerCase() + ? item.value.replaceAll(RegExp(r'_(incoming|outgoing)$'), '') + : item.value; + Clipboard.setData(ClipboardData(text: textToCopy)); showBar(context, S.of(context).transaction_details_copied(item.title)); }, child: ListRow( diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 3d9c9b480..9eabb0244 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -649,6 +649,38 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } } + // Jupiter (Solana) swap path + if (walletType == WalletType.solana && trade != null && provider is JupiterExchangeProvider) { + final swapTransactionBase64 = trade.routerData; + final requestId = trade.routerValue; + if (swapTransactionBase64?.isNotEmpty == true && + requestId?.isNotEmpty == true && + solana != null) { + try { + final actualFee = trade.fee ?? 0.0005; + // Fallback to estimate if not available + final fee = actualFee > 0 ? actualFee : 0.0005; + + final amount = double.tryParse(trade.amount) ?? 0.0; + + pendingTransaction = await solana!.signAndPrepareJupiterSwapTransaction( + wallet, + swapTransactionBase64!, + requestId!, + trade.payoutAddress ?? '', + amount, + fee, + ); + + state = ExecutedSuccessfullyState(); + return pendingTransaction; + } catch (e, s) { + printV('Jupiter swap error: $e\n$s'); + throw Exception('Failed to process Jupiter swap: $e'); + } + } + } + // Regular flow pendingTransaction = await wallet.createTransaction(_credentials(provider)); @@ -784,8 +816,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor if (walletType == WalletType.solana) { Future.delayed(Duration(seconds: 1), () async { try { - // Updates tx history with the exact mints involved in transaction - // Also updates balances for the tokens involved in the transaction await solana!.pollForTransaction( wallet, pendingTransaction!.id, @@ -793,9 +823,62 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor maxRetries: 5, ); } catch (e) { - printV('Failed to update transactions after send: $e'); + printV('Failed to poll for transaction: $e'); } }); + + // Update balances for currencies involved in swap + if (_currentTrade != null) { + Future.delayed(Duration(seconds: 2), () async { + try { + final tokenMints = []; + + // Extract from currency mint (skip native SOL) + if (_currentTrade!.from != null && _currentTrade!.from != CryptoCurrency.sol) { + try { + final fromMint = solana!.getTokenAddress(_currentTrade!.from!); + tokenMints.add(fromMint); + } catch (e) { + printV('Error getting from currency mint: $e'); + } + } + + // Extract to currency mint (skip native SOL) + if (_currentTrade!.to != null && _currentTrade!.to != CryptoCurrency.sol) { + try { + final toMint = solana!.getTokenAddress(_currentTrade!.to!); + tokenMints.add(toMint); + } catch (e) { + printV('Error getting to currency mint: $e'); + } + } + + if (tokenMints.isNotEmpty) { + solana!.updateTokenBalances( + wallet, + tokenMints: tokenMints, + ); + + // Retry after a bit more time to ensure balance is updated + Future.delayed(Duration(seconds: 2), () async { + try { + await solana!.updateTokenBalances( + wallet, + tokenMints: tokenMints, + ); + } catch (e) { + printV('Error retrying balance update: $e'); + } + }); + } + } catch (e) { + printV('Failed to update balances after send: $e'); + } finally { + _currentTrade = null; + _currentProvider = null; + } + }); + } } // Immediate transaction update for EVM chains, Tron, and Nano @@ -835,22 +918,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor _currentTrade!.txId = signature; - if (!isSuccess) { - _currentTrade!.stateRaw = TradeState.failed.raw; - if (_currentTrade!.isInBox) { - await _currentTrade!.save(); - } - } + _currentTrade!.stateRaw = isSuccess ? TradeState.completed.raw : TradeState.failed.raw; - if (isSuccess) { - _currentTrade!.stateRaw = TradeState.completed.raw; - - if (_currentTrade!.isInBox) { - await _currentTrade!.save(); - } - - _currentTrade = null; - _currentProvider = null; + if (_currentTrade!.isInBox) { + await _currentTrade!.save(); } } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index c5e49436e..37e403410 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -555,7 +555,7 @@ abstract class TransactionDetailsViewModelBase with Store { final _items = [ StandartListItem( title: S.current.transaction_details_transaction_id, - value: tx.txHash, + value: tx.txHash.replaceAll(RegExp(r'_(incoming|outgoing)$'), ''), key: ValueKey('standard_list_item_transaction_details_id_key'), ), StandartListItem(