mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-28 06:28:41 +01:00
feat: Integrate Jupiter DEX (#2761)
* 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 --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Omar Hatem
parent
368777514e
commit
cf795bcc48
@@ -8,6 +8,7 @@ import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/jupiter_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/near_Intents_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/swapsxyz_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/swaptrade_exchange_provider.dart';
|
||||
@@ -86,6 +87,9 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
case ExchangeProviderDescription.swapsXyz:
|
||||
_provider = SwapsXyzExchangeProvider();
|
||||
break;
|
||||
case ExchangeProviderDescription.jupiter:
|
||||
_provider = JupiterExchangeProvider();
|
||||
break;
|
||||
case ExchangeProviderDescription.nearIntents:
|
||||
_provider = NearIntentsExchangeProvider();
|
||||
break;
|
||||
@@ -118,6 +122,19 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
isEVMCompatibleChain(wallet.type) &&
|
||||
wallet.currency != trade.from;
|
||||
|
||||
/// Providers that should hide the "send from external" button
|
||||
static const List<Type> _providersThatHideExternalSend = [
|
||||
JupiterExchangeProvider,
|
||||
];
|
||||
|
||||
/// Returns true if the current provider should hide the external send button
|
||||
bool get shouldHideExternalSendButton {
|
||||
if (_provider == null) return false;
|
||||
return _providersThatHideExternalSend.any(
|
||||
(providerType) => _provider.runtimeType == providerType,
|
||||
);
|
||||
}
|
||||
|
||||
String get extraInfo => trade.extraId != null && trade.extraId!.isNotEmpty
|
||||
? '\n\n' + S.current.exchange_extra_info
|
||||
: '';
|
||||
|
||||
@@ -21,6 +21,7 @@ import 'package:cake_wallet/exchange/exchange_trade_state.dart';
|
||||
import 'package:cake_wallet/exchange/limits.dart';
|
||||
import 'package:cake_wallet/exchange/limits_state.dart';
|
||||
import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/jupiter_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/letsexchange_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
@@ -229,6 +230,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
LetsExchangeExchangeProvider(),
|
||||
StealthExExchangeProvider(),
|
||||
XOSwapExchangeProvider(),
|
||||
JupiterExchangeProvider(),
|
||||
// SwapsXyzExchangeProvider(),
|
||||
NearIntentsExchangeProvider(),
|
||||
TrocadorExchangeProvider(
|
||||
|
||||
@@ -21,15 +21,17 @@ import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||
import 'package:cake_wallet/evm/evm.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/jupiter_exchange_provider.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/exchange/provider/swapsxyz_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
@@ -136,6 +138,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
PendingTransaction? _pendingApprovalTx;
|
||||
bool _isSwapsXYZCallDataTx = false;
|
||||
|
||||
// Store trade and provider references for post-commit updates (e.g., Jupiter trade ID update)
|
||||
Trade? _currentTrade;
|
||||
ExchangeProvider? _currentProvider;
|
||||
|
||||
@observable
|
||||
ExecutionState state;
|
||||
|
||||
@@ -516,6 +522,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
@action
|
||||
Future<PendingTransaction?> createTransaction({ExchangeProvider? provider, Trade? trade}) async {
|
||||
_currentTrade = trade;
|
||||
_currentProvider = provider;
|
||||
|
||||
try {
|
||||
if (!(state is IsExecutingState)) state = IsExecutingState();
|
||||
|
||||
@@ -608,6 +617,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));
|
||||
@@ -738,12 +779,33 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
state = TransactionCommitted();
|
||||
|
||||
// Immediate transaction update for EVM chains, Solana, Tron, and Nano
|
||||
if (isEVMWallet ||
|
||||
[WalletType.solana, WalletType.tron, WalletType.nano].contains(walletType)) {
|
||||
await _updateSolanaTrade(signature: pendingTransaction!.id, isSuccess: true);
|
||||
|
||||
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,
|
||||
initialDelay: const Duration(seconds: 1),
|
||||
maxRetries: 5,
|
||||
);
|
||||
} catch (e) {
|
||||
printV('Failed to update transactions after send: $e');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Immediate transaction update for EVM chains, Tron, and Nano
|
||||
if (isEVMWallet || [WalletType.tron, WalletType.nano].contains(walletType)) {
|
||||
Future.delayed(Duration(seconds: 4), () async {
|
||||
try {
|
||||
await wallet.updateTransactionsHistory();
|
||||
await Future.wait([
|
||||
wallet.updateTransactionsHistory(),
|
||||
wallet.updateBalance() as Future<void>,
|
||||
]);
|
||||
} catch (e) {
|
||||
printV('Failed to update transactions after send: $e');
|
||||
}
|
||||
@@ -757,7 +819,38 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update Jupiter trade with relevant details after transaction is committed
|
||||
Future<void> _updateSolanaTrade({required String signature, required bool isSuccess}) async {
|
||||
if (_currentTrade == null ||
|
||||
_currentProvider?.title != 'Jupiter' ||
|
||||
walletType != WalletType.solana) return;
|
||||
|
||||
_currentTrade!.txId = signature;
|
||||
|
||||
if (!isSuccess) {
|
||||
_currentTrade!.stateRaw = TradeState.failed.raw;
|
||||
if (_currentTrade!.isInBox) {
|
||||
await _currentTrade!.save();
|
||||
}
|
||||
}
|
||||
|
||||
if (isSuccess) {
|
||||
_currentTrade!.stateRaw = TradeState.completed.raw;
|
||||
|
||||
if (_currentTrade!.isInBox) {
|
||||
await _currentTrade!.save();
|
||||
}
|
||||
|
||||
_currentTrade = null;
|
||||
_currentProvider = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/letsexchange_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/jupiter_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/near_Intents_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/swapsxyz_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/swaptrade_exchange_provider.dart';
|
||||
@@ -82,6 +83,9 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||
case ExchangeProviderDescription.swapsXyz:
|
||||
_provider = SwapsXyzExchangeProvider();
|
||||
break;
|
||||
case ExchangeProviderDescription.jupiter:
|
||||
_provider = JupiterExchangeProvider();
|
||||
break;
|
||||
case ExchangeProviderDescription.nearIntents:
|
||||
_provider = NearIntentsExchangeProvider();
|
||||
break;
|
||||
@@ -119,6 +123,8 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||
return 'https://scan.chainflip.io/channels/${trade.id}';
|
||||
case ExchangeProviderDescription.xoSwap:
|
||||
return 'https://orders.xoswap.com/${trade.id}';
|
||||
case ExchangeProviderDescription.jupiter:
|
||||
return 'https://solscan.io/tx/${trade.txId}';
|
||||
case ExchangeProviderDescription.nearIntents:
|
||||
return 'https://explorer.near-intents.org/transactions/${trade.id}';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user