Skip to content

Commit

Permalink
contrib: refactor fetching of wallet token balances using Redux (#447)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
0xcryptovenom authored Nov 24, 2024
1 parent 6fea0c4 commit adbc617
Show file tree
Hide file tree
Showing 22 changed files with 171 additions and 191 deletions.
71 changes: 71 additions & 0 deletions src/actions/thunks.ts
Original file line number Diff line number Diff line change
@@ -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<TokenSymbol, number | null>),
),
);
};
12 changes: 12 additions & 0 deletions src/actions/walletBalances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { TokenSymbol } from '@/types';

export const SET_TOKEN_BALANCES_ACTION_TYPE = 'setTokenBalances' as const;

export const setWalletTokenBalances = (
balances: Record<TokenSymbol, number | null> | null,
) => ({
type: SET_TOKEN_BALANCES_ACTION_TYPE,
payload: balances,
});

export type WalletBalancesActions = ReturnType<typeof setWalletTokenBalances>;
19 changes: 0 additions & 19 deletions src/actions/walletBalancesActions.ts

This file was deleted.

8 changes: 4 additions & 4 deletions src/components/RefreshButton/RefreshButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
12 changes: 8 additions & 4 deletions src/components/pages/buy_alp_adx/ALPSwap/ALPSwap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className={className}>
Expand All @@ -29,7 +29,11 @@ export default function ALPSwap({
}}
/>

{selectedAction === 'buy' ? <ALPSwapBuy connected={connected} triggerWalletTokenBalancesReload={triggerWalletTokenBalancesReload} /> : <ALPSwapSell connected={connected} triggerWalletTokenBalancesReload={triggerWalletTokenBalancesReload} />}
{selectedAction === 'buy' ? (
<ALPSwapBuy connected={connected} />
) : (
<ALPSwapSell connected={connected} />
)}
</div>
);
}
7 changes: 3 additions & 4 deletions src/components/pages/buy_alp_adx/ALPSwap/ALPSwapBuy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down
7 changes: 3 additions & 4 deletions src/components/pages/buy_alp_adx/ALPSwap/ALPSwapSell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down
8 changes: 0 additions & 8 deletions src/components/pages/trading/TradeComp/TradeComp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export default function TradeComp({
setTokenA,
setTokenB,
openedPosition,
triggerWalletTokenBalancesReload,
className,
isBigScreen,
activeRpc,
Expand All @@ -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: {
Expand Down Expand Up @@ -94,9 +92,6 @@ export default function TradeComp({
setTokenB={setTokenB}
wallet={wallet}
connected={connected}
triggerWalletTokenBalancesReload={
triggerWalletTokenBalancesReload
}
/>
) : (
<>
Expand All @@ -118,9 +113,6 @@ export default function TradeComp({
setTokenB={setTokenB}
wallet={wallet}
connected={connected}
triggerWalletTokenBalancesReload={
triggerWalletTokenBalancesReload
}
/>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -56,7 +57,6 @@ export default function LongShortTradingInputs({
connected,
setTokenA,
setTokenB,
triggerWalletTokenBalancesReload,
}: {
side: 'short' | 'long';
className?: string;
Expand All @@ -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);
Expand Down Expand Up @@ -270,7 +269,7 @@ export default function LongShortTradingInputs({
notification,
}));

triggerWalletTokenBalancesReload();
dispatch(fetchWalletTokenBalances());

setInputA(null);
setErrorMessage(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -39,7 +40,6 @@ export default function SwapTradingInputs({
connected,
setTokenA,
setTokenB,
triggerWalletTokenBalancesReload,
}: {
className?: string;
tokenA: Token;
Expand All @@ -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();

Expand Down Expand Up @@ -233,7 +232,7 @@ export default function SwapTradingInputs({
notification,
});

triggerWalletTokenBalancesReload();
dispatch(fetchWalletTokenBalances());
} catch (error) {
console.log('error', error);
}
Expand Down
7 changes: 4 additions & 3 deletions src/components/pages/user_profile/Veststats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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',
Expand Down
Loading

0 comments on commit adbc617

Please sign in to comment.