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/4] 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 a54dbc94ae36a5a19aed74961e8bafbabcf750bf Mon Sep 17 00:00:00 2001 From: ChefEric <173023571+chef-eric@users.noreply.github.com> Date: Fri, 1 Aug 2025 22:23:27 +0800 Subject: [PATCH 2/4] 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 { From 766457882bcd46b1c976fe48cf57231cba8f548a Mon Sep 17 00:00:00 2001 From: ChefEric <173023571+chef-eric@users.noreply.github.com> Date: Fri, 1 Aug 2025 22:57:54 +0800 Subject: [PATCH 3/4] Use per-wallet refresh for Solana balances --- apps/web/src/state/token/solanaTokenBalances.ts | 12 ++++++------ .../views/Swap/V3Swap/hooks/useConfirmModalState.tsx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/web/src/state/token/solanaTokenBalances.ts b/apps/web/src/state/token/solanaTokenBalances.ts index 9f62a39c11b91..99eab830b51f9 100644 --- a/apps/web/src/state/token/solanaTokenBalances.ts +++ b/apps/web/src/state/token/solanaTokenBalances.ts @@ -8,8 +8,8 @@ import { rpcUrlAtom } from '@pancakeswap/utils/user' import { fetchSolanaTokenBalances } from './solanaBalanceFetcher' -// Global refresh counter for triggering balance updates -export const solanaTokenBalanceRefreshCounterAtom = atom(0) +// Refresh counter per wallet address for triggering balance updates +export const solanaWalletBalanceRefreshCounterAtomFamily = atomFamily(() => atom(0)) /** * AtomFamily that uses Jotai's dependency tracking with refresh capability. @@ -20,8 +20,8 @@ const walletBalancesAtomFamily = atomFamily((walletAddress: string | null | unde atom(async (get) => { if (!walletAddress) return new Map() - // Add dependency on refresh counter to trigger updates - get(solanaTokenBalanceRefreshCounterAtom) + // Add dependency on wallet-specific refresh counter to trigger updates + get(solanaWalletBalanceRefreshCounterAtomFamily(walletAddress)) const rpc = get(rpcUrlAtom) return fetchSolanaTokenBalances(walletAddress, rpc) @@ -85,8 +85,8 @@ export function useSolanaTokenBalances( * 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) +export function useRefreshSolanaTokenBalances(walletAddress?: string | null) { + const setCounter = useSetAtom(solanaWalletBalanceRefreshCounterAtomFamily(walletAddress ?? null)) return useCallback(() => { setCounter((c) => c + 1) diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx b/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx index dadd39b675060..873cf5288d41c 100644 --- a/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx +++ b/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx @@ -201,7 +201,7 @@ const useConfirmActions = ( const { toastSuccess, toastError, toastInfo } = useToast() // Refresh function to update cached Solana balances after swap - const refreshSolanaBalances = useRefreshSolanaTokenBalances() + const refreshSolanaBalances = useRefreshSolanaTokenBalances(solanaWallet?.adapter.publicKey?.toBase58()) const resetState = useCallback(() => { setConfirmState(ConfirmModalState.REVIEWING) From 545f29240b6bc35278fd144b81b532965622fa1c Mon Sep 17 00:00:00 2001 From: ChefEric <173023571+chef-eric@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:05:55 +0800 Subject: [PATCH 4/4] fix: show solana balances without evm wallet --- .../web/src/components/CurrencyInputPanel/index.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/CurrencyInputPanel/index.tsx b/apps/web/src/components/CurrencyInputPanel/index.tsx index 2dd3493478a01..052057b92107f 100644 --- a/apps/web/src/components/CurrencyInputPanel/index.tsx +++ b/apps/web/src/components/CurrencyInputPanel/index.tsx @@ -13,8 +13,7 @@ import { useStablecoinPriceAmount } from 'hooks/useStablecoinPrice' import { StablePair } from 'views/AddLiquidity/AddStableLiquidity/hooks/useStableLPDerivedMintInfo' import { FiatLogo } from 'components/Logo/CurrencyLogo' -import { useCurrencyBalance } from 'state/wallet/hooks' -import { useAccount } from 'wagmi' +import { useUnifiedCurrencyBalance } from 'hooks/useUnifiedCurrencyBalance' import { CommonBasesType } from 'components/SearchModal/types' import CurrencySearchModal from '../SearchModal/CurrencySearchModal' @@ -43,11 +42,11 @@ interface CurrencyInputPanelProps { lpPercent?: string label?: string onCurrencySelect?: (currency: UnifiedCurrency) => void - currency?: Currency | null + currency?: UnifiedCurrency | null disableCurrencySelect?: boolean hideBalance?: boolean pair?: Pair | StablePair | null - otherCurrency?: Currency | null + otherCurrency?: UnifiedCurrency | null id: string showCommonBases?: boolean commonBasesType?: CommonBasesType @@ -93,9 +92,9 @@ const CurrencyInputPanel = memo(function CurrencyInputPanel({ title, hideBalanceComp, }: CurrencyInputPanelProps) { - const { address: account } = useAccount() - - const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined) + const selectedCurrencyBalance = useUnifiedCurrencyBalance(currency ?? undefined) as + | CurrencyAmount + | undefined const { t } = useTranslation() const mode = id