Skip to content

Commit

Permalink
chore: fetch latest src token balance
Browse files Browse the repository at this point in the history
  • Loading branch information
micaelae committed Oct 10, 2024
1 parent 27996cc commit 5ad1783
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 8 deletions.
88 changes: 88 additions & 0 deletions ui/hooks/bridge/useLatestBalance.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { BigNumber } from 'ethers';
import { renderHookWithProvider } from '../../../test/lib/render-helpers';
import { CHAIN_IDS } from '../../../shared/constants/network';
import { createBridgeMockStore } from '../../../test/jest/mock-store';
import { zeroAddress } from '../../__mocks__/ethereumjs-util';
import { createTestProviderTools } from '../../../test/stub/provider';
import * as tokenutil from '../../../shared/lib/token-util';
import useLatestBalance from './useLatestBalance';

const mockGetBalance = jest.fn();
jest.mock('@ethersproject/providers', () => {
return {
Web3Provider: jest.fn().mockImplementation(() => {
return {
getBalance: mockGetBalance,
};
}),
};
});

const mockFetchTokenBalance = jest.spyOn(tokenutil, 'fetchTokenBalance');
jest.mock('../../../shared/lib/token-util', () => ({
...jest.requireActual('../../../shared/lib/token-util'),
fetchTokenBalance: jest.fn(),
}));

const renderUseLatestBalance = (
token: { address: string; decimals?: number | string },
chainId: string,
mockStoreState: object,
) =>
renderHookWithProvider(
() => useLatestBalance(token as any, chainId as any),
mockStoreState,
);

describe('useLatestBalance', () => {
beforeEach(() => {
jest.clearAllMocks();
const { provider } = createTestProviderTools({
networkId: 'Ethereum',
chainId: CHAIN_IDS.MAINNET,
});

// eslint-disable-next-line @typescript-eslint/no-explicit-any
global.ethereumProvider = provider as any;
});

it('returns formattedBalance for native asset in current chain', async () => {
mockGetBalance.mockResolvedValue(BigNumber.from('1000000000000000000'));

const { result, waitForNextUpdate } = renderUseLatestBalance(
{ address: zeroAddress() },
CHAIN_IDS.MAINNET,
createBridgeMockStore(),
);

await waitForNextUpdate();
expect(result.current.formattedBalance).toStrictEqual('1');

expect(mockGetBalance).toHaveBeenCalledTimes(1);
expect(mockGetBalance).toHaveBeenCalledWith(
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
);
expect(mockFetchTokenBalance).toHaveBeenCalledTimes(0);
});

it('returns formattedBalance for ERC20 asset in current chain', async () => {
mockFetchTokenBalance.mockResolvedValueOnce(BigNumber.from('15390000'));

const { result, waitForNextUpdate } = renderUseLatestBalance(
{ address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', decimals: '6' },
CHAIN_IDS.MAINNET,
createBridgeMockStore(),
);

await waitForNextUpdate();
expect(result.current.formattedBalance).toStrictEqual('15.39');

expect(mockFetchTokenBalance).toHaveBeenCalledTimes(1);
expect(mockFetchTokenBalance).toHaveBeenCalledWith(
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
global.ethereumProvider,
);
expect(mockGetBalance).toHaveBeenCalledTimes(0);
});
});
58 changes: 58 additions & 0 deletions ui/hooks/bridge/useLatestBalance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useSelector } from 'react-redux';
import { zeroAddress } from 'ethereumjs-util';
import { Web3Provider } from '@ethersproject/providers';
import { Hex } from '@metamask/utils';
import { BigNumber } from 'ethers';
import { Numeric } from '../../../shared/modules/Numeric';
import { DEFAULT_PRECISION } from '../useCurrencyDisplay';
import { fetchTokenBalance } from '../../../shared/lib/token-util';
import {
getCurrentChainId,
getSelectedInternalAccount,
SwapsEthToken,
} from '../../selectors';
import { SwapsTokenObject } from '../../../shared/constants/swaps';
import { useAsyncResult } from '../useAsyncResult';

