A Metamask fork with Infura removed and default networks editable
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
ciphermask/ui/hooks/useTokenTracker.js

117 lines
4.1 KiB

import { useState, useEffect, useRef, useCallback } from 'react';
import TokenTracker from '@metamask/eth-token-tracker';
import { useSelector } from 'react-redux';
import { getCurrentChainId, getSelectedAddress } from '../selectors';
import { SECOND } from '../../shared/constants/time';
import { useEqualityCheck } from './useEqualityCheck';
export function useTokenTracker(
tokens,
includeFailedTokens = false,
hideZeroBalanceTokens = false,
) {
const chainId = useSelector(getCurrentChainId);
const userAddress = useSelector(getSelectedAddress);
const [loading, setLoading] = useState(() => tokens?.length >= 0);
const [tokensWithBalances, setTokensWithBalances] = useState([]);
const [error, setError] = useState(null);
const tokenTracker = useRef(null);
const memoizedTokens = useEqualityCheck(tokens);
const updateBalances = useCallback(
(tokenWithBalances) => {
const matchingTokens = hideZeroBalanceTokens
? tokenWithBalances.filter((token) => Number(token.balance) > 0)
: tokenWithBalances;
// TODO: improve this pattern for adding this field when we improve support for
// EIP721 tokens.
const matchingTokensWithIsERC721Flag = matchingTokens.map((token) => {
const additionalTokenData = memoizedTokens.find(
(t) => t.address === token.address,
);
return { ...token, isERC721: additionalTokenData?.isERC721 };
});
setTokensWithBalances(matchingTokensWithIsERC721Flag);
setLoading(false);
setError(null);
},
[hideZeroBalanceTokens, memoizedTokens],
);
const showError = useCallback((err) => {
setError(err);
setLoading(false);
}, []);
const teardownTracker = useCallback(() => {
if (tokenTracker.current) {
tokenTracker.current.stop();
tokenTracker.current.removeAllListeners('update');
tokenTracker.current.removeAllListeners('error');
tokenTracker.current = null;
}
}, []);
const buildTracker = useCallback(
(address, tokenList) => {
// clear out previous tracker, if it exists.
teardownTracker();
tokenTracker.current = new TokenTracker({
userAddress: address,
provider: global.ethereumProvider,
tokens: tokenList,
includeFailedTokens,
pollingInterval: SECOND * 8,
});
tokenTracker.current.on('update', updateBalances);
tokenTracker.current.on('error', showError);
tokenTracker.current.updateBalances();
},
[updateBalances, includeFailedTokens, showError, teardownTracker],
);
// Effect to remove the tracker when the component is removed from DOM
// Do not overload this effect with additional dependencies. teardownTracker
// is the only dependency here, which itself has no dependencies and will
// never update. The lack of dependencies that change is what confirms
// that this effect only runs on mount/unmount
useEffect(() => {
return teardownTracker;
}, [teardownTracker]);
// Effect to set loading state and initialize tracker when values change
useEffect(() => {
// This effect will only run initially and when:
// 1. chainId is updated,
// 2. userAddress is changed,
// 3. token list is updated and not equal to previous list
// in any of these scenarios, we should indicate to the user that their token
// values are in the process of updating by setting loading state.
setLoading(true);
if (!userAddress || chainId === undefined || !global.ethereumProvider) {
// If we do not have enough information to build a TokenTracker, we exit early
// When the values above change, the effect will be restarted. We also teardown
// tracker because inevitably this effect will run again momentarily.
teardownTracker();
return;
}
if (memoizedTokens.length === 0) {
// sets loading state to false and token list to empty
updateBalances([]);
}
buildTracker(userAddress, memoizedTokens);
}, [
userAddress,
teardownTracker,
chainId,
memoizedTokens,
updateBalances,
buildTracker,
]);
return { loading, tokensWithBalances, error };
}