Repository: OneKeyHQ/app-monorepo
Pull request #7362: Fix/speed swap approve
Author: login: ezailWang
Created: 2025-05-20T08:47:34Z | Merged: 2025-05-20T09:07:59Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added full support for managing and tracking the approval state of "speed swap" transactions, including interval-based polling, state updates, and UI notifications.
  - Introduced new UI feedback and notification handling for speed swap approval success, failure, or cancellation.
  - Added new event handling for speed swap approval resets.

- **Improvements**
  - Enhanced token approval management in the speed swap flow, including allowance checking and approval transaction handling.
  - Improved error handling and safety by using optional chaining to prevent runtime errors in various components.

- **Bug Fixes**
  - Prevented potential runtime errors by safely accessing properties that may be undefined.

- **Other**
  - Updated types and constants to support new speed swap approval features and configurations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/packages/kit-bg/src/services/ServiceSwap.ts b/packages/kit-bg/src/services/ServiceSwap.ts
index 1332d2aa6d37..75dfc8507ef4 100644
--- a/packages/kit-bg/src/services/ServiceSwap.ts
+++ b/packages/kit-bg/src/services/ServiceSwap.ts
@@ -42,6 +42,7 @@ import {
   swapHistoryStateFetchInterval,
   swapHistoryStateFetchRiceIntervalCount,
   swapQuoteEventTimeout,
+  swapSpeedSwapApprovingStateFetchInterval,
 } from '@onekeyhq/shared/types/swap/SwapProvider.constants';
 import type {
   ESwapQuoteKind,
@@ -58,6 +59,7 @@ import type {
   IFetchTokensParams,
   IOKXTransactionObject,
   ISpeedSwapConfig,
+  ISwapApproveAllowanceResponse,
   ISwapApproveTransaction,
   ISwapCheckSupportResponse,
   ISwapNetwork,
@@ -129,6 +131,10 @@ export default class ServiceSwap extends ServiceBase {
 
   private approvingIntervalCount = 0;
 
+  private speedSwapApprovingInterval: ReturnType<typeof setTimeout> | undefined;
+
+  private speedSwapApprovingIntervalCount = 0;
+
   constructor({ backgroundApi }: { backgroundApi: any }) {
     super({ backgroundApi });
   }
@@ -978,16 +984,15 @@ export default class ServiceSwap extends ServiceBase {
     };
     const client = await this.getClient(EServiceEndpointEnum.Swap);
 
-    const { data } = await client.get<IFetchResponse<string>>(
-      '/swap/v1/allowance',
-      {
-        params,
-        headers:
-          await this.backgroundApi.serviceAccountProfile._getWalletTypeHeader({
-            accountId,
-          }),
-      },
-    );
+    const { data } = await client.get<
+      IFetchResponse<ISwapApproveAllowanceResponse>
+    >('/swap/v1/allowance', {
+      params,
+      headers:
+        await this.backgroundApi.serviceAccountProfile._getWalletTypeHeader({
+          accountId,
+        }),
+    });
     return data?.data;
   }
 
@@ -998,6 +1003,12 @@ export default class ServiceSwap extends ServiceBase {
     return swapApprovingTransaction;
   }
 
+  @backgroundMethod()
+  async getSpeedSwapApprovingTransaction() {
+    const { speedSwapApprovingTransaction } = await inAppNotificationAtom.get();
+    return speedSwapApprovingTransaction;
+  }
+
   @backgroundMethod()
   async setApprovingTransaction(item?: ISwapApproveTransaction) {
     await inAppNotificationAtom.set((pre) => ({
@@ -1009,6 +1020,17 @@ export default class ServiceSwap extends ServiceBase {
     }));
   }
 
+  @backgroundMethod()
+  async setSpeedSwapApprovingTransaction(item?: ISwapApproveTransaction) {
+    await inAppNotificationAtom.set((pre) => ({
+      ...pre,
+      speedSwapApprovingTransaction: item,
+      ...(item?.status !== ESwapApproveTransactionStatus.PENDING && {
+        speedSwapApprovingLoading: false,
+      }),
+    }));
+  }
+
   @backgroundMethod()
   async closeApproving() {
     await inAppNotificationAtom.set((pre) => ({
@@ -1018,6 +1040,15 @@ export default class ServiceSwap extends ServiceBase {
     }));
   }
 
+  @backgroundMethod()
+  async closeSpeedSwapApproving() {
+    await inAppNotificationAtom.set((pre) => ({
+      ...pre,
+      speedSwapApprovingTransaction: undefined,
+      speedSwapApprovingLoading: false,
+    }));
+  }
+
   // --- swap provider manager
   @backgroundMethod()
   async getSwapProviderManager() {
@@ -1068,6 +1099,14 @@ export default class ServiceSwap extends ServiceBase {
     }
   }
 
+  @backgroundMethod()
+  async cleanSpeedSwapApprovingInterval() {
+    if (this.speedSwapApprovingInterval) {
+      clearTimeout(this.speedSwapApprovingInterval);
+      this.speedSwapApprovingInterval = undefined;
+    }
+  }
+
   async approvingStateRunSync(networkId: string, txId: string) {
     let enableInterval = true;
     try {
@@ -1123,6 +1162,61 @@ export default class ServiceSwap extends ServiceBase {
     }
   }
 
+  async speedSwapApprovingStateRunSync(networkId: string, txId: string) {
+    let enableInterval = true;
+    try {
+      const txState = await this.fetchTxState({
+        txId,
+        networkId,
+      });
+      const preApproveTx = await this.getSpeedSwapApprovingTransaction();
+      if (
+        txState.state === ESwapTxHistoryStatus.SUCCESS ||
+        txState.state === ESwapTxHistoryStatus.FAILED
+      ) {
+        enableInterval = false;
+        if (preApproveTx) {
+          if (
+            txState.state === ESwapTxHistoryStatus.SUCCESS ||
+            txState.state === ESwapTxHistoryStatus.FAILED
+          ) {
+            let newApproveTx: ISwapApproveTransaction = {
+              ...preApproveTx,
+              blockNumber: txState.blockNumber,
+              status: ESwapApproveTransactionStatus.SUCCESS,
+            };
+            if (txState.state === ESwapTxHistoryStatus.FAILED) {
+              newApproveTx = {
+                ...preApproveTx,
+                txId: undefined,
+                status: ESwapApproveTransactionStatus.FAILED,
+              };
+            }
+            await this.setSpeedSwapApprovingTransaction(newApproveTx);
+          }
+        }
+      } else if (
+        preApproveTx &&
+        preApproveTx.status !== ESwapApproveTransactionStatus.PENDING
+      ) {
+        await this.setSpeedSwapApprovingTransaction({
+          ...preApproveTx,
+          status: ESwapApproveTransactionStatus.PENDING,
+        });
+      }
+    } catch (e) {
+      console.error(e);
+    } finally {
+      if (enableInterval) {
+        this.speedSwapApprovingIntervalCount += 1;
+        void this.speedSwapApprovingStateAction();
+      } else {
+        void this.cleanSpeedSwapApprovingInterval();
+        this.speedSwapApprovingIntervalCount = 0;
+      }
+    }
+  }
+
   @backgroundMethod()
   async approvingStateAction() {
     void this.cleanApprovingInterval();
@@ -1139,6 +1233,22 @@ export default class ServiceSwap extends ServiceBase {
     }
   }
 
+  @backgroundMethod()
+  async speedSwapApprovingStateAction() {
+    void this.cleanSpeedSwapApprovingInterval();
+    const approvingTransaction = await this.getSpeedSwapApprovingTransaction();
+    if (approvingTransaction && approvingTransaction.txId) {
+      this.speedSwapApprovingInterval = setTimeout(() => {
+        if (approvingTransaction.txId) {
+          void this.speedSwapApprovingStateRunSync(
+            approvingTransaction.fromToken.networkId,
+            approvingTransaction.txId,
+          );
+        }
+      }, swapSpeedSwapApprovingStateFetchInterval * (Math.floor(this.speedSwapApprovingIntervalCount / swapHistoryStateFetchRiceIntervalCount) + 1));
+    }
+  }
+
   // --- swap history
   @backgroundMethod()
   async fetchSwapHistoryListFromSimple() {
@@ -1996,6 +2106,7 @@ export default class ServiceSwap extends ServiceBase {
         provider: '',
         speedConfig: {
           slippage: 0.5,
+          spenderAddress: '',
           defaultTokens: [],
         },
         supportSpeedSwap: false,
diff --git a/packages/kit-bg/src/states/jotai/atoms/InAppNotification.ts b/packages/kit-bg/src/states/jotai/atoms/InAppNotification.ts
index 99d6f3a1351d..7ffa3b37e539 100644
--- a/packages/kit-bg/src/states/jotai/atoms/InAppNotification.ts
+++ b/packages/kit-bg/src/states/jotai/atoms/InAppNotification.ts
@@ -14,6 +14,8 @@ export type IInAppNotificationAtom = {
   swapLimitOrders: IFetchLimitOrderRes[];
   swapLimitOrdersLoading: boolean;
   swapApprovingTransaction: ISwapApproveTransaction | undefined;
+  speedSwapApprovingTransaction: ISwapApproveTransaction | undefined;
+  speedSwapApprovingLoading: boolean;
   swapRecentTokenPairs: { fromToken: ISwapToken; toToken: ISwapToken }[];
   swapPercentageInputStageShowForNative: boolean;
   swapProviderManager: ISwapProviderManager[];
@@ -29,10 +31,12 @@ export const { target: inAppNotificationAtom, use: useInAppNotificationAtom } =
       swapLimitOrders: [],
       swapLimitOrdersLoading: false,
       swapApprovingTransaction: undefined,
+      speedSwapApprovingTransaction: undefined,
       swapRecentTokenPairs: [],
       swapPercentageInputStageShowForNative: false,
       swapProviderManager: [],
       bridgeProviderManager: [],
       swapApprovingLoading: false,
+      speedSwapApprovingLoading: false,
     },
   });
diff --git a/packages/kit/src/provider/Container/InAppNotification/index.tsx b/packages/kit/src/provider/Container/InAppNotification/index.tsx
index 9b3b3fda2b85..68e9bc8f65c0 100644
--- a/packages/kit/src/provider/Container/InAppNotification/index.tsx
+++ b/packages/kit/src/provider/Container/InAppNotification/index.tsx
@@ -32,13 +32,27 @@ import { handleSwapNavigation } from '../../../views/Swap/hooks/useSwapNavigatio
 
 const InAppNotification = () => {
   const [
-    { swapHistoryPendingList, swapApprovingTransaction },
+    {
+      swapHistoryPendingList,
+      swapApprovingTransaction,
+      speedSwapApprovingTransaction,
+    },
     setInAppNotificationAtom,
   ] = useInAppNotificationAtom();
   const swapApprovingTransactionRef = useRef(swapApprovingTransaction);
   if (swapApprovingTransactionRef.current !== swapApprovingTransaction) {
     swapApprovingTransactionRef.current = swapApprovingTransaction;
   }
+
+  const speedSwapApprovingTransactionRef = useRef(
+    speedSwapApprovingTransaction,
+  );
+  if (
+    speedSwapApprovingTransactionRef.current !== speedSwapApprovingTransaction
+  ) {
+    speedSwapApprovingTransactionRef.current = speedSwapApprovingTransaction;
+  }
+
   const intl = useIntl();
   const navigation = useAppNavigation();
   useEffect(() => {
@@ -279,6 +293,75 @@ const InAppNotification = () => {
     approvingSuccessAction,
   ]);
 
+  // speed swap approving state
+  useEffect(() => {
+    if (
+      speedSwapApprovingTransaction?.txId &&
+      speedSwapApprovingTransaction?.status ===
+        ESwapApproveTransactionStatus.PENDING
+    ) {
+      void backgroundApiProxy.serviceSwap.speedSwapApprovingStateAction();
+    } else {
+      void backgroundApiProxy.serviceSwap.cleanSpeedSwapApprovingInterval();
+    }
+  }, [
+    speedSwapApprovingTransaction?.txId,
+    speedSwapApprovingTransaction?.status,
+  ]);
+
+  useEffect(() => {
+    if (
+      speedSwapApprovingTransaction?.status ===
+      ESwapApproveTransactionStatus.FAILED
+    ) {
+      setInAppNotificationAtom((prev) => ({
+        ...prev,
+        speedSwapApprovingLoading: false,
+        speedSwapApprovingTransaction: undefined,
+      }));
+      Toast.error({
+        title: intl.formatMessage({
+          id: ETranslations.swap_page_toast_approve_failed,
+        }),
+      });
+    } else if (
+      speedSwapApprovingTransaction?.status ===
+      ESwapApproveTransactionStatus.CANCEL
+    ) {
+      setInAppNotificationAtom((prev) => ({
+        ...prev,
+        speedSwapApprovingLoading: false,
+        speedSwapApprovingTransaction: undefined,
+      }));
+      Toast.error({
+        title: intl.formatMessage({
+          id: ETranslations.swap_page_toast_approve_canceled,
+        }),
+      });
+    } else if (
+      speedSwapApprovingTransaction?.status ===
+      ESwapApproveTransactionStatus.SUCCESS
+    ) {
+      if (
+        !(
+          speedSwapApprovingTransactionRef.current?.resetApproveValue &&
+          Number(speedSwapApprovingTransactionRef.current?.resetApproveValue) >
+            0
+        )
+      ) {
+        Toast.success({
+          title: intl.formatMessage({
+            id: ETranslations.swap_page_toast_approve_successful,
+          }),
+        });
+      } else {
+        appEventBus.emit(EAppEventBusNames.SwapSpeedApprovingReset, {
+          approvedSwapInfo: speedSwapApprovingTransactionRef.current,
+        });
+      }
+    }
+  }, [intl, setInAppNotificationAtom, speedSwapApprovingTransaction?.status]);
+
   return null;
 };
 
diff --git a/packages/kit/src/states/jotai/contexts/swap/atoms.ts b/packages/kit/src/states/jotai/contexts/swap/atoms.ts
index 9f7c8284e5a9..68ec6ce5a22b 100644
--- a/packages/kit/src/states/jotai/contexts/swap/atoms.ts
+++ b/packages/kit/src/states/jotai/contexts/swap/atoms.ts
@@ -334,10 +334,10 @@ export const {
     return 0;
   });
   return sortedList.map((p) => {
-    if (p.quoteId === recommendedSorted?.[0]?.quoteId && p.toAmount) {
+    if (p?.quoteId === recommendedSorted?.[0]?.quoteId && p.toAmount) {
       p.isBest = true;
     }
-    if (p.quoteId === receivedSorted?.[0]?.quoteId && p.toAmount) {
+    if (p?.quoteId === receivedSorted?.[0]?.quoteId && p.toAmount) {
       p.receivedBest = true;
     }
     if (p.quoteId === gasFeeSorted?.[0]?.quoteId && p.toAmount) {
diff --git a/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/SwapPanel.tsx b/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/SwapPanel.tsx
index e3f6bfb285e9..ffb72d193d54 100644
--- a/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/SwapPanel.tsx
+++ b/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/SwapPanel.tsx
@@ -44,10 +44,18 @@ export function SwapPanel() {
   const {
     speedSwapBuildTx,
     speedSwapBuildTxLoading,
-    cancelSpeedSwapBuildTx,
-    handleSpeedSwapBuildTxSuccess,
+    checkTokenApproveAllowance,
+    checkTokenAllowanceLoading,
+    speedSwapApproveHandler,
+    speedSwapApproveLoading,
   } = useSpeedSwapActions({
-    networkId: networkId ?? '',
+    token: {
+      networkId: 'evm--1',
+      contractAddress: '',
+      symbol: '',
+      decimals: 0,
+      logoURI: '',
+    },
     accountId: '',
   });
 
diff --git a/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx b/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx
index 80c03f2a83b0..796354d479db 100644
--- a/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx
+++ b/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx
@@ -1,40 +1,172 @@
-import { useCallback, useState } from 'react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
 
 import BigNumber from 'bignumber.js';
+import { useIntl } from 'react-intl';
 
+import { Dialog } from '@onekeyhq/components';
 import type { IEncodedTx } from '@onekeyhq/core/src/types';
 import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy';
 import { useSignatureConfirm } from '@onekeyhq/kit/src/hooks/useSignatureConfirm';
-import type { ITransferInfo } from '@onekeyhq/kit-bg/src/vaults/types';
+import {
+  useInAppNotificationAtom,
+  useSettingsPersistAtom,
+} from '@onekeyhq/kit-bg/src/states/jotai/atoms';
+import type {
+  IApproveInfo,
+  ITransferInfo,
+} from '@onekeyhq/kit-bg/src/vaults/types';
+import { presetNetworksMap } from '@onekeyhq/shared/src/config/presetNetworks';
+import {
+  EAppEventBusNames,
+  appEventBus,
+} from '@onekeyhq/shared/src/eventBus/appEventBus';
+import { ETranslations } from '@onekeyhq/shared/src/locale';
 import { toBigIntHex } from '@onekeyhq/shared/src/utils/numberUtils';
+import { equalTokenNoCaseSensitive } from '@onekeyhq/shared/src/utils/tokenUtils';
 import type {
+  ISwapApproveTransaction,
   ISwapTokenBase,
+  ISwapTxHistory,
   ISwapTxInfo,
 } from '@onekeyhq/shared/types/swap/types';
 import {
   EProtocolOfExchange,
+  ESwapApproveTransactionStatus,
   ESwapQuoteKind,
   ESwapTabSwitchType,
+  ESwapTxHistoryStatus,
 } from '@onekeyhq/shared/types/swap/types';
+import type { ISendTxOnSuccessData } from '@onekeyhq/shared/types/tx';
 
 export function useSpeedSwapActions({
-  networkId,
+  token,
   accountId,
 }: {
-  networkId: string;
+  token: ISwapTokenBase;
   accountId?: string;
 }) {
+  const intl = useIntl();
+  const [inAppNotificationAtom, setInAppNotificationAtom] =
+    useInAppNotificationAtom();
+  const [settingsAtom] = useSettingsPersistAtom();
   const [speedSwapBuildTxLoading, setSpeedSwapBuildTxLoading] = useState(false);
   const [checkTokenAllowanceLoading, setCheckTokenAllowanceLoading] =
     useState(false);
   const { navigationToTxConfirm } = useSignatureConfirm({
     accountId: accountId ?? '',
-    networkId,
+    networkId: token.networkId,
   });
 
-  const handleSpeedSwapBuildTxSuccess = useCallback(() => {
-    // todo handle build tx success
-  }, []);
+  // --- build tx
+
+  const handleSpeedSwapBuildTxSuccess = useCallback(
+    async (data: ISendTxOnSuccessData[]) => {
+      const transactionSignedInfo = data[0].signedTx;
+      const transactionDecodedInfo = data[0].decodedTx;
+      const txId = transactionSignedInfo.txid;
+      const { swapInfo } = transactionSignedInfo;
+      const {
+        totalFeeInNative,
+        totalFeeFiatValue,
+        networkId: txNetworkId,
+      } = transactionDecodedInfo;
+      if (swapInfo) {
+        const fromNetworkPreset = Object.values(presetNetworksMap).find(
+          (item) => item.id === swapInfo.sender.token.networkId,
+        );
+        const toNetworkPreset = Object.values(presetNetworksMap).find(
+          (item) => item.id === swapInfo.receiver.token.networkId,
+        );
+        if (
+          swapInfo &&
+          (swapInfo.protocol === EProtocolOfExchange.SWAP ||
+            swapInfo.swapBuildResData.result.isWrapped)
+        ) {
+          const useOrderId = false;
+          const swapHistoryItem: ISwapTxHistory = {
+            status: ESwapTxHistoryStatus.PENDING,
+            currency: settingsAtom.currencyInfo?.symbol,
+            accountInfo: {
+              sender: {
+                accountId: swapInfo.sender.accountInfo?.accountId,
+                networkId: swapInfo.sender.accountInfo?.networkId,
+              },
+              receiver: {
+                accountId: swapInfo.receiver.accountInfo?.accountId,
+                networkId: swapInfo.receiver.accountInfo?.networkId,
+              },
+            },
+            baseInfo: {
+              toAmount: swapInfo.receiver.amount,
+              fromAmount: swapInfo.sender.amount,
+              fromToken: swapInfo.sender.token,
+              toToken: swapInfo.receiver.token,
+              fromNetwork: {
+                networkId: fromNetworkPreset?.id ?? '',
+                name: fromNetworkPreset?.name ?? '',
+                symbol: fromNetworkPreset?.symbol ?? '',
+                logoURI: fromNetworkPreset?.logoURI ?? '',
+                shortcode: fromNetworkPreset?.shortcode ?? '',
+              },
+              toNetwork: {
+                networkId: toNetworkPreset?.id ?? '',
+                name: toNetworkPreset?.name ?? '',
+                symbol: toNetworkPreset?.symbol ?? '',
+                logoURI: toNetworkPreset?.logoURI ?? '',
+                shortcode: toNetworkPreset?.shortcode ?? '',
+              },
+            },
+            txInfo: {
+              txId,
+              useOrderId,
+              gasFeeFiatValue: totalFeeFiatValue,
+              gasFeeInNative: totalFeeInNative,
+              sender: swapInfo.accountAddress,
+              receiver: swapInfo.receivingAddress,
+            },
+            date: {
+              created: Date.now(),
+              updated: Date.now(),
+            },
+            swapInfo: {
+              instantRate: swapInfo.swapBuildResData.result?.instantRate ?? '0',
+              provider: swapInfo.swapBuildResData.result?.info,
+              socketBridgeScanUrl:
+                swapInfo.swapBuildResData.socketBridgeScanUrl,
+              oneKeyFee:
+                swapInfo.swapBuildResData.result?.fee?.percentageFee ?? 0,
+              protocolFee:
+                swapInfo.swapBuildResData.result?.fee?.protocolFees ?? 0,
+              otherFeeInfos:
+                swapInfo.swapBuildResData.result?.fee?.otherFeeInfos ?? [],
+              orderId: swapInfo.swapBuildResData.orderId,
+              supportUrl: swapInfo.swapBuildResData.result?.supportUrl,
+              orderSupportUrl:
+                swapInfo.swapBuildResData.result?.orderSupportUrl,
+              oneKeyFeeExtraInfo:
+                swapInfo.swapBuildResData.result?.oneKeyFeeExtraInfo,
+            },
+            ctx: swapInfo.swapBuildResData.ctx,
+          };
+          await backgroundApiProxy.serviceSwap.addSwapHistoryItem(
+            swapHistoryItem,
+          );
+          if (
+            swapInfo.sender.token.networkId ===
+            swapInfo.receiver.token.networkId
+          ) {
+            void backgroundApiProxy.serviceNotification.blockNotificationForTxId(
+              {
+                networkId: txNetworkId,
+                tx: txId,
+              },
+            );
+          }
+        }
+      }
+    },
+    [settingsAtom.currencyInfo?.symbol],
+  );
 
   const cancelSpeedSwapBuildTx = useCallback(() => {
     // todo cancel build tx
@@ -47,18 +179,19 @@ export function useSpeedSwapActions({
       amount,
       slippage,
       provider,
-      userAddress,
-      receivingAddress,
     }: {
       fromToken: ISwapTokenBase;
       toToken: ISwapTokenBase;
       amount: string;
       slippage: number;
       provider: string;
-      userAddress: string;
-      receivingAddress: string;
     }) => {
       setSpeedSwapBuildTxLoading(true);
+      const userAddress =
+        await backgroundApiProxy.serviceAccount.getAccountAddressForApi({
+          accountId: accountId ?? '',
+          networkId: fromToken.networkId,
+        });
       const buildRes =
         await backgroundApiProxy.serviceSwap.fetchBuildSpeedSwapTx({
           fromToken,
@@ -66,7 +199,7 @@ export function useSpeedSwapActions({
           fromTokenAmount: amount,
           provider,
           userAddress,
-          receivingAddress,
+          receivingAddress: userAddress,
           slippagePercentage: slippage,
           accountId,
           protocol: EProtocolOfExchange.SWAP,
@@ -117,7 +250,7 @@ export function useSpeedSwapActions({
           },
         },
         accountAddress: userAddress,
-        receivingAddress,
+        receivingAddress: userAddress,
         swapBuildResData: {
           ...buildRes,
           result: {
@@ -146,15 +279,75 @@ export function useSpeedSwapActions({
     ],
   );
 
+  // --- approve
+
+  const handleSpeedSwapApproveTxSuccess = useCallback(
+    async (data: ISendTxOnSuccessData[]) => {
+      if (data?.[0]) {
+        const transactionSignedInfo = data[0].signedTx;
+        const approveInfo = data[0].approveInfo;
+        const txId = transactionSignedInfo.txid;
+        if (
+          inAppNotificationAtom.speedSwapApprovingTransaction &&
+          !inAppNotificationAtom.speedSwapApprovingTransaction.resetApproveValue
+        ) {
+          void backgroundApiProxy.serviceNotification.blockNotificationForTxId({
+            networkId:
+              inAppNotificationAtom.speedSwapApprovingTransaction.fromToken
+                .networkId,
+            tx: txId,
+          });
+        }
+        setInAppNotificationAtom((prev) => {
+          if (prev.speedSwapApprovingTransaction) {
+            return {
+              ...prev,
+              speedSwapApprovingTransaction: {
+                ...prev.speedSwapApprovingTransaction,
+                txId,
+                resetApproveIsMax: !!approveInfo?.isMax,
+                ...(approveInfo
+                  ? {
+                      amount: approveInfo.amount,
+                    }
+                  : {}),
+              },
+            };
+          }
+          return prev;
+        });
+      }
+    },
+    [
+      inAppNotificationAtom.speedSwapApprovingTransaction,
+      setInAppNotificationAtom,
+    ],
+  );
+
+  const cancelSpeedSwapApproveTx = useCallback(() => {
+    setInAppNotificationAtom((prev) => {
+      if (prev.speedSwapApprovingTransaction) {
+        return {
+          ...prev,
+          speedSwapApprovingTransaction: {
+            ...prev.speedSwapApprovingTransaction,
+            status: ESwapApproveTransactionStatus.CANCEL,
+          },
+        };
+      }
+      return prev;
+    });
+  }, [setInAppNotificationAtom]);
+
   const checkTokenApproveAllowance = useCallback(
-    async (token: ISwapTokenBase, spenderAddress: string) => {
+    async (spenderAddress: string) => {
       setCheckTokenAllowanceLoading(true);
       const userAddress =
         await backgroundApiProxy.serviceAccount.getAccountAddressForApi({
           accountId: accountId ?? '',
           networkId: token.networkId,
         });
-      const allowance =
+      const approveRes =
         await backgroundApiProxy.serviceSwap.fetchApproveAllowance({
           networkId: token.networkId,
           tokenAddress: token.contractAddress,
@@ -162,17 +355,203 @@ export function useSpeedSwapActions({
           walletAddress: userAddress,
         });
       setCheckTokenAllowanceLoading(false);
-      return allowance;
+      return approveRes;
     },
-    [accountId],
+    [accountId, token.contractAddress, token.networkId],
   );
 
+  const approveRun = useCallback(
+    async ({
+      provider,
+      spenderAddress,
+      amount,
+      isReset,
+      fromToken,
+      toToken,
+    }: {
+      provider: string;
+      spenderAddress: string;
+      amount: string;
+      isReset?: boolean;
+      fromToken: ISwapTokenBase;
+      toToken: ISwapTokenBase;
+    }) => {
+      try {
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingLoading: true,
+        }));
+        const userAddress =
+          await backgroundApiProxy.serviceAccount.getAccountAddressForApi({
+            accountId: accountId ?? '',
+            networkId: token.networkId,
+          });
+        const approveInfo: IApproveInfo = {
+          owner: userAddress,
+          spender: spenderAddress,
+          amount: isReset ? '0' : amount,
+          isMax: !isReset,
+          tokenInfo: {
+            ...fromToken,
+            isNative: !!fromToken.isNative,
+            address: fromToken.contractAddress,
+            name: fromToken.name ?? fromToken.symbol,
+          },
+          swapApproveRes: undefined,
+        };
+        await navigationToTxConfirm({
+          approvesInfo: [approveInfo],
+          isInternalSwap: true,
+          onSuccess: handleSpeedSwapApproveTxSuccess,
+          onCancel: cancelSpeedSwapApproveTx,
+        });
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingTransaction: {
+            swapType: ESwapTabSwitchType.SWAP,
+            protocol: EProtocolOfExchange.SWAP,
+            provider,
+            providerName: provider,
+            unSupportReceiveAddressDifferent: false,
+            fromToken,
+            toToken,
+            amount,
+            toAmount: amount,
+            useAddress: userAddress,
+            spenderAddress,
+            status: ESwapApproveTransactionStatus.PENDING,
+            kind: ESwapQuoteKind.SELL,
+            resetApproveValue: isReset ? '0' : amount,
+            resetApproveIsMax: !isReset,
+          },
+        }));
+      } catch (e) {
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingLoading: false,
+        }));
+      }
+    },
+    [
+      accountId,
+      setInAppNotificationAtom,
+      token,
+      navigationToTxConfirm,
+      handleSpeedSwapApproveTxSuccess,
+      cancelSpeedSwapApproveTx,
+    ],
+  );
+
+  const speedSwapApproveHandler = useCallback(
+    async ({
+      provider,
+      spenderAddress,
+      amount,
+      isReset,
+      fromToken,
+      toToken,
+    }: {
+      provider: string;
+      spenderAddress: string;
+      amount: string;
+      isReset?: boolean;
+      fromToken: ISwapTokenBase;
+      toToken: ISwapTokenBase;
+    }) => {
+      if (isReset) {
+        Dialog.confirm({
+          onConfirmText: intl.formatMessage({
+            id: ETranslations.global_continue,
+          }),
+          onConfirm: () => {
+            void approveRun({
+              provider,
+              spenderAddress,
+              amount,
+              isReset,
+              fromToken,
+              toToken,
+            });
+          },
+          showCancelButton: true,
+          title: intl.formatMessage({
+            id: ETranslations.swap_page_provider_approve_usdt_dialog_title,
+          }),
+          description: intl.formatMessage({
+            id: ETranslations.swap_page_provider_approve_usdt_dialog_content,
+          }),
+          icon: 'ErrorOutline',
+        });
+      } else {
+        void approveRun({
+          provider,
+          spenderAddress,
+          amount,
+          isReset,
+          fromToken,
+          toToken,
+        });
+      }
+    },
+    [approveRun, intl],
+  );
+  const speedSwapApproveLoading = useMemo(() => {
+    const speedSwapApproveTransaction =
+      inAppNotificationAtom.speedSwapApprovingTransaction;
+    if (
+      speedSwapApproveTransaction &&
+      inAppNotificationAtom.speedSwapApprovingLoading &&
+      (equalTokenNoCaseSensitive({
+        token1: speedSwapApproveTransaction.fromToken,
+        token2: token,
+      }) ||
+        equalTokenNoCaseSensitive({
+          token1: speedSwapApproveTransaction.toToken,
+          token2: token,
+        }))
+    ) {
+      return true;
+    }
+    return false;
+  }, [
+    inAppNotificationAtom.speedSwapApprovingLoading,
+    inAppNotificationAtom.speedSwapApprovingTransaction,
+    token,
+  ]);
+
+  const handleSwapSpeedApprovingReset = useCallback(
+    ({ approvedSwapInfo }: { approvedSwapInfo: ISwapApproveTransaction }) => {
+      if (approvedSwapInfo.resetApproveValue) {
+        void approveRun({
+          provider: approvedSwapInfo.provider,
+          spenderAddress: approvedSwapInfo.spenderAddress,
+          amount: approvedSwapInfo.resetApproveValue,
+          isReset: false,
+          fromToken: approvedSwapInfo.fromToken,
+          toToken: approvedSwapInfo.toToken,
+        });
+      }
+    },
+    [approveRun],
+  );
+
+  useEffect(() => {
+    appEventBus.off(
+      EAppEventBusNames.SwapSpeedApprovingReset,
+      handleSwapSpeedApprovingReset,
+    );
+    appEventBus.on(
+      EAppEventBusNames.SwapSpeedApprovingReset,
+      handleSwapSpeedApprovingReset,
+    );
+  }, [handleSwapSpeedApprovingReset]);
+
   return {
     speedSwapBuildTx,
     speedSwapBuildTxLoading,
-    cancelSpeedSwapBuildTx,
-    handleSpeedSwapBuildTxSuccess,
     checkTokenApproveAllowance,
     checkTokenAllowanceLoading,
+    speedSwapApproveHandler,
+    speedSwapApproveLoading,
   };
 }
diff --git a/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapInit.tsx b/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapInit.tsx
index 061d60301268..f0a99e53a683 100644
--- a/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapInit.tsx
+++ b/packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapInit.tsx
@@ -16,6 +16,7 @@ export function useSpeedSwapInit(networkId: string) {
       initResult: {
         provider: '',
         speedConfig: {
+          spenderAddress: '',
           slippage: 0.5,
           defaultTokens: [],
         },
diff --git a/packages/kit/src/views/Swap/hooks/useSwapQuote.ts b/packages/kit/src/views/Swap/hooks/useSwapQuote.ts
index 3e306c1a8990..7476b85d4f97 100644
--- a/packages/kit/src/views/Swap/hooks/useSwapQuote.ts
+++ b/packages/kit/src/views/Swap/hooks/useSwapQuote.ts
@@ -463,7 +463,7 @@ export function useSwapQuote() {
     }) => {
       setSwapManualSelectQuoteProviders({
         protocol: data.approvedSwapInfo.protocol,
-        quoteId: data.approvedSwapInfo.quoteId,
+        quoteId: data.approvedSwapInfo?.quoteId,
         info: {
           provider: data.approvedSwapInfo.provider,
           providerName: data.approvedSwapInfo.providerName,
diff --git a/packages/kit/src/views/Swap/pages/components/SwapMainLand.tsx b/packages/kit/src/views/Swap/pages/components/SwapMainLand.tsx
index 98745c012bcd..06be9afb8f4a 100644
--- a/packages/kit/src/views/Swap/pages/components/SwapMainLand.tsx
+++ b/packages/kit/src/views/Swap/pages/components/SwapMainLand.tsx
@@ -282,7 +282,7 @@ const SwapMainLoad = ({ swapInitParams, pageType }: ISwapMainLoadProps) => {
           {alerts.states.length > 0 &&
           !quoteLoading &&
           !quoteEventFetching &&
-          alerts.quoteId === (quoteResult?.quoteId ?? '') ? (
+          alerts?.quoteId === (quoteResult?.quoteId ?? '') ? (
             <SwapAlertContainer alerts={alerts.states} />
           ) : null}
           <SwapRecentTokenPairsGroup
diff --git a/packages/shared/src/eventBus/appEventBus.ts b/packages/shared/src/eventBus/appEventBus.ts
index 301b458ef033..0c8e58456986 100644
--- a/packages/shared/src/eventBus/appEventBus.ts
+++ b/packages/shared/src/eventBus/appEventBus.ts
@@ -60,6 +60,9 @@ export interface IAppEventBusPayload {
     approvedSwapInfo: ISwapApproveTransaction;
     enableFilled?: boolean;
   };
+  [EAppEventBusNames.SwapSpeedApprovingReset]: {
+    approvedSwapInfo: ISwapApproveTransaction;
+  };
   [EAppEventBusNames.WalletRemove]: {
     walletId: string;
   };
diff --git a/packages/shared/src/eventBus/appEventBusNames.ts b/packages/shared/src/eventBus/appEventBusNames.ts
index 71267b267fbd..07e107e85fe2 100644
--- a/packages/shared/src/eventBus/appEventBusNames.ts
+++ b/packages/shared/src/eventBus/appEventBusNames.ts
@@ -64,6 +64,7 @@ export enum EAppEventBusNames {
   ShowSystemDiskFullWarning = 'ShowSystemDiskFullWarning',
   SwapTxHistoryStatusUpdate = 'SwapTxHistoryStatusUpdate',
   SwapApprovingSuccess = 'SwapApprovingSuccess',
+  SwapSpeedApprovingReset = 'SwapSpeedApprovingReset',
   AddedCustomNetwork = 'AddedCustomNetwork',
   ShowFindInWebPage = 'ShowFindInWebPage',
   ChangeTokenDetailTabVerticalScrollEnabled = 'ChangeTokenDetailTabVerticalScrollEnabled',
diff --git a/packages/shared/types/swap/SwapProvider.constants.ts b/packages/shared/types/swap/SwapProvider.constants.ts
index 1500a1f8eae2..5e3f5584bab9 100644
--- a/packages/shared/types/swap/SwapProvider.constants.ts
+++ b/packages/shared/types/swap/SwapProvider.constants.ts
@@ -51,6 +51,11 @@ export const swapApprovingStateFetchInterval = timerUtils.getTimeDurationMs({
   seconds: 2,
 });
 
+export const swapSpeedSwapApprovingStateFetchInterval =
+  timerUtils.getTimeDurationMs({
+    seconds: 1,
+  });
+
 export const swapHistoryStateFetchInterval = timerUtils.getTimeDurationMs({
   seconds: 3,
 });
diff --git a/packages/shared/types/swap/types.ts b/packages/shared/types/swap/types.ts
index a61a14d88d32..dd0917bb50a0 100644
--- a/packages/shared/types/swap/types.ts
+++ b/packages/shared/types/swap/types.ts
@@ -246,7 +246,7 @@ export interface ISwapApproveTransaction {
   unSupportReceiveAddressDifferent?: boolean;
   provider: string;
   providerName: string;
-  quoteId: string;
+  quoteId?: string;
   useAddress: string;
   spenderAddress: string;
   amount: string;
@@ -508,6 +508,14 @@ export interface ISwapState {
   isRefreshQuote?: boolean;
 }
 
+export interface ISwapApproveAllowanceResponse {
+  isApproved: boolean;
+  allowanceTarget: string;
+  shouldApproveAmount: string;
+  approvedAmount: string;
+  shouldResetApprove?: boolean;
+}
+
 export interface ISwapCheckWarningDef {
   swapFromAddressInfo: ReturnType<typeof useSwapAddressInfo>;
   swapToAddressInfo: ReturnType<typeof useSwapAddressInfo>;
@@ -806,6 +814,7 @@ export interface ISpeedSwapConfig {
   provider: string;
   speedConfig: {
     slippage: number;
+    spenderAddress: string;
     defaultTokens: ISwapTokenBase[];
   };
   supportSpeedSwap: boolean;


Reviews:
user: login: limichange
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T08:49:10Z
user: login: weatherstar
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T08:51:58Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 10**

<details>
<summary>🔭 Outside diff range comments (2)</summary><blockquote>

<details>
<summary>packages/kit-bg/src/services/ServiceSwap.ts (2)</summary><blockquote>

`965-997`: _🛠️ Refactor suggestion_

**Add an explicit return type for `fetchApproveAllowance`**

`fetchApproveAllowance` now parses the server response into `ISwapApproveAllowanceResponse`, but because the function signature lacks a return-type annotation, callers still get `any`.  
Exposing the correct type will give IDE/intellisense support and prevent silent misuse.

```diff
-  async fetchApproveAllowance({
+  async fetchApproveAllowance({
     ...
-  }) {
+  }): Promise<ISwapApproveAllowanceResponse | undefined> {
```

No other changes are needed—the generic typed GET already guarantees `data.data` has the desired shape.

---

`1006-1050`: _🧹 Nitpick (assertive)_

**Missing setter for `speedSwapApprovingLoading`**

The new helpers mirror the existing swap approval API, yet there is no `updateSpeedSwapApprovingLoading` companion to `updateSwapApprovingLoading`.  
UI code that only toggles `speedSwapApprovingLoading` via that dedicated method will now have to poke the atom directly (or risk inconsistent UX).

Either:

1. Add a symmetric method, or  
2. Remove both specialised setters and always rely on `setSpeedSwapApprovingTransaction` / `setApprovingTransaction` (they already flip the flag).

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9a2902a0ec10199a2048caf0ac4c21a51d5fb846 and a1351533d81fae7003fdb1436ad1b41e358f2b04.

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `packages/kit-bg/src/services/ServiceSwap.ts` (11 hunks)
* `packages/kit-bg/src/states/jotai/atoms/InAppNotification.ts` (2 hunks)
* `packages/kit/src/provider/Container/InAppNotification/index.tsx` (2 hunks)
* `packages/kit/src/states/jotai/contexts/swap/atoms.ts` (1 hunks)
* `packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/SwapPanel.tsx` (1 hunks)
* `packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx` (4 hunks)
* `packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapInit.tsx` (1 hunks)
* `packages/kit/src/views/Swap/hooks/useSwapQuote.ts` (1 hunks)
* `packages/kit/src/views/Swap/pages/components/SwapMainLand.tsx` (1 hunks)
* `packages/shared/src/eventBus/appEventBus.ts` (1 hunks)
* `packages/shared/src/eventBus/appEventBusNames.ts` (1 hunks)
* `packages/shared/types/swap/SwapProvider.constants.ts` (1 hunks)
* `packages/shared/types/swap/types.ts` (3 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (6)</summary>

<details>
<summary>packages/shared/src/eventBus/appEventBus.ts (1)</summary><blockquote>

<details>
<summary>packages/shared/types/swap/types.ts (1)</summary>

* `ISwapApproveTransaction` (241-260)

</details>

</blockquote></details>
<details>
<summary>packages/kit-bg/src/states/jotai/atoms/InAppNotification.ts (1)</summary><blockquote>

<details>
<summary>packages/shared/types/swap/types.ts (1)</summary>

* `ISwapApproveTransaction` (241-260)

</details>

</blockquote></details>
<details>
<summary>packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/SwapPanel.tsx (1)</summary><blockquote>

<details>
<summary>packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx (1)</summary>

* `useSpeedSwapActions` (41-557)

</details>

</blockquote></details>
<details>
<summary>packages/kit/src/provider/Container/InAppNotification/index.tsx (3)</summary><blockquote>

<details>
<summary>packages/components/src/actions/Toast/index.tsx (1)</summary>

* `Toast` (237-306)

</details>
<details>
<summary>apps/desktop/app/i18n.ts (1)</summary>

* `ETranslations` (62-62)

</details>
<details>
<summary>packages/shared/src/eventBus/appEventBus.ts (2)</summary>

* `appEventBus` (445-445)
* `EAppEventBusNames` (445-445)

</details>

</blockquote></details>
<details>
<summary>packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx (9)</summary><blockquote>

<details>
<summary>packages/shared/types/swap/types.ts (3)</summary>

* `ISwapTokenBase` (118-127)
* `ISwapTxHistory` (716-768)
* `ISwapApproveTransaction` (241-260)

</details>
<details>
<summary>packages/kit/src/hooks/useSignatureConfirm.ts (1)</summary>

* `useSignatureConfirm` (360-360)

</details>
<details>
<summary>packages/shared/types/tx.ts (1)</summary>

* `ISendTxOnSuccessData` (248-253)

</details>
<details>
<summary>packages/shared/src/config/presetNetworks.ts (1)</summary>

* `presetNetworksMap` (2714-2829)

</details>
<details>
<summary>packages/kit-bg/src/vaults/types.ts (1)</summary>

* `IApproveInfo` (479-486)

</details>
<details>
<summary>packages/components/src/composite/Dialog/index.tsx (1)</summary>

* `Dialog` (647-663)

</details>
<details>
<summary>apps/desktop/app/i18n.ts (1)</summary>

* `ETranslations` (62-62)

</details>
<details>
<summary>packages/shared/src/utils/tokenUtils.ts (1)</summary>

* `equalTokenNoCaseSensitive` (270-289)

</details>
<details>
<summary>packages/shared/src/eventBus/appEventBus.ts (2)</summary>

* `appEventBus` (445-445)
* `EAppEventBusNames` (445-445)

</details>

</blockquote></details>
<details>
<summary>packages/kit-bg/src/services/ServiceSwap.ts (3)</summary><blockquote>

<details>
<summary>packages/shared/types/swap/types.ts (3)</summary>

* `IFetchResponse` (677-681)
* `ISwapApproveAllowanceResponse` (511-517)
* `ISwapApproveTransaction` (241-260)

</details>
<details>
<summary>packages/shared/src/background/backgroundDecorators.ts (1)</summary>

* `backgroundMethod` (218-218)

</details>
<details>
<summary>packages/shared/types/swap/SwapProvider.constants.ts (2)</summary>

* `swapSpeedSwapApprovingStateFetchInterval` (54-57)
* `swapHistoryStateFetchRiceIntervalCount` (63-63)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>packages/kit-bg/src/services/ServiceSwap.ts</summary>

[error] 1240-1240: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (3)</summary>

* GitHub Check: lint (20.x)
* GitHub Check: unittest (20.x)
* GitHub Check: Analyze (javascript-typescript)

</details>

<details>
<summary>🔇 Additional comments (15)</summary><blockquote>

<details>
<summary>packages/shared/types/swap/SwapProvider.constants.ts (1)</summary>

`54-57`: **Appropriate polling interval for speed swap approval state**

The new constant `swapSpeedSwapApprovingStateFetchInterval` sets a 1-second polling interval for speed swap approvals. This is faster than regular swap approvals (2 seconds) and history state fetching (3 seconds), providing more responsive feedback for speed swap operations.

</details>
<details>
<summary>packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapInit.tsx (1)</summary>

`19-19`: **Properly initialized spenderAddress property**

The addition of `spenderAddress` initialization matches the extended `ISpeedSwapConfig` interface requirements. This property will store the contract address authorized to spend tokens during speed swap operations.

</details>
<details>
<summary>packages/shared/src/eventBus/appEventBusNames.ts (1)</summary>

`67-67`: **Added event for speed swap approval reset**

The new `SwapSpeedApprovingReset` event follows existing naming patterns and enables communication between components when speed swap approvals need to be reset.

</details>
<details>
<summary>packages/shared/src/eventBus/appEventBus.ts (1)</summary>

`63-65`: **Properly typed event payload for speed swap reset**

The payload type for `SwapSpeedApprovingReset` includes the necessary `approvedSwapInfo` field of type `ISwapApproveTransaction`, giving components all required data to handle approval resets.

</details>
<details>
<summary>packages/kit/src/views/Swap/hooks/useSwapQuote.ts (1)</summary>

`466-466`: **Added null-safety with optional chaining**

Using optional chaining (`?.`) on `data.approvedSwapInfo.quoteId` prevents potential runtime errors when `approvedSwapInfo` is undefined or null. This defensive coding practice enhances app stability.

</details>
<details>
<summary>packages/kit/src/views/Swap/pages/components/SwapMainLand.tsx (1)</summary>

`285-285`: **Added null safety with optional chaining**

The optional chaining operator prevents potential runtime errors when `alerts` is undefined or null. Good defensive programming practice.

</details>
<details>
<summary>packages/kit-bg/src/states/jotai/atoms/InAppNotification.ts (3)</summary>

`17-18`: **New state properties for speed swap approval**

Added new state properties to track speed swap approval transaction and loading state, separate from the regular swap approval flow.

---

`34-35`: **Initialized speedSwapApprovingTransaction state**

Properly initialized the new state property to match the pattern of the existing swap approval transaction state.

---

`40-41`: **Initialized speedSwapApprovingLoading state**

Correctly set the initial loading state to false, consistent with other loading flags in this atom.

</details>
<details>
<summary>packages/kit/src/states/jotai/contexts/swap/atoms.ts (2)</summary>

`337-338`: **Added null safety with optional chaining**

The optional chaining operator prevents potential runtime errors when `p` is undefined or null before checking its `quoteId`.

---

`340-341`: **Added null safety with optional chaining**

Similar to the previous change, this adds defensive programming to prevent crashes when accessing properties on potentially null objects.

</details>
<details>
<summary>packages/shared/types/swap/types.ts (3)</summary>

`249-249`: **Made quoteId optional in ISwapApproveTransaction**

This change aligns with the optional chaining used when accessing this property elsewhere in the codebase, improving type safety.

---

`511-517`: **Added typed interface for approve allowance responses**

Created a structured interface for allowance-related API responses, improving type safety and code readability.

---

`817-817`: **Added required spenderAddress to ISpeedSwapConfig**

The speed swap configuration now requires a spender address, which is needed for the approval transaction.

</details>
<details>
<summary>packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx (1)</summary>

`499-516`: **Loading check relies on both directions – simplify**

Current logic checks `token == fromToken OR token == toToken`.  
Approvals are always for *fromToken*; the `toToken` comparison is dead code.

Remove the second branch for clarity and micro-perf.

[ suggest_nitpick ]

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T08:52:59Z
user: login: huhuanming
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T09:07:57Z

Review comments:
diff_hunk: @@ -44,10 +44,18 @@ export function SwapPanel() {
   const {
     speedSwapBuildTx,
     speedSwapBuildTxLoading,
-    cancelSpeedSwapBuildTx,
-    handleSpeedSwapBuildTxSuccess,
+    checkTokenApproveAllowance,
+    checkTokenAllowanceLoading,
+    speedSwapApproveHandler,
+    speedSwapApproveLoading,
   } = useSpeedSwapActions({
-    networkId: networkId ?? '',
+    token: {
+      networkId: 'evm--1',
+      contractAddress: '',
+      symbol: '',
+      decimals: 0,
+      logoURI: '',
+    },
     accountId: '',
   });
path: packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/SwapPanel.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Avoid hard-coding the token & account – pass real values instead**

`useSpeedSwapActions()` now receives a dummy token (`evm--1 / '' / 0`) and an empty `accountId`.  
Several internal calls (e.g. `equalTokenNoCaseSensitive`, address look-ups) rely on those fields being correct.  
Running with empty data will:

1. Make the allowance check always miss – approvals will never match.
2. Break account–specific address look-ups, returning undefined and throwing inside the hook.
3. Hide real loading state because the comparison token ≠ paymentToken.

Pass the current `paymentToken` and the real `accountId` you already have in `swapPanel`:

```diff
-  } = useSpeedSwapActions({
-    token: {
-      networkId: 'evm--1',
-      contractAddress: '',
-      symbol: '',
-      decimals: 0,
-      logoURI: '',
-    },
-    accountId: '',
-  });
+  } = useSpeedSwapActions({
+    token: paymentToken,
+    accountId: accountId ?? '',
+  });
```

That keeps the approval logic in sync with the UI selection.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
  } = useSpeedSwapActions({
    token: paymentToken,
    accountId: accountId ?? '',
  });
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/SwapPanel.tsx
around lines 52 to 60, avoid hard-coding the token and accountId with dummy
values. Instead, pass the actual current paymentToken and the real accountId
from the swapPanel component to useSpeedSwapActions(). This ensures that
internal calls relying on these fields work correctly, approval checks are
accurate, and the UI loading state reflects the real token selection.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T08:52:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -279,6 +293,75 @@ const InAppNotification = () => {
     approvingSuccessAction,
   ]);
 
+  // speed swap approving state
+  useEffect(() => {
+    if (
+      speedSwapApprovingTransaction?.txId &&
+      speedSwapApprovingTransaction?.status ===
+        ESwapApproveTransactionStatus.PENDING
+    ) {
+      void backgroundApiProxy.serviceSwap.speedSwapApprovingStateAction();
+    } else {
+      void backgroundApiProxy.serviceSwap.cleanSpeedSwapApprovingInterval();
+    }
+  }, [
+    speedSwapApprovingTransaction?.txId,
+    speedSwapApprovingTransaction?.status,
+  ]);
+
+  useEffect(() => {
+    if (
+      speedSwapApprovingTransaction?.status ===
+      ESwapApproveTransactionStatus.FAILED
+    ) {
+      setInAppNotificationAtom((prev) => ({
+        ...prev,
+        speedSwapApprovingLoading: false,
+        speedSwapApprovingTransaction: undefined,
+      }));
+      Toast.error({
+        title: intl.formatMessage({
+          id: ETranslations.swap_page_toast_approve_failed,
+        }),
+      });
+    } else if (
+      speedSwapApprovingTransaction?.status ===
+      ESwapApproveTransactionStatus.CANCEL
+    ) {
+      setInAppNotificationAtom((prev) => ({
+        ...prev,
+        speedSwapApprovingLoading: false,
+        speedSwapApprovingTransaction: undefined,
+      }));
+      Toast.error({
+        title: intl.formatMessage({
+          id: ETranslations.swap_page_toast_approve_canceled,
+        }),
+      });
+    } else if (
+      speedSwapApprovingTransaction?.status ===
+      ESwapApproveTransactionStatus.SUCCESS
+    ) {
+      if (
+        !(
+          speedSwapApprovingTransactionRef.current?.resetApproveValue &&
+          Number(speedSwapApprovingTransactionRef.current?.resetApproveValue) >
+            0
+        )
+      ) {
+        Toast.success({
+          title: intl.formatMessage({
+            id: ETranslations.swap_page_toast_approve_successful,
+          }),
+        });
+      } else {
+        appEventBus.emit(EAppEventBusNames.SwapSpeedApprovingReset, {
+          approvedSwapInfo: speedSwapApprovingTransactionRef.current,
+        });
+      }
+    }
+  }, [intl, setInAppNotificationAtom, speedSwapApprovingTransaction?.status]);
path: packages/kit/src/provider/Container/InAppNotification/index.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**SUCCESS path with `resetApproveValue > 0` may create an infinite loop**

When `resetApproveValue > 0` you emit `SwapSpeedApprovingReset`, which triggers
`approveRun` again.  
The new approval transaction you dispatch also sets `resetApproveValue`
to a **non-zero** amount (see hook).  
After it succeeds you emit the same event again → endless cycle.

Confirm the intended flow.  
If the reset-to-zero step is the only one that should emit, gate the
event by `isReset === true` *before* saving `resetApproveValue` in the hook (see comment in hook file).

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/kit/src/provider/Container/InAppNotification/index.tsx around lines
342 to 363, the code emits the SwapSpeedApprovingReset event whenever
resetApproveValue is greater than zero, causing an infinite loop because the
event triggers approveRun which resets resetApproveValue to a non-zero value
again. To fix this, add a condition to check if isReset is true before emitting
the event, ensuring that only the reset-to-zero step triggers the event and
breaks the cycle. Adjust the logic to gate the event emission based on isReset
status before saving resetApproveValue in the related hook.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T08:52:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -279,6 +293,75 @@ const InAppNotification = () => {
     approvingSuccessAction,
   ]);
 
+  // speed swap approving state
+  useEffect(() => {
+    if (
+      speedSwapApprovingTransaction?.txId &&
+      speedSwapApprovingTransaction?.status ===
+        ESwapApproveTransactionStatus.PENDING
+    ) {
+      void backgroundApiProxy.serviceSwap.speedSwapApprovingStateAction();
+    } else {
+      void backgroundApiProxy.serviceSwap.cleanSpeedSwapApprovingInterval();
+    }
+  }, [
path: packages/kit/src/provider/Container/InAppNotification/index.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Stop the approval poller when status resolves**

`cleanSpeedSwapApprovingInterval()` is called only when `txId` is absent *or* status ≠ `PENDING`.  
But the local loading flag (`speedSwapApprovingLoading`) is never cleared when the tx finishes with **SUCCESS**.  
Result: the UI spinner keeps spinning even though the toast says “success”.

Add the same reset you already use for FAILED / CANCEL:

```diff
       speedSwapApprovingTransaction?.status ===
       ESwapApproveTransactionStatus.SUCCESS
     ) {
+      setInAppNotificationAtom((prev) => ({
+        ...prev,
+        speedSwapApprovingLoading: false,
+      }));
```

Keeps visual state consistent with reality.



> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/kit/src/provider/Container/InAppNotification/index.tsx around lines
296 to 307, the function cleanSpeedSwapApprovingInterval() is only called when
txId is missing or status is not PENDING, but the local loading flag
speedSwapApprovingLoading is not cleared when the transaction status changes to
SUCCESS, causing the UI spinner to keep spinning incorrectly. To fix this,
modify the useEffect to also call cleanSpeedSwapApprovingInterval() and reset
the loading flag when the status is SUCCESS, similar to how it is handled for
FAILED or CANCEL statuses, ensuring the visual state matches the actual
transaction state.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T08:52:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -146,33 +279,279 @@ export function useSpeedSwapActions({
     ],
   );
 
+  // --- approve
+
+  const handleSpeedSwapApproveTxSuccess = useCallback(
+    async (data: ISendTxOnSuccessData[]) => {
+      if (data?.[0]) {
+        const transactionSignedInfo = data[0].signedTx;
+        const approveInfo = data[0].approveInfo;
+        const txId = transactionSignedInfo.txid;
+        if (
+          inAppNotificationAtom.speedSwapApprovingTransaction &&
+          !inAppNotificationAtom.speedSwapApprovingTransaction.resetApproveValue
+        ) {
+          void backgroundApiProxy.serviceNotification.blockNotificationForTxId({
+            networkId:
+              inAppNotificationAtom.speedSwapApprovingTransaction.fromToken
+                .networkId,
+            tx: txId,
+          });
+        }
+        setInAppNotificationAtom((prev) => {
+          if (prev.speedSwapApprovingTransaction) {
+            return {
+              ...prev,
+              speedSwapApprovingTransaction: {
+                ...prev.speedSwapApprovingTransaction,
+                txId,
+                resetApproveIsMax: !!approveInfo?.isMax,
+                ...(approveInfo
+                  ? {
+                      amount: approveInfo.amount,
+                    }
+                  : {}),
+              },
+            };
+          }
+          return prev;
+        });
+      }
+    },
+    [
+      inAppNotificationAtom.speedSwapApprovingTransaction,
+      setInAppNotificationAtom,
+    ],
+  );
+
+  const cancelSpeedSwapApproveTx = useCallback(() => {
+    setInAppNotificationAtom((prev) => {
+      if (prev.speedSwapApprovingTransaction) {
+        return {
+          ...prev,
+          speedSwapApprovingTransaction: {
+            ...prev.speedSwapApprovingTransaction,
+            status: ESwapApproveTransactionStatus.CANCEL,
+          },
+        };
+      }
+      return prev;
+    });
+  }, [setInAppNotificationAtom]);
+
   const checkTokenApproveAllowance = useCallback(
-    async (token: ISwapTokenBase, spenderAddress: string) => {
+    async (spenderAddress: string) => {
       setCheckTokenAllowanceLoading(true);
       const userAddress =
         await backgroundApiProxy.serviceAccount.getAccountAddressForApi({
           accountId: accountId ?? '',
           networkId: token.networkId,
         });
-      const allowance =
+      const approveRes =
         await backgroundApiProxy.serviceSwap.fetchApproveAllowance({
           networkId: token.networkId,
           tokenAddress: token.contractAddress,
           spenderAddress,
           walletAddress: userAddress,
         });
       setCheckTokenAllowanceLoading(false);
-      return allowance;
+      return approveRes;
     },
-    [accountId],
+    [accountId, token.contractAddress, token.networkId],
   );
 
+  const approveRun = useCallback(
+    async ({
+      provider,
+      spenderAddress,
+      amount,
+      isReset,
+      fromToken,
+      toToken,
+    }: {
+      provider: string;
+      spenderAddress: string;
+      amount: string;
+      isReset?: boolean;
+      fromToken: ISwapTokenBase;
+      toToken: ISwapTokenBase;
+    }) => {
+      try {
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingLoading: true,
+        }));
+        const userAddress =
+          await backgroundApiProxy.serviceAccount.getAccountAddressForApi({
+            accountId: accountId ?? '',
+            networkId: token.networkId,
+          });
+        const approveInfo: IApproveInfo = {
+          owner: userAddress,
+          spender: spenderAddress,
+          amount: isReset ? '0' : amount,
+          isMax: !isReset,
+          tokenInfo: {
+            ...fromToken,
+            isNative: !!fromToken.isNative,
+            address: fromToken.contractAddress,
+            name: fromToken.name ?? fromToken.symbol,
+          },
+          swapApproveRes: undefined,
+        };
+        await navigationToTxConfirm({
+          approvesInfo: [approveInfo],
+          isInternalSwap: true,
+          onSuccess: handleSpeedSwapApproveTxSuccess,
+          onCancel: cancelSpeedSwapApproveTx,
+        });
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingTransaction: {
+            swapType: ESwapTabSwitchType.SWAP,
+            protocol: EProtocolOfExchange.SWAP,
+            provider,
+            providerName: provider,
+            unSupportReceiveAddressDifferent: false,
+            fromToken,
+            toToken,
+            amount,
+            toAmount: amount,
+            useAddress: userAddress,
+            spenderAddress,
+            status: ESwapApproveTransactionStatus.PENDING,
+            kind: ESwapQuoteKind.SELL,
+            resetApproveValue: isReset ? '0' : amount,
+            resetApproveIsMax: !isReset,
+          },
+        }));
path: packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`resetApproveValue` logic is inverted – causes endless re-approve loop**

You set  
```ts
resetApproveValue: isReset ? '0' : amount;
```
So for the **normal** approval (`isReset === false`) you store a non-zero
`resetApproveValue`.  
In `InAppNotification` a non-zero value triggers `SwapSpeedApprovingReset`,  
which calls `approveRun` again, generating a fresh tx that again stores
a non-zero value… loop.

Flip the assignment:

```diff
- resetApproveValue: isReset ? '0' : amount,
+ resetApproveValue: isReset ? amount : '0',
```

Or simply omit the field when not a reset.

This change stops the event storm and matches the
_reset → new-approve_ two-step UX.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx
around lines 424 to 427, the resetApproveValue is incorrectly assigned as '0'
when isReset is true and amount otherwise, causing an endless re-approve loop.
Fix this by flipping the logic so that resetApproveValue is '0' when not
resetting and amount when resetting, or alternatively omit resetApproveValue
entirely when not resetting to prevent triggering repeated approvals and align
with the intended reset-to-new-approve flow.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T08:52:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -146,33 +279,279 @@ export function useSpeedSwapActions({
     ],
   );
 
+  // --- approve
+
+  const handleSpeedSwapApproveTxSuccess = useCallback(
+    async (data: ISendTxOnSuccessData[]) => {
+      if (data?.[0]) {
+        const transactionSignedInfo = data[0].signedTx;
+        const approveInfo = data[0].approveInfo;
+        const txId = transactionSignedInfo.txid;
+        if (
+          inAppNotificationAtom.speedSwapApprovingTransaction &&
+          !inAppNotificationAtom.speedSwapApprovingTransaction.resetApproveValue
+        ) {
+          void backgroundApiProxy.serviceNotification.blockNotificationForTxId({
+            networkId:
+              inAppNotificationAtom.speedSwapApprovingTransaction.fromToken
+                .networkId,
+            tx: txId,
+          });
+        }
+        setInAppNotificationAtom((prev) => {
+          if (prev.speedSwapApprovingTransaction) {
+            return {
+              ...prev,
+              speedSwapApprovingTransaction: {
+                ...prev.speedSwapApprovingTransaction,
+                txId,
+                resetApproveIsMax: !!approveInfo?.isMax,
+                ...(approveInfo
+                  ? {
+                      amount: approveInfo.amount,
+                    }
+                  : {}),
+              },
+            };
+          }
+          return prev;
+        });
+      }
+    },
+    [
+      inAppNotificationAtom.speedSwapApprovingTransaction,
+      setInAppNotificationAtom,
+    ],
+  );
+
+  const cancelSpeedSwapApproveTx = useCallback(() => {
+    setInAppNotificationAtom((prev) => {
+      if (prev.speedSwapApprovingTransaction) {
+        return {
+          ...prev,
+          speedSwapApprovingTransaction: {
+            ...prev.speedSwapApprovingTransaction,
+            status: ESwapApproveTransactionStatus.CANCEL,
+          },
+        };
+      }
+      return prev;
+    });
+  }, [setInAppNotificationAtom]);
+
   const checkTokenApproveAllowance = useCallback(
-    async (token: ISwapTokenBase, spenderAddress: string) => {
+    async (spenderAddress: string) => {
       setCheckTokenAllowanceLoading(true);
       const userAddress =
         await backgroundApiProxy.serviceAccount.getAccountAddressForApi({
           accountId: accountId ?? '',
           networkId: token.networkId,
         });
-      const allowance =
+      const approveRes =
         await backgroundApiProxy.serviceSwap.fetchApproveAllowance({
           networkId: token.networkId,
           tokenAddress: token.contractAddress,
           spenderAddress,
           walletAddress: userAddress,
         });
       setCheckTokenAllowanceLoading(false);
-      return allowance;
+      return approveRes;
     },
-    [accountId],
+    [accountId, token.contractAddress, token.networkId],
   );
 
+  const approveRun = useCallback(
+    async ({
+      provider,
+      spenderAddress,
+      amount,
+      isReset,
+      fromToken,
+      toToken,
+    }: {
+      provider: string;
+      spenderAddress: string;
+      amount: string;
+      isReset?: boolean;
+      fromToken: ISwapTokenBase;
+      toToken: ISwapTokenBase;
+    }) => {
+      try {
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingLoading: true,
+        }));
+        const userAddress =
+          await backgroundApiProxy.serviceAccount.getAccountAddressForApi({
+            accountId: accountId ?? '',
+            networkId: token.networkId,
+          });
+        const approveInfo: IApproveInfo = {
+          owner: userAddress,
+          spender: spenderAddress,
+          amount: isReset ? '0' : amount,
+          isMax: !isReset,
+          tokenInfo: {
+            ...fromToken,
+            isNative: !!fromToken.isNative,
+            address: fromToken.contractAddress,
+            name: fromToken.name ?? fromToken.symbol,
+          },
+          swapApproveRes: undefined,
+        };
+        await navigationToTxConfirm({
+          approvesInfo: [approveInfo],
+          isInternalSwap: true,
+          onSuccess: handleSpeedSwapApproveTxSuccess,
+          onCancel: cancelSpeedSwapApproveTx,
+        });
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingTransaction: {
+            swapType: ESwapTabSwitchType.SWAP,
+            protocol: EProtocolOfExchange.SWAP,
+            provider,
+            providerName: provider,
+            unSupportReceiveAddressDifferent: false,
+            fromToken,
+            toToken,
+            amount,
+            toAmount: amount,
+            useAddress: userAddress,
+            spenderAddress,
+            status: ESwapApproveTransactionStatus.PENDING,
+            kind: ESwapQuoteKind.SELL,
+            resetApproveValue: isReset ? '0' : amount,
+            resetApproveIsMax: !isReset,
+          },
+        }));
+      } catch (e) {
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingLoading: false,
+        }));
+      }
+    },
path: packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Don’t swallow unexpected errors silently**

`approveRun` catches **all** exceptions and just flips a loading flag.  
Developers and users lose crucial context.

At minimum log the error; better, surface a toast:

```diff
- } catch (e) {
-   setInAppNotificationAtom((pre) => ({
-     ...pre,
-     speedSwapApprovingLoading: false,
-   }));
+ } catch (e) {
+   console.error('speedSwap approve failed', e);
+   Toast.error({
+     title: intl.formatMessage({ id: ETranslations.swap_page_toast_approve_failed }),
+   });
+   setInAppNotificationAtom((pre) => ({
+     ...pre,
+     speedSwapApprovingLoading: false,
+   }));
 }
```

Makes debugging easier and users aren’t left guessing.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
      } catch (e) {
        console.error('speedSwap approve failed', e);
        Toast.error({
          title: intl.formatMessage({ id: ETranslations.swap_page_toast_approve_failed }),
        });
        setInAppNotificationAtom((pre) => ({
          ...pre,
          speedSwapApprovingLoading: false,
        }));
      }
    },
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx
between lines 380 and 434, the catch block for the approveRun function currently
only resets the loading flag without logging or surfacing the error. To fix
this, add error logging inside the catch block to record the caught exception,
and optionally trigger a user-facing toast notification to inform users of the
failure. This will improve debugging and user experience by providing visibility
into unexpected errors.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T08:52:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -146,33 +279,279 @@ export function useSpeedSwapActions({
     ],
   );
 
+  // --- approve
+
+  const handleSpeedSwapApproveTxSuccess = useCallback(
+    async (data: ISendTxOnSuccessData[]) => {
+      if (data?.[0]) {
+        const transactionSignedInfo = data[0].signedTx;
+        const approveInfo = data[0].approveInfo;
+        const txId = transactionSignedInfo.txid;
+        if (
+          inAppNotificationAtom.speedSwapApprovingTransaction &&
+          !inAppNotificationAtom.speedSwapApprovingTransaction.resetApproveValue
+        ) {
+          void backgroundApiProxy.serviceNotification.blockNotificationForTxId({
+            networkId:
+              inAppNotificationAtom.speedSwapApprovingTransaction.fromToken
+                .networkId,
+            tx: txId,
+          });
+        }
+        setInAppNotificationAtom((prev) => {
+          if (prev.speedSwapApprovingTransaction) {
+            return {
+              ...prev,
+              speedSwapApprovingTransaction: {
+                ...prev.speedSwapApprovingTransaction,
+                txId,
+                resetApproveIsMax: !!approveInfo?.isMax,
+                ...(approveInfo
+                  ? {
+                      amount: approveInfo.amount,
+                    }
+                  : {}),
+              },
+            };
+          }
+          return prev;
+        });
+      }
+    },
+    [
+      inAppNotificationAtom.speedSwapApprovingTransaction,
+      setInAppNotificationAtom,
+    ],
+  );
+
+  const cancelSpeedSwapApproveTx = useCallback(() => {
+    setInAppNotificationAtom((prev) => {
+      if (prev.speedSwapApprovingTransaction) {
+        return {
+          ...prev,
+          speedSwapApprovingTransaction: {
+            ...prev.speedSwapApprovingTransaction,
+            status: ESwapApproveTransactionStatus.CANCEL,
+          },
+        };
+      }
+      return prev;
+    });
+  }, [setInAppNotificationAtom]);
+
   const checkTokenApproveAllowance = useCallback(
-    async (token: ISwapTokenBase, spenderAddress: string) => {
+    async (spenderAddress: string) => {
       setCheckTokenAllowanceLoading(true);
       const userAddress =
         await backgroundApiProxy.serviceAccount.getAccountAddressForApi({
           accountId: accountId ?? '',
           networkId: token.networkId,
         });
-      const allowance =
+      const approveRes =
         await backgroundApiProxy.serviceSwap.fetchApproveAllowance({
           networkId: token.networkId,
           tokenAddress: token.contractAddress,
           spenderAddress,
           walletAddress: userAddress,
         });
       setCheckTokenAllowanceLoading(false);
-      return allowance;
+      return approveRes;
     },
-    [accountId],
+    [accountId, token.contractAddress, token.networkId],
   );
 
+  const approveRun = useCallback(
+    async ({
+      provider,
+      spenderAddress,
+      amount,
+      isReset,
+      fromToken,
+      toToken,
+    }: {
+      provider: string;
+      spenderAddress: string;
+      amount: string;
+      isReset?: boolean;
+      fromToken: ISwapTokenBase;
+      toToken: ISwapTokenBase;
+    }) => {
+      try {
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingLoading: true,
+        }));
+        const userAddress =
+          await backgroundApiProxy.serviceAccount.getAccountAddressForApi({
+            accountId: accountId ?? '',
+            networkId: token.networkId,
+          });
+        const approveInfo: IApproveInfo = {
+          owner: userAddress,
+          spender: spenderAddress,
+          amount: isReset ? '0' : amount,
+          isMax: !isReset,
+          tokenInfo: {
+            ...fromToken,
+            isNative: !!fromToken.isNative,
+            address: fromToken.contractAddress,
+            name: fromToken.name ?? fromToken.symbol,
+          },
+          swapApproveRes: undefined,
+        };
+        await navigationToTxConfirm({
+          approvesInfo: [approveInfo],
+          isInternalSwap: true,
+          onSuccess: handleSpeedSwapApproveTxSuccess,
+          onCancel: cancelSpeedSwapApproveTx,
+        });
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingTransaction: {
+            swapType: ESwapTabSwitchType.SWAP,
+            protocol: EProtocolOfExchange.SWAP,
+            provider,
+            providerName: provider,
+            unSupportReceiveAddressDifferent: false,
+            fromToken,
+            toToken,
+            amount,
+            toAmount: amount,
+            useAddress: userAddress,
+            spenderAddress,
+            status: ESwapApproveTransactionStatus.PENDING,
+            kind: ESwapQuoteKind.SELL,
+            resetApproveValue: isReset ? '0' : amount,
+            resetApproveIsMax: !isReset,
+          },
+        }));
+      } catch (e) {
+        setInAppNotificationAtom((pre) => ({
+          ...pre,
+          speedSwapApprovingLoading: false,
+        }));
+      }
+    },
+    [
+      accountId,
+      setInAppNotificationAtom,
+      token,
+      navigationToTxConfirm,
+      handleSpeedSwapApproveTxSuccess,
+      cancelSpeedSwapApproveTx,
+    ],
+  );
+
+  const speedSwapApproveHandler = useCallback(
+    async ({
+      provider,
+      spenderAddress,
+      amount,
+      isReset,
+      fromToken,
+      toToken,
+    }: {
+      provider: string;
+      spenderAddress: string;
+      amount: string;
+      isReset?: boolean;
+      fromToken: ISwapTokenBase;
+      toToken: ISwapTokenBase;
+    }) => {
+      if (isReset) {
+        Dialog.confirm({
+          onConfirmText: intl.formatMessage({
+            id: ETranslations.global_continue,
+          }),
+          onConfirm: () => {
+            void approveRun({
+              provider,
+              spenderAddress,
+              amount,
+              isReset,
+              fromToken,
+              toToken,
+            });
+          },
+          showCancelButton: true,
+          title: intl.formatMessage({
+            id: ETranslations.swap_page_provider_approve_usdt_dialog_title,
+          }),
+          description: intl.formatMessage({
+            id: ETranslations.swap_page_provider_approve_usdt_dialog_content,
+          }),
+          icon: 'ErrorOutline',
+        });
+      } else {
+        void approveRun({
+          provider,
+          spenderAddress,
+          amount,
+          isReset,
+          fromToken,
+          toToken,
+        });
+      }
+    },
+    [approveRun, intl],
+  );
+  const speedSwapApproveLoading = useMemo(() => {
+    const speedSwapApproveTransaction =
+      inAppNotificationAtom.speedSwapApprovingTransaction;
+    if (
+      speedSwapApproveTransaction &&
+      inAppNotificationAtom.speedSwapApprovingLoading &&
+      (equalTokenNoCaseSensitive({
+        token1: speedSwapApproveTransaction.fromToken,
+        token2: token,
+      }) ||
+        equalTokenNoCaseSensitive({
+          token1: speedSwapApproveTransaction.toToken,
+          token2: token,
+        }))
+    ) {
+      return true;
+    }
+    return false;
+  }, [
+    inAppNotificationAtom.speedSwapApprovingLoading,
+    inAppNotificationAtom.speedSwapApprovingTransaction,
+    token,
+  ]);
+
+  const handleSwapSpeedApprovingReset = useCallback(
+    ({ approvedSwapInfo }: { approvedSwapInfo: ISwapApproveTransaction }) => {
+      if (approvedSwapInfo.resetApproveValue) {
+        void approveRun({
+          provider: approvedSwapInfo.provider,
+          spenderAddress: approvedSwapInfo.spenderAddress,
+          amount: approvedSwapInfo.resetApproveValue,
+          isReset: false,
+          fromToken: approvedSwapInfo.fromToken,
+          toToken: approvedSwapInfo.toToken,
+        });
+      }
+    },
+    [approveRun],
+  );
+
+  useEffect(() => {
+    appEventBus.off(
+      EAppEventBusNames.SwapSpeedApprovingReset,
+      handleSwapSpeedApprovingReset,
+    );
+    appEventBus.on(
+      EAppEventBusNames.SwapSpeedApprovingReset,
+      handleSwapSpeedApprovingReset,
+    );
+  }, [handleSwapSpeedApprovingReset]);
path: packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Leak-free event listener**

`useEffect` registers a listener every render cycle because you first
`off` then `on` unconditionally.  
On unmount you never detach, so the last handler stays alive.

Simplify:

```diff
useEffect(() => {
  appEventBus.on(EAppEventBusNames.SwapSpeedApprovingReset, handleSwapSpeedApprovingReset);
-}, [handleSwapSpeedApprovingReset]);
+  return () =>
+    appEventBus.off(
+      EAppEventBusNames.SwapSpeedApprovingReset,
+      handleSwapSpeedApprovingReset,
+    );
+}, [handleSwapSpeedApprovingReset]);
```

One `on`, one matching `off` – no memory leak.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
  useEffect(() => {
    appEventBus.off(
      EAppEventBusNames.SwapSpeedApprovingReset,
      handleSwapSpeedApprovingReset,
    );
    appEventBus.on(
      EAppEventBusNames.SwapSpeedApprovingReset,
      handleSwapSpeedApprovingReset,
    );
    return () =>
      appEventBus.off(
        EAppEventBusNames.SwapSpeedApprovingReset,
        handleSwapSpeedApprovingReset,
      );
  }, [handleSwapSpeedApprovingReset]);
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx
around lines 538 to 547, the useEffect hook currently removes and adds the event
listener on every render without a proper cleanup on unmount, causing a memory
leak. Fix this by registering the event listener once inside useEffect and
returning a cleanup function that removes the listener on unmount, ensuring
exactly one on and one off call to prevent leaks.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T08:52:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1123,6 +1162,61 @@ export default class ServiceSwap extends ServiceBase {
     }
   }
 
+  async speedSwapApprovingStateRunSync(networkId: string, txId: string) {
+    let enableInterval = true;
+    try {
+      const txState = await this.fetchTxState({
+        txId,
+        networkId,
+      });
+      const preApproveTx = await this.getSpeedSwapApprovingTransaction();
+      if (
+        txState.state === ESwapTxHistoryStatus.SUCCESS ||
+        txState.state === ESwapTxHistoryStatus.FAILED
+      ) {
+        enableInterval = false;
+        if (preApproveTx) {
+          if (
+            txState.state === ESwapTxHistoryStatus.SUCCESS ||
+            txState.state === ESwapTxHistoryStatus.FAILED
+          ) {
+            let newApproveTx: ISwapApproveTransaction = {
+              ...preApproveTx,
+              blockNumber: txState.blockNumber,
+              status: ESwapApproveTransactionStatus.SUCCESS,
+            };
+            if (txState.state === ESwapTxHistoryStatus.FAILED) {
+              newApproveTx = {
+                ...preApproveTx,
+                txId: undefined,
+                status: ESwapApproveTransactionStatus.FAILED,
+              };
+            }
+            await this.setSpeedSwapApprovingTransaction(newApproveTx);
+          }
+        }
+      } else if (
+        preApproveTx &&
+        preApproveTx.status !== ESwapApproveTransactionStatus.PENDING
+      ) {
+        await this.setSpeedSwapApprovingTransaction({
+          ...preApproveTx,
+          status: ESwapApproveTransactionStatus.PENDING,
+        });
+      }
+    } catch (e) {
+      console.error(e);
+    } finally {
+      if (enableInterval) {
+        this.speedSwapApprovingIntervalCount += 1;
+        void this.speedSwapApprovingStateAction();
+      } else {
+        void this.cleanSpeedSwapApprovingInterval();
+        this.speedSwapApprovingIntervalCount = 0;
+      }
+    }
+  }
path: packages/kit-bg/src/services/ServiceSwap.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Large copy-paste block – extract common logic**

`speedSwapApprovingStateRunSync()` is 99 % identical to `approvingStateRunSync()`. Keeping two copies doubles the chance of subtle bugs.  
A small higher-order function can consolidate them:

```ts
private async _runApprovingSync(
  getters: {
    getTx(): Promise<ISwapApproveTransaction | undefined>;
    setTx(tx?: ISwapApproveTransaction): Promise<void>;
    cleanInterval(): Promise<void>;
  },
  intervalRef: { count: number },
  networkId: string,
  txId: string,
) { ... }
```

Both flows then call `_runApprovingSync` with the appropriate getters/refs. This shrinks >150 lines to ~40 and keeps behaviour in lockstep.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/kit-bg/src/services/ServiceSwap.ts between lines 1165 and 1218, the
method speedSwapApprovingStateRunSync duplicates almost all logic from
approvingStateRunSync, increasing maintenance risk. Refactor by extracting the
shared logic into a private helper method _runApprovingSync that accepts getter
and setter functions for the transaction, a cleanup function, an interval count
reference, and parameters networkId and txId. Then replace both original methods
with calls to _runApprovingSync using appropriate arguments, reducing code
duplication and ensuring consistent behavior.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T08:52:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,6 +131,10 @@ export default class ServiceSwap extends ServiceBase {
 
   private approvingIntervalCount = 0;
 
+  private speedSwapApprovingInterval: ReturnType<typeof setTimeout> | undefined;
+
+  private speedSwapApprovingIntervalCount = 0;
+
   constructor({ backgroundApi }: { backgroundApi: any }) {
path: packages/kit-bg/src/services/ServiceSwap.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Duplication of interval state – consider a shared helper**

`approvingInterval(Count)` and `speedSwapApprovingInterval(Count)` differ only in name. Two nearly-identical timer pairs increase maintenance cost and the risk of divergence.

Recommend extracting a tiny helper:

```ts
private createApprovingTimer(kind: 'normal' | 'speed') {
  return {
    interval: undefined as ReturnType<typeof setTimeout> | undefined,
    count: 0,
  };
}
private timers = {
  normal: this.createApprovingTimer('normal'),
  speed: this.createApprovingTimer('speed'),
};
```

Both flows can then reuse the same scheduling logic with one parameter (`kind`). Less code, fewer bugs.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/kit-bg/src/services/ServiceSwap.ts around lines 134 to 138, there is
duplication of interval state variables for normal and speed swap approving
intervals and counts. Refactor by creating a shared helper method that returns
an object with interval and count properties, then define a timers object
holding both normal and speed timers using this helper. Update the code to use
this timers object with a kind parameter to unify and reuse the scheduling
logic, reducing duplication and maintenance overhead.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T08:52:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1139,6 +1233,22 @@ export default class ServiceSwap extends ServiceBase {
     }
   }
 
+  @backgroundMethod()
+  async speedSwapApprovingStateAction() {
+    void this.cleanSpeedSwapApprovingInterval();
+    const approvingTransaction = await this.getSpeedSwapApprovingTransaction();
+    if (approvingTransaction && approvingTransaction.txId) {
+      this.speedSwapApprovingInterval = setTimeout(() => {
+        if (approvingTransaction.txId) {
+          void this.speedSwapApprovingStateRunSync(
path: packages/kit-bg/src/services/ServiceSwap.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Tiny readability tweak – optional chaining**

Biome flagged this: use optional chaining to shorten the null-check.

```diff
-    const approvingTransaction = await this.getSpeedSwapApprovingTransaction();
-    if (approvingTransaction && approvingTransaction.txId) {
+    const approvingTransaction = await this.getSpeedSwapApprovingTransaction();
+    if (approvingTransaction?.txId) {
```

Purely cosmetic, but keeps the code consistent with modern style.




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
  @backgroundMethod()
  async speedSwapApprovingStateAction() {
    void this.cleanSpeedSwapApprovingInterval();
    const approvingTransaction = await this.getSpeedSwapApprovingTransaction();
    if (approvingTransaction?.txId) {
      this.speedSwapApprovingInterval = setTimeout(() => {
        if (approvingTransaction.txId) {
          void this.speedSwapApprovingStateRunSync(
```

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 1240-1240: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/kit-bg/src/services/ServiceSwap.ts around lines 1236 to 1243,
replace explicit null-checks on approvingTransaction and its txId property with
optional chaining to improve readability and modernize the code style.
Specifically, use optional chaining when accessing txId to shorten the condition
and maintain consistency with modern JavaScript practices.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T08:52:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1068,6 +1099,14 @@ export default class ServiceSwap extends ServiceBase {
     }
   }
 
+  @backgroundMethod()
+  async cleanSpeedSwapApprovingInterval() {
+    if (this.speedSwapApprovingInterval) {
+      clearTimeout(this.speedSwapApprovingInterval);
+      this.speedSwapApprovingInterval = undefined;
+    }
+  }
path: packages/kit-bg/src/services/ServiceSwap.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Timer hygiene – clear count as well**

`cleanSpeedSwapApprovingInterval()` clears the timeout but leaves `speedSwapApprovingIntervalCount` untouched.  
If the interval restarts later, it will resume with an outdated multiplier, slowing the first retry.

Add a reset:

```diff
   if (this.speedSwapApprovingInterval) {
     clearTimeout(this.speedSwapApprovingInterval);
     this.speedSwapApprovingInterval = undefined;
+    this.speedSwapApprovingIntervalCount = 0;
   }
```




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
  @backgroundMethod()
  async cleanSpeedSwapApprovingInterval() {
    if (this.speedSwapApprovingInterval) {
      clearTimeout(this.speedSwapApprovingInterval);
      this.speedSwapApprovingInterval = undefined;
      this.speedSwapApprovingIntervalCount = 0;
    }
  }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/kit-bg/src/services/ServiceSwap.ts around lines 1102 to 1108, the
method cleanSpeedSwapApprovingInterval clears the timeout but does not reset
speedSwapApprovingIntervalCount, causing the retry multiplier to persist
incorrectly. Fix this by adding a line to reset speedSwapApprovingIntervalCount
to its initial value (likely zero) within the method after clearing the timeout.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T08:52:58Z
author_association: CONTRIBUTOR

PR comments:
user: login: codesandbox[bot]
created_at: 2025-05-20T08:47:37Z
body: #### Review or Edit in CodeSandbox

Open the branch in <a href="https://codesandbox.io/p/github/OneKeyHQ/app-monorepo/fix/speed-swap-approve?mode=review&utm_source=gh_app">Web Editor</a> &bull; <a href="https://codesandbox.io/p/vscode?owner=OneKeyHQ&repo=app-monorepo&branch=fix/speed-swap-approve&utm_source=gh_app">VS Code</a> &bull; <a href="https://codesandbox.io/p/vscode?owner=OneKeyHQ&repo=app-monorepo&branch=fix/speed-swap-approve&insiders=true&utm_source=gh_app">Insiders</a><br>
Open <a href="https://codesandbox.io/p/devtool/preview/OneKeyHQ/app-monorepo/fix/speed-swap-approve?task=preview&port=51423&redirect=true&utm_source=gh_app">Preview</a>

<!-- open-in-codesandbox:complete -->

author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-20T08:47:41Z
body: <!-- This is an auto-generated comment: review paused by coderabbit.ai -->

> [!NOTE]
> ## Reviews paused
> 
> Use the following commands to manage reviews:
> - `@coderabbitai resume` to resume automatic reviews.
> - `@coderabbitai review` to trigger a single review.

<!-- end of auto-generated comment: review paused by coderabbit.ai -->
<!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update adds a parallel approval flow for "speed swap" transactions, mirroring the existing swap approval logic. It introduces new state management, polling intervals, event handling, and UI feedback for speed swap approvals. Several types and interfaces are extended to support this feature, and optional chaining is added in multiple places to prevent runtime errors.

## Changes

| File(s) / Path(s)                                                                                     | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
|-------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `packages/kit-bg/src/services/ServiceSwap.ts`                                                         | Added speed swap approval state management: new interval/counter, methods for getting/setting/closing transactions, polling logic, and error handling. Updated allowance response typing and extended speed swap config with `spenderAddress`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `packages/kit-bg/src/states/jotai/atoms/InAppNotification.ts`                                         | Added `speedSwapApprovingTransaction` and `speedSwapApprovingLoading` to the notification atom type and initial state.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `packages/kit/src/provider/Container/InAppNotification/index.tsx`                                     | Added handling for `speedSwapApprovingTransaction`: new refs, effects for polling, success/failure UI feedback, and event emission. Mirrors existing swap approval logic for speed swaps.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `packages/kit/src/states/jotai/contexts/swap/atoms.ts`                                                | Added optional chaining to `p?.quoteId` in swap quote list logic to prevent errors if `p` is null or undefined.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/SwapPanel.tsx`                     | Updated use of `useSpeedSwapActions`: replaced old handlers with new approval and allowance handlers, updated hook parameters, and removed now-unused build transaction handlers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapActions.tsx`     | Major update: added full approval flow for speed swaps, including approval transaction handling, notification updates, event bus integration, confirmation dialogs, and new loading states. Updated hook return values and removed unused handlers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `packages/kit/src/views/Market/MarketDetailV2/components/SwapPanel/hooks/useSpeedSwapInit.tsx`        | Added `spenderAddress` (empty string) to the default `speedConfig` in the initialization object.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `packages/kit/src/views/Swap/hooks/useSwapQuote.ts`                                                   | Used optional chaining for `approvedSwapInfo?.quoteId` to avoid errors if `approvedSwapInfo` is missing.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `packages/kit/src/views/Swap/pages/components/SwapMainLand.tsx`                                       | Used optional chaining for `alerts?.quoteId` in a render condition to prevent errors if `alerts` is missing.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `packages/shared/src/eventBus/appEventBus.ts`                                                         | Added new event payload type for `SwapSpeedApprovingReset` with `approvedSwapInfo` property.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `packages/shared/src/eventBus/appEventBusNames.ts`                                                    | Added new event name: `SwapSpeedApprovingReset` to the event bus names enum.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `packages/shared/types/swap/SwapProvider.constants.ts`                                                | Added constant: `swapSpeedSwapApprovingStateFetchInterval` (1 second).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `packages/shared/types/swap/types.ts`                                                                 | Made `quoteId` optional in `ISwapApproveTransaction`. Added new interface `ISwapApproveAllowanceResponse`. Added `spenderAddress` to `ISpeedSwapConfig.speedConfig`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant UI
    participant Hook
    participant NotificationAtom
    participant ServiceSwap
    participant EventBus

    UI->>Hook: User initiates speed swap approval
    Hook->>ServiceSwap: approveRun (start approval tx)
    ServiceSwap-->>NotificationAtom: set speedSwapApprovingLoading = true
    ServiceSwap-->>NotificationAtom: set speedSwapApprovingTransaction (PENDING)
    ServiceSwap->>UI: Navigate to tx confirmation
    Note over UI,ServiceSwap: User confirms or cancels

    ServiceSwap->>ServiceSwap: Start polling tx status (interval)
    ServiceSwap->>ServiceSwap: speedSwapApprovingStateRunSync
    alt Tx Success
        ServiceSwap-->>NotificationAtom: set speedSwapApprovingTransaction (SUCCESS)
        ServiceSwap->>EventBus: emit SwapSpeedApprovingReset (if reset needed)
        NotificationAtom-->>UI: Show success toast
    else Tx Fail/Cancel
        ServiceSwap-->>NotificationAtom: set speedSwapApprovingTransaction (FAILED/CANCEL)
        NotificationAtom-->>UI: Show error toast
    end
    ServiceSwap->>ServiceSwap: Clean polling interval
```


<!-- walkthrough_end -->

<!-- announcements_start -->

> [!NOTE]
> <details>
> <summary>⚡️ AI Code Reviews for VS Code, Cursor, Windsurf</summary>
> 
> CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
> Learn more [here](http://coderabbit.ai/ide).
> 
> </details>

---

> [!NOTE]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings
<details open="true">
<summary>🧪 Generate Unit Tests</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with Unit Tests
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit Unit Tests in branch `fix/speed-swap-approve`
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post Copyable Unit Tests in Comment

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=OneKeyHQ/app-monorepo&utm_content=7362):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai auto-generate unit tests` to generate unit tests for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` or `Summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: limichange
created_at: 2025-05-20T08:53:24Z
body: @coderabbitai stop and resolve.
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-20T08:53:54Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
@limichange I'll stop reviewing and resolve all review comments on this PR.

<details>
<summary>✅ Actions performed</summary>

Comments resolved.

---

Reviews paused.

</details>
author_association: CONTRIBUTOR