/**
* Custom hook to fetch and format the latest balance of a given token or native asset.
*
* @param token - The token object for which the balance is to be fetched. Can be null.
* @param chainId - The chain ID to be used for fetching the balance. Optional.
* @returns An object containing the formatted balance as a string.
*/
const useLatestBalance = (
token: SwapsTokenObject | SwapsEthToken | null,
chainId?: Hex,
) => {
const { address: selectedAddress } = useSelector(getSelectedInternalAccount);
const currentChainId = useSelector(getCurrentChainId);

const { value: latestBalance } = useAsyncResult<BigNumber>(async () => {
if (token && chainId && currentChainId === chainId) {
if (!token.address || token.address === zeroAddress()) {
const ethersProvider = new Web3Provider(global.ethereumProvider);
return await ethersProvider.getBalance(selectedAddress);
}
return await fetchTokenBalance(
token.address,
selectedAddress,
global.ethereumProvider,
);
}
// TODO implement fetching balance on non-active chain and update unit test
return undefined;
}, [token, selectedAddress, global.ethereumProvider]);

return {
formattedBalance:
token && latestBalance
? Numeric.from(latestBalance.toString(), 10)
.shiftedBy(token?.decimals ? Number(token.decimals) : 18)
.round(DEFAULT_PRECISION)
.toString()
: undefined,
};
};

export default useLatestBalance;
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ exports[`PrepareBridgePage should render the component 1`] = `
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
Balance
:
"0"
</p>
<div
class="mm-box currency-display-component mm-box--display-flex mm-box--flex-wrap-wrap mm-box--align-items-center"
Expand Down Expand Up @@ -184,9 +182,7 @@ exports[`PrepareBridgePage should render the component 1`] = `
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
Balance
:
"0"
</p>
<div
class="mm-box currency-display-component mm-box--display-flex mm-box--flex-wrap-wrap mm-box--align-items-center"
Expand Down
8 changes: 6 additions & 2 deletions ui/pages/bridge/prepare/bridge-input-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
CHAIN_ID_TO_CURRENCY_SYMBOL_MAP,
CHAIN_ID_TOKEN_IMAGE_MAP,
} from '../../../../shared/constants/network';
import useLatestBalance from '../../../hooks/bridge/useLatestBalance';

const generateAssetFromToken = (
chainId: Hex,
Expand Down Expand Up @@ -91,7 +92,10 @@ export const BridgeInputGroup = ({
true,
);

const latestBalance = '0';
const { formattedBalance } = useLatestBalance(
token,
networkProps?.network?.chainId,
);

return (
<Box className={className}>
Expand Down Expand Up @@ -132,7 +136,7 @@ export const BridgeInputGroup = ({
</Box>
<Box className="prepare-bridge-page__amounts-row">
<Text>
{t('balance')}: {JSON.stringify(latestBalance)}
{formattedBalance ? `${t('balance')}: ${formattedBalance}` : ' '}
</Text>
<CurrencyDisplay
currency="usd"
Expand Down
11 changes: 11 additions & 0 deletions ui/pages/bridge/prepare/prepare-bridge-page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@ import { renderWithProvider } from '../../../../test/jest';
import configureStore from '../../../store/store';
import { createBridgeMockStore } from '../../../../test/jest/mock-store';
import { CHAIN_IDS } from '../../../../shared/constants/network';
import { createTestProviderTools } from '../../../../test/stub/provider';
import PrepareBridgePage from './prepare-bridge-page';

describe('PrepareBridgePage', () => {
beforeAll(() => {
const { provider } = createTestProviderTools({
networkId: 'Ethereum',
chainId: CHAIN_IDS.MAINNET,
});

// eslint-disable-next-line @typescript-eslint/no-explicit-any
global.ethereumProvider = provider as any;
});

it('should render the component', () => {
const mockStore = createBridgeMockStore(
{
Expand Down

0 comments on commit 5ad1783

Please sign in to comment.