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:
David Adegoke
2026-01-16 23:28:36 +01:00
committed by GitHub
co-authored by Omar Hatem
parent 368777514e
commit cf795bcc48
16 changed files with 1509 additions and 67 deletions
@@ -23,6 +23,7 @@ class SyncIndicatorIcon extends StatelessWidget {
static const String finished = 'finished';
static const String success = 'success';
static const String complete = 'complete';
static const String completed = 'completed';
@override
Widget build(BuildContext context) {
@@ -39,6 +40,7 @@ class SyncIndicatorIcon extends StatelessWidget {
case finished:
case success:
case complete:
case completed:
indicatorColor = CustomThemeColors.syncGreen;
break;
case waiting:
@@ -163,20 +163,21 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24),
bottomSection: Column(
children: [
PrimaryButton(
key: ValueKey('exchange_trade_page_send_from_external_button_key'),
text: S.current.send_from_external_wallet,
onPressed: () async {
Navigator.of(context).pushNamed(Routes.exchangeTradeExternalSendPage);
},
color: widget.exchangeTradeViewModel.isSendable
? Theme.of(context).colorScheme.surfaceContainer
: Theme.of(context).colorScheme.primary,
textColor: widget.exchangeTradeViewModel.isSendable
? Theme.of(context).colorScheme.onSecondaryContainer
: Theme.of(context).colorScheme.onPrimary,
isDisabled: widget.exchangeTradeViewModel.isSwapsXyzSendingEVMTokenSwap,
),
if (!widget.exchangeTradeViewModel.shouldHideExternalSendButton)
PrimaryButton(
key: ValueKey('exchange_trade_page_send_from_external_button_key'),
text: S.current.send_from_external_wallet,
onPressed: () async {
Navigator.of(context).pushNamed(Routes.exchangeTradeExternalSendPage);
},
color: widget.exchangeTradeViewModel.isSendable
? Theme.of(context).colorScheme.surfaceContainer
: Theme.of(context).colorScheme.primary,
textColor: widget.exchangeTradeViewModel.isSendable
? Theme.of(context).colorScheme.onSecondaryContainer
: Theme.of(context).colorScheme.onPrimary,
isDisabled: widget.exchangeTradeViewModel.isSwapsXyzSendingEVMTokenSwap,
),
SizedBox(height: 16),
Observer(
builder: (_) {
@@ -188,7 +189,8 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
!(sendingState is TransactionCommitted)),
child: LoadingPrimaryButton(
key: ValueKey('exchange_trade_page_send_from_cake_button_key'),
isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty ||
isDisabled: trade.inputAddress == null ||
trade.inputAddress!.isEmpty ||
sendingState is ExecutedSuccessfullyState,
isLoading: sendingState is IsExecutingState,
onPressed: _onPressedSendFromCakeWallet,
@@ -213,8 +215,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendVM.walletType,
hardwareWalletType:
sendVM.wallet.walletInfo.hardwareWalletType!,
hardwareWalletType: sendVM.wallet.walletInfo.hardwareWalletType!,
onConnectDevice: (context, _) {
sendVM.hardwareWalletViewModel!.initWallet(sendVM.wallet);
Navigator.of(context).pop();
@@ -308,7 +309,8 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
return ConfirmSendingBottomSheet(
key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'),
footerType: FooterType.slideActionButton,
isSlideActionEnabled: widget.exchangeTradeViewModel.sendViewModel.isReadyForSend,
isSlideActionEnabled:
widget.exchangeTradeViewModel.sendViewModel.isReadyForSend,
walletType: widget.exchangeTradeViewModel.sendViewModel.walletType,
titleText: S.of(bottomSheetContext).confirm_transaction,
titleIconPath:
@@ -338,8 +340,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
},
);
if (result == null) widget.exchangeTradeViewModel.sendViewModel.dismissTransaction();
if (result == null) widget.exchangeTradeViewModel.sendViewModel.dismissTransaction();
}
});
}