From a8632559f82b469ab4e556bd92a470b474448339 Mon Sep 17 00:00:00 2001 From: ChefEric <173023571+chef-eric@users.noreply.github.com> Date: Thu, 31 Jul 2025 21:04:44 +0800 Subject: [PATCH 1/2] fix solana swap default --- apps/web/src/hooks/useSolanaTokenList.ts | 14 +++++++------- apps/web/src/views/SwapSimplify/index.tsx | 8 ++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/web/src/hooks/useSolanaTokenList.ts b/apps/web/src/hooks/useSolanaTokenList.ts index 5282ec705ab53..209d5ad084a4f 100644 --- a/apps/web/src/hooks/useSolanaTokenList.ts +++ b/apps/web/src/hooks/useSolanaTokenList.ts @@ -10,14 +10,14 @@ import { useQuery } from '@tanstack/react-query' import { SOLANA_LISTS_CONFIG, TokenListKey, USER_ADDED_KEY, convertRawTokenInfoIntoSPLToken } from 'config/solana-list' // Custom hook for individual token list queries -function useTokenListQuery(listKey: TokenListKey) { +function useTokenListQuery(listKey: TokenListKey, enabled: boolean) { const listSettings = useAtomValue(solanaListSettingsAtom) // PancakeSwap list is always enabled const isEnabled = listKey === TokenListKey.PANCAKESWAP ? true : listSettings[listKey] const listConfig = SOLANA_LISTS_CONFIG[listKey] return useQuery({ - queryKey: ['solana-token-list', listConfig.key, isEnabled], + queryKey: ['solana-token-list', listConfig.key, isEnabled && enabled], queryFn: async () => { const res = await fetch(listConfig.apiUrl) if (!res.ok) { @@ -29,7 +29,7 @@ function useTokenListQuery(listKey: TokenListKey) { retry: 3, refetchOnWindowFocus: false, refetchOnReconnect: false, - enabled: isEnabled, + enabled: isEnabled && enabled, select: (data) => { return listConfig.parser(data) }, @@ -48,14 +48,14 @@ function saveUserAddedTokens(tokens: TokenInfo[]) { localStorage.setItem(USER_ADDED_KEY, JSON.stringify(tokens)) } -export function useSolanaTokenList() { +export function useSolanaTokenList(enabled = true) { const [userTokens, setUserTokens] = useState(getUserAddedTokens()) const setTokenList = useSetAtom(solanaTokenListAtom) // Create individual queries for each token list using the custom hook - const { data: pcsTokens, isLoading: pcsLoading } = useTokenListQuery(TokenListKey.PANCAKESWAP) - const { data: raydiumTokens, isLoading: raydiumLoading } = useTokenListQuery(TokenListKey.RAYDIUM) - const { data: jupiterTokens, isLoading: jupiterLoading } = useTokenListQuery(TokenListKey.JUPITER) + const { data: pcsTokens, isLoading: pcsLoading } = useTokenListQuery(TokenListKey.PANCAKESWAP, enabled) + const { data: raydiumTokens, isLoading: raydiumLoading } = useTokenListQuery(TokenListKey.RAYDIUM, enabled) + const { data: jupiterTokens, isLoading: jupiterLoading } = useTokenListQuery(TokenListKey.JUPITER, enabled) const mergedTokens = useMemo(() => { const seen = new Set() diff --git a/apps/web/src/views/SwapSimplify/index.tsx b/apps/web/src/views/SwapSimplify/index.tsx index cbc28ce94cd3b..64e3bb039a121 100644 --- a/apps/web/src/views/SwapSimplify/index.tsx +++ b/apps/web/src/views/SwapSimplify/index.tsx @@ -5,6 +5,9 @@ import { useAtom } from 'jotai' import { MobileCard } from 'components/AdPanel/MobileCard' import { useCurrency } from 'hooks/Tokens' +import { useSolanaTokenList } from 'hooks/useSolanaTokenList' +import { useActiveChainId } from 'hooks/useActiveChainId' +import { NonEVMChainId } from '@pancakeswap/chains' import { AutoSlippageProvider } from 'hooks/useAutoSlippageWithFallback' import { useSwapHotTokenDisplay } from 'hooks/useSwapHotTokenDisplay' import dynamic from 'next/dynamic' @@ -29,6 +32,7 @@ const Wrapper = styled(Box)` const InfinitySwapInner = () => { const { query } = useRouter() + const { chainId } = useActiveChainId() const { isMobile, isDesktop } = useMatchBreakpoints() const { isChartExpanded } = useContext(SwapFeaturesContext) const [isChartDisplayed, setIsChartDisplayed] = useAtom(chartDisplayAtom) @@ -44,6 +48,10 @@ const InfinitySwapInner = () => { const inputCurrency = useCurrency(inputCurrencyId, inputChainId) const outputCurrency = useCurrency(outputCurrencyId, outputChainId) + // Prefetch Solana tokens when user switches to Solana + useSolanaTokenList(chainId === NonEVMChainId.SOLANA) + + useEffect(() => { if (firstTime && query.showTradingReward) { setFirstTime(false) From 70a21ae95e9d2f90e6ee25e7fe4ca1d02571e490 Mon Sep 17 00:00:00 2001 From: ChefEric <173023571+chef-eric@users.noreply.github.com> Date: Fri, 1 Aug 2025 22:23:21 +0800 Subject: [PATCH 2/2] feat(solana): wait for tx before refreshing balances --- .../src/state/token/solanaTokenBalances.ts | 15 ++++++- .../V3Swap/hooks/useConfirmModalState.tsx | 41 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/apps/web/src/state/token/solanaTokenBalances.ts b/apps/web/src/state/token/solanaTokenBalances.ts index 31d9bf0d8d6b9..9f62a39c11b91 100644 --- a/apps/web/src/state/token/solanaTokenBalances.ts +++ b/apps/web/src/state/token/solanaTokenBalances.ts @@ -1,4 +1,4 @@ -import { useMemo } from 'react' +import { useMemo, useCallback } from 'react' import BN from 'bignumber.js' import { atom, useAtomValue, useSetAtom } from 'jotai' @@ -79,3 +79,16 @@ export function useSolanaTokenBalances( return { balances: filtered, loading: false } }, [mintAddresses, state]) } + +/** + * Hook to trigger a manual refresh of Solana token balances. + * It simply increments the global refresh counter, causing + * any atoms that depend on it to re-fetch balances. + */ +export function useRefreshSolanaTokenBalances() { + const setCounter = useSetAtom(solanaTokenBalanceRefreshCounterAtom) + + return useCallback(() => { + setCounter((c) => c + 1) + }, [setCounter]) +} diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx b/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx index 5176e69a6ca5b..dadd39b675060 100644 --- a/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx +++ b/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx @@ -57,6 +57,7 @@ import { getBridgeCalldata } from 'views/Swap/Bridge/api' import { useBridgeCheckApproval } from 'views/Swap/Bridge/hooks' import { VersionedTransaction } from '@solana/web3.js' import { UltraSwapError, UltraSwapErrorType, ultraSwapService } from '@pancakeswap/solana-router-sdk' +import { confirmTransaction } from '@pancakeswap/solana-core-sdk' import { ChainId as EvmChainId } from '@pancakeswap/chains' import { useUserSlippage } from '@pancakeswap/utils/user' @@ -65,6 +66,8 @@ import { activeBridgeOrderMetadataAtom } from 'views/Swap/Bridge/CrossChainConfi import { Permit2Schema } from 'views/Swap/Bridge/types' import { getBridgeOrderPriceImpact } from 'views/Swap/Bridge/utils' import useAccountActiveChain from 'hooks/useAccountActiveChain' +import { useRefreshSolanaTokenBalances } from 'state/token/solanaTokenBalances' +import { useSolanaConnectionWithRpcAtom } from 'hooks/solana/useSolanaConnectionWithRpcAtom' import { BatchCall, getBatchedTransaction as getBatchedTransactionHelper } from './batchHelper' import { eip5792UserRejectUpgradeError, userRejectedError } from './useSendSwapTransaction' import { useSwapCallback } from './useSwapCallback' @@ -197,6 +200,9 @@ const useConfirmActions = ( const { toastSuccess, toastError, toastInfo } = useToast() + // Refresh function to update cached Solana balances after swap + const refreshSolanaBalances = useRefreshSolanaTokenBalances() + const resetState = useCallback(() => { setConfirmState(ConfirmModalState.REVIEWING) setTxHash(undefined) @@ -238,6 +244,24 @@ const useConfirmActions = ( [chainId], ) + const connection = useSolanaConnectionWithRpcAtom() + + const retryWaitForSolanaTransaction = useCallback( + async (signature?: string) => { + if (!signature) return undefined + const waitTx = async () => { + try { + await confirmTransaction(connection, signature) + } catch (error) { + throw new RetryableError() + } + } + const { promise } = retry(waitTx, { n: 5, minWait: 3000, maxWait: 5000 }) + return promise + }, + [connection], + ) + // define the action of each step const revokeStep = useMemo(() => { const action = async (nextState?: ConfirmModalState) => { @@ -820,6 +844,10 @@ const useConfirmActions = ( ) setConfirmState(ConfirmModalState.COMPLETED) + + // Wait for transaction confirmation then refresh balances + await retryWaitForSolanaTransaction(signature) + refreshSolanaBalances() } catch (error: any) { console.error('Solana swap error', error) if (error?.message?.includes('rejected')) { @@ -836,7 +864,18 @@ const useConfirmActions = ( showIndicator: false, getCalldata: () => [], } - }, [solanaAccount, order, resetState, showError, toastSuccess, t, signTransaction, solanaWallet?.adapter.publicKey]) + }, [ + solanaAccount, + order, + resetState, + showError, + toastSuccess, + t, + signTransaction, + retryWaitForSolanaTransaction, + refreshSolanaBalances, + solanaWallet?.adapter.publicKey, + ]) const actions = useMemo(() => { return {