diff --git a/src/actions/thunks.ts b/src/actions/thunks.ts new file mode 100644 index 000000000..572635243 --- /dev/null +++ b/src/actions/thunks.ts @@ -0,0 +1,71 @@ +import { BN } from '@coral-xyz/anchor'; +import { NATIVE_MINT } from '@solana/spl-token'; + +import { SOL_DECIMALS } from '@/constant'; +import { selectWalletPublicKey } from '@/selectors/wallet'; +import type { Dispatch, RootState } from '@/store/store'; +import type { TokenSymbol } from '@/types'; +import { findATAAddressSync, nativeToUi } from '@/utils'; + +import { setWalletTokenBalances } from './walletBalances'; + +export const fetchWalletTokenBalances = + () => async (dispatch: Dispatch, getState: () => RootState) => { + const connection = window.adrena.mainConnection; + const walletPublicKey = selectWalletPublicKey(getState()); + + if (!walletPublicKey || !connection) { + dispatch(setWalletTokenBalances(null)); + return; + } + + const tokens = [ + ...window.adrena.client.tokens, + window.adrena.client.alpToken, + window.adrena.client.adxToken, + { mint: NATIVE_MINT, symbol: 'SOL' }, + ]; + + const balances = await Promise.all( + tokens.map(async ({ mint }) => { + const ata = findATAAddressSync(walletPublicKey, mint); + + // in case of SOL, consider both SOL in the wallet + WSOL in ATA + if (mint.equals(NATIVE_MINT)) { + try { + const [wsolBalance, solBalance] = await Promise.all([ + // Ignore ATA error if any, consider there are 0 WSOL + connection.getTokenAccountBalance(ata).catch(() => null), + connection.getBalance(walletPublicKey), + ]); + + return ( + wsolBalance?.value.uiAmount ?? + 0 + nativeToUi(new BN(solBalance), SOL_DECIMALS) + ); + } catch { + // Error loading info + return null; + } + } + + try { + const balance = await connection.getTokenAccountBalance(ata); + return balance.value.uiAmount; + } catch { + // Cannot find ATA + return null; + } + }), + ); + + dispatch( + setWalletTokenBalances( + balances.reduce((acc, balance, index) => { + acc[tokens[index].symbol] = balance; + + return acc; + }, {} as Record), + ), + ); + }; diff --git a/src/actions/walletBalances.ts b/src/actions/walletBalances.ts new file mode 100644 index 000000000..853d1514e --- /dev/null +++ b/src/actions/walletBalances.ts @@ -0,0 +1,12 @@ +import type { TokenSymbol } from '@/types'; + +export const SET_TOKEN_BALANCES_ACTION_TYPE = 'setTokenBalances' as const; + +export const setWalletTokenBalances = ( + balances: Record | null, +) => ({ + type: SET_TOKEN_BALANCES_ACTION_TYPE, + payload: balances, +}); + +export type WalletBalancesActions = ReturnType; diff --git a/src/actions/walletBalancesActions.ts b/src/actions/walletBalancesActions.ts deleted file mode 100644 index a53d30183..000000000 --- a/src/actions/walletBalancesActions.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Dispatch } from '@reduxjs/toolkit'; - -import { TokenSymbol } from '@/types'; - -export type SetTokenBalancesAction = { - type: 'setTokenBalances'; - payload: Record | null; -}; - -export type WalletBalancesAction = SetTokenBalancesAction; - -export const setWalletTokenBalancesAction = - (balances: Record | null) => - async (dispatch: Dispatch) => { - dispatch({ - type: 'setTokenBalances', - payload: balances, - }); - }; diff --git a/src/components/RefreshButton/RefreshButton.tsx b/src/components/RefreshButton/RefreshButton.tsx index 2bf3b9a61..af9bd94ad 100644 --- a/src/components/RefreshButton/RefreshButton.tsx +++ b/src/components/RefreshButton/RefreshButton.tsx @@ -2,16 +2,16 @@ import Image from 'next/image'; import React from 'react'; import { twMerge } from 'tailwind-merge'; -import useWatchWalletBalance from '@/hooks/useWatchWalletBalance'; +import { fetchWalletTokenBalances } from '@/actions/thunks'; +import { useDispatch } from '@/store/store'; import { addNotification } from '@/utils'; import refreshIcon from '../../../public/images/refresh.png'; export default function RefreshButton({ className }: { className?: string }) { - const { triggerWalletTokenBalancesReload } = useWatchWalletBalance(); - + const dispatch = useDispatch(); const handleReload = () => { - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); addNotification({ title: 'Wallet balances refreshed', type: 'success', diff --git a/src/components/pages/buy_alp_adx/ALPSwap/ALPSwap.tsx b/src/components/pages/buy_alp_adx/ALPSwap/ALPSwap.tsx index 377880a36..880cda5e2 100644 --- a/src/components/pages/buy_alp_adx/ALPSwap/ALPSwap.tsx +++ b/src/components/pages/buy_alp_adx/ALPSwap/ALPSwap.tsx @@ -7,14 +7,14 @@ import ALPSwapSell from './ALPSwapSell'; export default function ALPSwap({ className, - triggerWalletTokenBalancesReload, connected, }: { className?: string; - triggerWalletTokenBalancesReload: () => void; connected: boolean; }) { - const [selectedAction, setSelectedAction] = useState<'buy' | 'sell' | null>('buy'); + const [selectedAction, setSelectedAction] = useState<'buy' | 'sell' | null>( + 'buy', + ); return (
@@ -29,7 +29,11 @@ export default function ALPSwap({ }} /> - {selectedAction === 'buy' ? : } + {selectedAction === 'buy' ? ( + + ) : ( + + )}
); } diff --git a/src/components/pages/buy_alp_adx/ALPSwap/ALPSwapBuy.tsx b/src/components/pages/buy_alp_adx/ALPSwap/ALPSwapBuy.tsx index 0fc7d73a5..b5bca55e1 100644 --- a/src/components/pages/buy_alp_adx/ALPSwap/ALPSwapBuy.tsx +++ b/src/components/pages/buy_alp_adx/ALPSwap/ALPSwapBuy.tsx @@ -5,6 +5,7 @@ import Image from 'next/image'; import { useCallback, useEffect, useState } from 'react'; import { twMerge } from 'tailwind-merge'; +import { fetchWalletTokenBalances } from '@/actions/thunks'; import { openCloseConnectionModalAction } from '@/actions/walletActions'; import Button from '@/components/common/Button/Button'; import MultiStepNotification from '@/components/common/MultiStepNotification/MultiStepNotification'; @@ -25,11 +26,9 @@ let loadingCounterAlternativeData = 0; export default function ALPSwapBuy({ className, connected, - triggerWalletTokenBalancesReload, }: { className?: string; connected: boolean; - triggerWalletTokenBalancesReload: () => void; }) { const dispatch = useDispatch(); const walletTokenBalances = useSelector((s) => s.walletTokenBalances); @@ -85,13 +84,13 @@ export default function ALPSwapBuy({ notification, }); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); setCollateralInput(null); } catch (error) { console.log('error', error); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [alpInput, collateralInput, collateralToken.decimals, collateralToken.mint, connected, triggerWalletTokenBalancesReload, wallet && wallet.walletAddress]); + }, [alpInput, collateralInput, collateralToken.decimals, collateralToken.mint, connected, wallet && wallet.walletAddress]); const estimateAddLiquidityAndFeeForAlternativeRoutes = useCallback(async () => { // Because we fire one request every time the user change the input, needs to keep only the last one diff --git a/src/components/pages/buy_alp_adx/ALPSwap/ALPSwapSell.tsx b/src/components/pages/buy_alp_adx/ALPSwap/ALPSwapSell.tsx index b5715599d..108cabf13 100644 --- a/src/components/pages/buy_alp_adx/ALPSwap/ALPSwapSell.tsx +++ b/src/components/pages/buy_alp_adx/ALPSwap/ALPSwapSell.tsx @@ -3,6 +3,7 @@ import { PublicKey } from '@solana/web3.js'; import { useCallback, useEffect, useState } from 'react'; import { twMerge } from 'tailwind-merge'; +import { fetchWalletTokenBalances } from '@/actions/thunks'; import { openCloseConnectionModalAction } from '@/actions/walletActions'; import Button from '@/components/common/Button/Button'; import MultiStepNotification from '@/components/common/MultiStepNotification/MultiStepNotification'; @@ -19,11 +20,9 @@ let loadingCounterMainData = 0; export default function ALPSwapSell({ className, connected, - triggerWalletTokenBalancesReload, }: { className?: string; connected: boolean; - triggerWalletTokenBalancesReload: () => void; }) { const dispatch = useDispatch(); const walletTokenBalances = useSelector((s) => s.walletTokenBalances); @@ -67,13 +66,13 @@ export default function ALPSwapSell({ notification, }); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); setAlpInput(null); } catch (error) { console.log('error', error); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [alpInput, collateralInput, collateralToken.decimals, collateralToken.mint, connected, triggerWalletTokenBalancesReload, wallet && wallet.walletAddress]); + }, [alpInput, collateralInput, collateralToken.decimals, collateralToken.mint, connected, wallet && wallet.walletAddress]); const estimateRemoveLiquidityAndFee = useCallback(async () => { // Because we fire one request every time the user change the input, needs to keep only the last one diff --git a/src/components/pages/trading/TradeComp/TradeComp.tsx b/src/components/pages/trading/TradeComp/TradeComp.tsx index 6ef436a1f..0dddfc7c7 100644 --- a/src/components/pages/trading/TradeComp/TradeComp.tsx +++ b/src/components/pages/trading/TradeComp/TradeComp.tsx @@ -21,7 +21,6 @@ export default function TradeComp({ setTokenA, setTokenB, openedPosition, - triggerWalletTokenBalancesReload, className, isBigScreen, activeRpc, @@ -37,7 +36,6 @@ export default function TradeComp({ setTokenA: (t: Token | null) => void; setTokenB: (t: Token | null) => void; openedPosition: PositionExtended | null; - triggerWalletTokenBalancesReload: () => void; className?: string; isBigScreen?: boolean | null; activeRpc: { @@ -94,9 +92,6 @@ export default function TradeComp({ setTokenB={setTokenB} wallet={wallet} connected={connected} - triggerWalletTokenBalancesReload={ - triggerWalletTokenBalancesReload - } /> ) : ( <> @@ -118,9 +113,6 @@ export default function TradeComp({ setTokenB={setTokenB} wallet={wallet} connected={connected} - triggerWalletTokenBalancesReload={ - triggerWalletTokenBalancesReload - } /> )} diff --git a/src/components/pages/trading/TradingInputs/LongShortTradingInputs.tsx b/src/components/pages/trading/TradingInputs/LongShortTradingInputs.tsx index fb1ee113f..b29124932 100644 --- a/src/components/pages/trading/TradingInputs/LongShortTradingInputs.tsx +++ b/src/components/pages/trading/TradingInputs/LongShortTradingInputs.tsx @@ -7,6 +7,7 @@ import Link from 'next/link'; import { useCallback, useEffect, useState } from 'react'; import { twMerge } from 'tailwind-merge'; +import { fetchWalletTokenBalances } from '@/actions/thunks'; import { openCloseConnectionModalAction } from '@/actions/walletActions'; import AutoScalableDiv from '@/components/common/AutoScalableDiv/AutoScalableDiv'; import Button from '@/components/common/Button/Button'; @@ -56,7 +57,6 @@ export default function LongShortTradingInputs({ connected, setTokenA, setTokenB, - triggerWalletTokenBalancesReload, }: { side: 'short' | 'long'; className?: string; @@ -69,7 +69,6 @@ export default function LongShortTradingInputs({ connected: boolean; setTokenA: (t: Token | null) => void; setTokenB: (t: Token | null) => void; - triggerWalletTokenBalancesReload: () => void; }) { const dispatch = useDispatch(); const tokenPrices = useSelector((s) => s.tokenPrices); @@ -270,7 +269,7 @@ export default function LongShortTradingInputs({ notification, })); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); setInputA(null); setErrorMessage(null); diff --git a/src/components/pages/trading/TradingInputs/SwapTradingInputs.tsx b/src/components/pages/trading/TradingInputs/SwapTradingInputs.tsx index cad3e9865..748acd5fe 100644 --- a/src/components/pages/trading/TradingInputs/SwapTradingInputs.tsx +++ b/src/components/pages/trading/TradingInputs/SwapTradingInputs.tsx @@ -4,6 +4,7 @@ import Image from 'next/image'; import { useEffect, useState } from 'react'; import { twMerge } from 'tailwind-merge'; +import { fetchWalletTokenBalances } from '@/actions/thunks'; import { openCloseConnectionModalAction } from '@/actions/walletActions'; import Button from '@/components/common/Button/Button'; import MultiStepNotification from '@/components/common/MultiStepNotification/MultiStepNotification'; @@ -39,7 +40,6 @@ export default function SwapTradingInputs({ connected, setTokenA, setTokenB, - triggerWalletTokenBalancesReload, }: { className?: string; tokenA: Token; @@ -50,7 +50,6 @@ export default function SwapTradingInputs({ connected: boolean; setTokenA: (t: Token | null) => void; setTokenB: (t: Token | null) => void; - triggerWalletTokenBalancesReload: () => void; }) { const dispatch = useDispatch(); @@ -233,7 +232,7 @@ export default function SwapTradingInputs({ notification, }); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); } catch (error) { console.log('error', error); } diff --git a/src/components/pages/user_profile/Veststats.tsx b/src/components/pages/user_profile/Veststats.tsx index 615adde2b..1f50b5249 100644 --- a/src/components/pages/user_profile/Veststats.tsx +++ b/src/components/pages/user_profile/Veststats.tsx @@ -7,8 +7,10 @@ import { } from 'date-fns'; import { Doughnut } from 'react-chartjs-2'; +import { fetchWalletTokenBalances } from '@/actions/thunks'; import Button from '@/components/common/Button/Button'; import FormatNumber from '@/components/Number/FormatNumber'; +import { useDispatch } from '@/store/store'; import { Vest } from '@/types'; import { addFailedTxNotification, @@ -21,12 +23,11 @@ ChartJS.register(annotationPlugin, ArcElement, Tooltip, Legend); export default function VestStats({ vest, getUserVesting, - triggerWalletTokenBalancesReload, }: { vest: Vest; getUserVesting: () => void; - triggerWalletTokenBalancesReload: () => void; }) { + const dispatch = useDispatch(); const amount = nativeToUi( vest.amount, window.adrena.client.adxToken.decimals, @@ -76,7 +77,7 @@ export default function VestStats({ const txHash = await window.adrena.client.claimUserVest(); getUserVesting(); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); return addSuccessTxNotification({ title: 'Successfully Claimed ADX', diff --git a/src/hooks/useWatchWalletBalance.ts b/src/hooks/useWatchWalletBalance.ts deleted file mode 100644 index 5b6e5bb64..000000000 --- a/src/hooks/useWatchWalletBalance.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { BN } from '@coral-xyz/anchor'; -import { NATIVE_MINT } from '@solana/spl-token'; -import { PublicKey, RpcResponseAndContext, TokenAmount } from '@solana/web3.js'; -import { useCallback, useEffect, useState } from 'react'; - -import { setWalletTokenBalancesAction } from '@/actions/walletBalancesActions'; -import { SOL_DECIMALS } from '@/constant'; -import { useDispatch, useSelector } from '@/store/store'; -import { TokenSymbol } from '@/types'; -import { findATAAddressSync, nativeToUi } from '@/utils'; - -// TODO: Make it responsive to wallet token balance change -export default function useWatchWalletBalance(): { - triggerWalletTokenBalancesReload: () => void; -} { - const [trickReload, triggerReload] = useState(0); - const dispatch = useDispatch(); - const wallet = useSelector((s) => s.walletState.wallet); - - const loadWalletBalances = useCallback(async () => { - const connection = window.adrena.client.connection; - - if (!wallet || !dispatch || !connection) { - dispatch(setWalletTokenBalancesAction(null)); - return; - } - - console.log('Load user wallet token balances'); - - const tokens = [ - ...window.adrena.client.tokens, - window.adrena.client.alpToken, - window.adrena.client.adxToken, - { mint: NATIVE_MINT, symbol: 'SOL' }, - ]; - - const balances = await Promise.all( - tokens.map(async ({ mint }) => { - const ata = findATAAddressSync( - new PublicKey(wallet.walletAddress), - mint, - ); - - // in case of SOL, consider both SOL in the wallet + WSOL in ATA - if (mint.equals(NATIVE_MINT)) { - try { - const [wsolBalance, solBalance] = await Promise.all([ - // Ignore ATA error if any, consider there are 0 WSOL - new Promise((resolve) => { - connection - .getTokenAccountBalance(ata) - .then(resolve) - .catch(() => resolve(null)); - }) as Promise | null>, - - connection.getBalance(new PublicKey(wallet.walletAddress)), - ]); - - return ( - wsolBalance?.value.uiAmount ?? - 0 + nativeToUi(new BN(solBalance), SOL_DECIMALS) - ); - } catch { - // Error loading info - return null; - } - } - - try { - const balance = await connection.getTokenAccountBalance(ata); - return balance.value.uiAmount; - } catch { - // Cannot find ATA - return null; - } - }), - ); - - dispatch( - setWalletTokenBalancesAction( - balances.reduce((acc, balance, index) => { - acc[tokens[index].symbol] = balance; - - return acc; - }, {} as Record), - ), - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [wallet, dispatch, trickReload, window.adrena.client.connection]); - - useEffect(() => { - loadWalletBalances(); - }, [loadWalletBalances]); - - return { - triggerWalletTokenBalancesReload: () => { - triggerReload(trickReload + 1); - }, - }; -} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 6b4775f13..a76fd1bac 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -12,6 +12,7 @@ import { useEffect, useState } from 'react'; import { CookiesProvider, useCookies } from 'react-cookie'; import { Provider } from 'react-redux'; +import { fetchWalletTokenBalances } from '@/actions/thunks'; import { AdrenaClient } from '@/AdrenaClient'; import RootLayout from '@/components/layouts/RootLayout/RootLayout'; import TermsAndConditionsModal from '@/components/TermsAndConditionsModal/TermsAndConditionsModal'; @@ -25,7 +26,6 @@ import useUserProfile from '@/hooks/useUserProfile'; import useWallet from '@/hooks/useWallet'; import useWalletAdapters from '@/hooks/useWalletAdapters'; import useWatchTokenPrices from '@/hooks/useWatchTokenPrices'; -import useWatchWalletBalance from '@/hooks/useWatchWalletBalance'; import initializeApp, { createReadOnlyAdrenaProgram, } from '@/initializeApp'; @@ -38,7 +38,7 @@ import { } from '@/utils'; import logo from '../../public/images/logo.svg'; -import store from '../store/store'; +import store, { useDispatch, useSelector } from '../store/store'; function Loader(): JSX.Element { return ( @@ -85,7 +85,7 @@ export default function App(props: AppProps) { const preferredSolanaExplorer: SolanaExplorerOptions = cookies?.solanaExplorer && - SOLANA_EXPLORERS_OPTIONS.hasOwnProperty(cookies.solanaExplorer) + SOLANA_EXPLORERS_OPTIONS.hasOwnProperty(cookies.solanaExplorer) ? cookies?.solanaExplorer : 'Solana Explorer'; @@ -100,7 +100,12 @@ export default function App(props: AppProps) { activeRpc !== null ) { setInitStatus('starting'); - initializeApp(preferredSolanaExplorer, CONFIG, activeRpc.connection, PYTH_CONNECTION).then(() => { + initializeApp( + preferredSolanaExplorer, + CONFIG, + activeRpc.connection, + PYTH_CONNECTION, + ).then(() => { setInitStatus('done'); }); } @@ -126,7 +131,6 @@ export default function App(props: AppProps) { /> - ); @@ -169,6 +173,7 @@ function AppComponent({ setFavoriteRpc: (favoriteRpc: string) => void; preferredSolanaExplorer: SolanaExplorerOptions; }) { + const dispatch = useDispatch(); const router = useRouter(); const mainPool = useMainPool(); const custodies = useCustodies(mainPool); @@ -176,10 +181,15 @@ function AppComponent({ const wallet = useWallet(adapters); const positions = usePositions(); const { userProfile, triggerUserProfileReload } = useUserProfile(); + const walletAddress = useSelector((s) => s.walletState.wallet?.walletAddress); useWatchTokenPrices(); - const { triggerWalletTokenBalancesReload } = useWatchWalletBalance(); + // Fetch token balances for the connected wallet: + // on initial mount of the app & on account change. + useEffect(() => { + dispatch(fetchWalletTokenBalances()); + }, [walletAddress, dispatch]); const [cookies, setCookie] = useCookies([ 'terms-and-conditions-acceptance', @@ -355,7 +365,6 @@ function AppComponent({ mainPool={mainPool} custodies={custodies} wallet={wallet} - triggerWalletTokenBalancesReload={triggerWalletTokenBalancesReload} positions={positions} connected={connected} activeRpc={activeRpc} diff --git a/src/pages/buy_alp/index.tsx b/src/pages/buy_alp/index.tsx index 0af6afd38..7e52cea88 100644 --- a/src/pages/buy_alp/index.tsx +++ b/src/pages/buy_alp/index.tsx @@ -16,7 +16,6 @@ import infoIcon from '../../../public/images/Icons/info.svg'; export default function Buy({ connected, - triggerWalletTokenBalancesReload, mainPool, }: PageProps) { const aumUsd = useAssetsUnderManagement(); @@ -102,7 +101,6 @@ export default function Buy({ diff --git a/src/pages/faucet_devnet/index.tsx b/src/pages/faucet_devnet/index.tsx index b9f29e1db..36361a423 100644 --- a/src/pages/faucet_devnet/index.tsx +++ b/src/pages/faucet_devnet/index.tsx @@ -9,11 +9,12 @@ import Image from 'next/image'; import Link from 'next/link'; import { useState } from 'react'; +import { fetchWalletTokenBalances } from '@/actions/thunks'; import Button from '@/components/common/Button/Button'; import StyledContainer from '@/components/common/StyledContainer/StyledContainer'; import StyledSubContainer from '@/components/common/StyledSubContainer/StyledSubContainer'; import { devnetFaucetBankWallet } from '@/constant'; -import { useSelector } from '@/store/store'; +import { useDispatch, useSelector } from '@/store/store'; import { PageProps, Token } from '@/types'; import { addFailedTxNotification, @@ -27,8 +28,8 @@ import { export default function FaucetDevnet({ wallet, - triggerWalletTokenBalancesReload, }: PageProps) { + const dispatch = useDispatch(); const tokenPrices = useSelector((s) => s.tokenPrices); const [pendingTx, setPendingTx] = useState(false); @@ -87,7 +88,7 @@ export default function FaucetDevnet({ ); setPendingTx(false); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); return addSuccessTxNotification({ title: 'Successful Transaction', diff --git a/src/pages/genesis/index.tsx b/src/pages/genesis/index.tsx index a498fdb08..ca17da097 100644 --- a/src/pages/genesis/index.tsx +++ b/src/pages/genesis/index.tsx @@ -6,6 +6,7 @@ import Image from 'next/image'; import React, { useEffect, useState } from 'react'; import { twMerge } from 'tailwind-merge'; +import { fetchWalletTokenBalances } from '@/actions/thunks'; import Button from '@/components/common/Button/Button'; import Modal from '@/components/common/Modal/Modal'; import MultiStepNotification from '@/components/common/MultiStepNotification/MultiStepNotification'; @@ -23,7 +24,7 @@ import { GENESIS_REWARD_ADX_PER_USDC } from '@/constant'; import useCountDown from '@/hooks/useCountDown'; import { useDebounce } from '@/hooks/useDebounce'; import useWalletStakingAccounts from '@/hooks/useWalletStakingAccounts'; -import { useSelector } from '@/store/store'; +import { useDispatch, useSelector } from '@/store/store'; import { GenesisLock, PageProps, SolanaExplorerOptions, WalletAdapterExtended } from '@/types'; import { formatNumber, formatPriceInfo, nativeToUi, uiToNative } from '@/utils'; @@ -39,7 +40,6 @@ import xIcon from '../../../public/images/x-black-bg.png'; export default function Genesis({ connected, userProfile, - triggerWalletTokenBalancesReload, activeRpc, rpcInfos, autoRpcMode, @@ -97,6 +97,7 @@ export default function Genesis({ const [totalStakedAmount, setTotalStakedAmount] = useState( null, ); + const dispatch = useDispatch(); const from = new Date(); const campaignEndDate = genesis @@ -264,7 +265,7 @@ export default function Genesis({ notification, }); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); triggerWalletStakingAccountsReload(); setFundsAmount(null); setIsSuccess(true); diff --git a/src/pages/my_dashboard/index.tsx b/src/pages/my_dashboard/index.tsx index cbf1c8128..e18ccbdd7 100644 --- a/src/pages/my_dashboard/index.tsx +++ b/src/pages/my_dashboard/index.tsx @@ -9,6 +9,7 @@ import TradingStats from '@/components/pages/user_profile/TradingStats'; import VestStats from '@/components/pages/user_profile/Veststats'; import WalletConnection from '@/components/WalletAdapter/WalletConnection'; import useWalletStakingAccounts from '@/hooks/useWalletStakingAccounts'; +import { useDispatch } from '@/store/store'; import { PageProps, VestExtended, @@ -19,7 +20,6 @@ export default function MyDashboard({ positions, userProfile, triggerUserProfileReload, - triggerWalletTokenBalancesReload, readonly, wallet, }: PageProps & { @@ -124,9 +124,6 @@ export default function MyDashboard({ )} diff --git a/src/pages/stake/index.tsx b/src/pages/stake/index.tsx index da01212b9..d35fc1933 100644 --- a/src/pages/stake/index.tsx +++ b/src/pages/stake/index.tsx @@ -4,6 +4,7 @@ import { PublicKey } from '@solana/web3.js'; import { AnimatePresence, motion } from 'framer-motion'; import React, { useCallback, useEffect, useState } from 'react'; +import { fetchWalletTokenBalances } from '@/actions/thunks'; import Modal from '@/components/common/Modal/Modal'; import MultiStepNotification from '@/components/common/MultiStepNotification/MultiStepNotification'; import Loader from '@/components/Loader/Loader'; @@ -21,7 +22,7 @@ import useStakingAccount from '@/hooks/useStakingAccount'; import useStakingAccountRewardsAccumulated from '@/hooks/useStakingAccountRewardsAccumulated'; import { useStakingClaimableRewards } from '@/hooks/useStakingClaimableRewards'; import useWalletStakingAccounts from '@/hooks/useWalletStakingAccounts'; -import { useSelector } from '@/store/store'; +import { useDispatch, useSelector } from '@/store/store'; import { AdxLockPeriod, AlpLockPeriod, @@ -61,8 +62,8 @@ export const LIQUID_STAKE_LOCK_DURATION = 0; export default function Stake({ connected, - triggerWalletTokenBalancesReload, }: PageProps) { + const dispatch = useDispatch(); const wallet = useSelector((s) => s.walletState.wallet); const walletTokenBalances = useSelector((s) => s.walletTokenBalances); const { @@ -213,7 +214,7 @@ export default function Stake({ setAmount(null); setLockPeriod(DEFAULT_LOCKED_STAKE_LOCK_DURATION); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); triggerWalletStakingAccountsReload(); setActiveStakingToken(null); } catch (error) { @@ -245,7 +246,7 @@ export default function Stake({ notification, }); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); triggerWalletStakingAccountsReload(); setActiveRedeemLiquidADX(false); } catch (error) { @@ -282,7 +283,7 @@ export default function Stake({ notification, }); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); triggerWalletStakingAccountsReload(); setUpgradeLockedStake(false); } catch (error) { @@ -325,7 +326,7 @@ export default function Stake({ notification, }); - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); triggerWalletStakingAccountsReload(); if (earlyExit) { setLockedStake(null); @@ -385,7 +386,7 @@ export default function Stake({ source: 'optimistic', } as unknown as ClaimHistoryExtended; - triggerWalletTokenBalancesReload(); + dispatch(fetchWalletTokenBalances()); // Reset rewards in the ui until next fetch if (tokenSymbol === 'ADX') { adxRewards.pendingUsdcRewards = 0; diff --git a/src/pages/trade/index.tsx b/src/pages/trade/index.tsx index 26142deda..a1fabea1b 100644 --- a/src/pages/trade/index.tsx +++ b/src/pages/trade/index.tsx @@ -62,7 +62,6 @@ export default function Trade({ wallet, connected, triggerUserProfileReload, - triggerWalletTokenBalancesReload, activeRpc, adapters, showFeesInPnl, @@ -408,9 +407,6 @@ export default function Trade({ openedPosition={openedPosition} wallet={wallet} connected={connected} - triggerWalletTokenBalancesReload={ - triggerWalletTokenBalancesReload - } isBigScreen={isBigScreen} activeRpc={activeRpc} terminalId="integrated-terminal-1" @@ -434,7 +430,6 @@ export default function Trade({ openedPosition={openedPosition} wallet={wallet} connected={connected} - triggerWalletTokenBalancesReload={triggerWalletTokenBalancesReload} isBigScreen={isBigScreen} activeRpc={activeRpc} terminalId="integrated-terminal-2" @@ -503,9 +498,6 @@ export default function Trade({ className="p-0 m-0" wallet={wallet} connected={connected} - triggerWalletTokenBalancesReload={ - triggerWalletTokenBalancesReload - } activeRpc={activeRpc} terminalId="integrated-terminal-3" adapters={adapters} diff --git a/src/reducers/walletBalancesReducer.ts b/src/reducers/walletBalancesReducer.ts index e0adf4ae2..4299cf60e 100644 --- a/src/reducers/walletBalancesReducer.ts +++ b/src/reducers/walletBalancesReducer.ts @@ -1,6 +1,10 @@ -import { TokenSymbol } from '@/types'; +import type { DisconnectWalletAction } from '@/actions/walletActions'; +import type { TokenSymbol } from '@/types'; -import { WalletBalancesAction } from '../actions/walletBalancesActions'; +import { + SET_TOKEN_BALANCES_ACTION_TYPE, + type WalletBalancesActions, +} from '../actions/walletBalances'; export type WalletBalancesState = Record | null; @@ -8,11 +12,15 @@ const initialState: WalletBalancesState = null; export default function walletReducer( state = initialState, - action: WalletBalancesAction, + action: WalletBalancesActions | DisconnectWalletAction, ) { switch (action.type) { - case 'setTokenBalances': + case SET_TOKEN_BALANCES_ACTION_TYPE: return action.payload; + // reset wallet balances state immediately + // when the user disconnects their wallet. + case 'disconnect': + return initialState; default: return state; } diff --git a/src/selectors/wallet.ts b/src/selectors/wallet.ts new file mode 100644 index 000000000..39a60452d --- /dev/null +++ b/src/selectors/wallet.ts @@ -0,0 +1,17 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { PublicKey } from '@solana/web3.js'; + +import type { RootState } from '@/store/store'; + +export const selectWalletAddress = (state: RootState) => + state.walletState.wallet?.walletAddress ?? null; + +/** + * Memoize the computation of the base58 wallet address string to its + * PublicKey counterpart. + * - https://reselect.js.org/api/createselector/ + */ +export const selectWalletPublicKey = createSelector( + [selectWalletAddress], + (walletAddress) => (walletAddress ? new PublicKey(walletAddress) : null), +); diff --git a/src/types.d.ts b/src/types.d.ts index 34ba4c99a..a03f70e76 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -67,7 +67,6 @@ export type PageProps = { triggerUserProfileReload: () => void; custodies: CustodyExtended[] | null; wallet: Wallet | null; - triggerWalletTokenBalancesReload: () => void; positions: PositionExtended[] | null; connected: boolean; activeRpc: {