From adbc6177e792150c07e3d05d309a92bf960ae1ed Mon Sep 17 00:00:00 2001 From: Venom <184024755+0xcryptovenom@users.noreply.github.com> Date: Sun, 24 Nov 2024 06:31:48 +0100 Subject: [PATCH] contrib: refactor fetching of wallet token balances using Redux (#447) * contrib: add reselect dependency (same version as the one used by rtk-query) Enables memoization of Redux selectors * contrib: add selectors directory w/ selectWalletAddress + selectWalletPublicKey memoized selector * contrib: refactor token balances fetching using Redux - remove useWatchWalletBalance hook in favor of a fetchWalletTokenBalances thunk action. - remove props drilling from the App component to all component which may need to fetch token balances by using the fetchWalletTokenBalances thunk action. - the same pattern can be applied to most custom hooks in the code base. it allows separating business logic from the UI, better architecture which also improves testability. it will have a good impact on rendering performance with wider adoption in the code base. - clean-up walletBalances actions: - use type inference - define constant action type & use it in the matching reducer - use concise definition of action creators: a simple function returning an action object * contrib: fix wallet token balances should reset when user disconnects * contrib: fix fetchWalletTokenBalances typing issue --- src/actions/thunks.ts | 71 +++++++++++++ src/actions/walletBalances.ts | 12 +++ src/actions/walletBalancesActions.ts | 19 ---- .../RefreshButton/RefreshButton.tsx | 8 +- .../pages/buy_alp_adx/ALPSwap/ALPSwap.tsx | 12 ++- .../pages/buy_alp_adx/ALPSwap/ALPSwapBuy.tsx | 7 +- .../pages/buy_alp_adx/ALPSwap/ALPSwapSell.tsx | 7 +- .../pages/trading/TradeComp/TradeComp.tsx | 8 -- .../TradingInputs/LongShortTradingInputs.tsx | 5 +- .../TradingInputs/SwapTradingInputs.tsx | 5 +- .../pages/user_profile/Veststats.tsx | 7 +- src/hooks/useWatchWalletBalance.ts | 100 ------------------ src/pages/_app.tsx | 23 ++-- src/pages/buy_alp/index.tsx | 2 - src/pages/faucet_devnet/index.tsx | 7 +- src/pages/genesis/index.tsx | 7 +- src/pages/my_dashboard/index.tsx | 5 +- src/pages/stake/index.tsx | 15 +-- src/pages/trade/index.tsx | 8 -- src/reducers/walletBalancesReducer.ts | 16 ++- src/selectors/wallet.ts | 17 +++ src/types.d.ts | 1 - 22 files changed, 171 insertions(+), 191 deletions(-) create mode 100644 src/actions/thunks.ts create mode 100644 src/actions/walletBalances.ts delete mode 100644 src/actions/walletBalancesActions.ts delete mode 100644 src/hooks/useWatchWalletBalance.ts create mode 100644 src/selectors/wallet.ts 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: {