mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Smoothen Jupiter Swap for Solana Wallets (#2816)
* feat: Integrate Jupiter DEX * feat: Enable internal swaps * feat: implement Jupiter swap execution api and enhance trade handling - Added executeSwap method to handle signed swap transactions via the execute endpoint. - Updated signAndPrepareJupiterSwapTransaction to include request ID and handle swap execution response. - Modified trade details to use txId instead of id for Jupiter trades. - Enhanced error handling for swap execution with user-friendly messages. - Updated sendviewmodel to manage trade state updates after transaction commitment. * fix: null error after successfully swapping * fix: Finallyyy fixed the annoying tx history issue for solana dex swaps * fix: update inputAddress to use toAddress in JupiterExchangeProvider * feat: enhance transaction handling and token balance updates - Cut down tx fetch/update time for transactions update after swapping - Added TransactionFetchResult class to hold parsed transactions and token mints. - Added pollForTransaction method to handle transaction polling with exponential backoff. - Conditionally hide the external send button based on provider type. * feat: add fees to trade object and handle null case * feat: add Jupiter referral fee and account configuration * fix: Jupiter dex swap fixes - Fetch and display balance after each completed swap - Make independent calls parallel, boosting balance display - Remove incoming or outoging tags from tx history
This commit is contained in:
@@ -531,9 +531,13 @@ abstract class SolanaWalletBase
|
||||
}
|
||||
|
||||
Future<void> updateTokenBalance({List<String>? 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();
|
||||
}
|
||||
|
||||
@@ -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<CryptoCurrency> _notSupported = [];
|
||||
|
||||
static final List<CryptoCurrency> _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';
|
||||
|
||||
@@ -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<void>(context, S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child: ListRow(
|
||||
|
||||
@@ -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 = <String>[];
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user