mirror of
https://github.com/RabbyHub/Rabby.git
synced 2026-05-10 03:00:26 -04:00
chore: refactor token&protocol manage (#3653)
* chore: remove dead code * chore: useToken params * chore: useQueryProjects params * chore: clean dead code * chore: remove blocked token logic * chore: remove blocked token logic * chore: sort * chore: remove dead code * chore: remove customize tokens logic * chore: refactor useTokens * fix: ignore db error for tokens * chore: DisplayedToken to parseTokenItem * chore: patch other DisplayedToken * fix: token update in token item * feat: catch db error for protocol data * fix: sort import * chore: clean dead code * fix: bugs
This commit is contained in:
@@ -231,19 +231,14 @@ const TokenAmountInput = ({
|
||||
tokens: allTokens,
|
||||
isLoading: isLoadingAllTokens,
|
||||
isAllTokenLoading, // 包含lpToken
|
||||
} = useTokens(
|
||||
currentAccount?.address,
|
||||
undefined,
|
||||
selectorOpened.current ? tokenSelectorVisible : true,
|
||||
} = useTokens(currentAccount?.address, {
|
||||
visible: selectorOpened.current ? tokenSelectorVisible : true,
|
||||
updateNonce,
|
||||
mainnetChainServerId,
|
||||
undefined,
|
||||
isFromMode ? lpTokenMode : undefined, // only show lp tokens in from mode
|
||||
undefined,
|
||||
!!keyword,
|
||||
false,
|
||||
true
|
||||
);
|
||||
chainServerId: mainnetChainServerId,
|
||||
lpTokensOnly: isFromMode ? lpTokenMode : undefined, // only show lp tokens in from mode
|
||||
searchMode: !!keyword,
|
||||
realtimeMode: true,
|
||||
});
|
||||
|
||||
const handleSelectToken = useCallback(() => {
|
||||
if (allTokens.length > 0) {
|
||||
|
||||
@@ -198,19 +198,14 @@ const TokenSelect = forwardRef<
|
||||
tokens: allTokens,
|
||||
isLoading: isLoadingAllTokens,
|
||||
isAllTokenLoading, // 包含lp Token的请求
|
||||
} = useTokens(
|
||||
useSwapTokenList ? undefined : currentAccount?.address,
|
||||
undefined,
|
||||
tokenSelectorVisible,
|
||||
} = useTokens(useSwapTokenList ? undefined : currentAccount?.address, {
|
||||
visible: tokenSelectorVisible,
|
||||
updateNonce,
|
||||
queryConds.chainServerId,
|
||||
undefined,
|
||||
isFromMode ? lpTokenMode : undefined, // only show lp tokens in from mode
|
||||
undefined,
|
||||
!!queryConds.keyword,
|
||||
false,
|
||||
true
|
||||
);
|
||||
chainServerId: queryConds.chainServerId,
|
||||
lpTokensOnly: isFromMode ? lpTokenMode : undefined, // only show lp tokens in from mode
|
||||
searchMode: !!queryConds.keyword,
|
||||
realtimeMode: true,
|
||||
});
|
||||
|
||||
const {
|
||||
value: swapTokenList,
|
||||
|
||||
@@ -701,7 +701,7 @@ function CommonTokenItem(props: {
|
||||
);
|
||||
}, [isSwapTo, isBridgeTo, supportChains, chainItem]);
|
||||
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const { value: remoteValue, loading, error } = useAsync(async () => {
|
||||
if (updateToken && currentAccount?.address) {
|
||||
const data = await wallet.openapi.getToken(
|
||||
currentAccount?.address,
|
||||
@@ -710,9 +710,13 @@ function CommonTokenItem(props: {
|
||||
);
|
||||
return data;
|
||||
}
|
||||
return token;
|
||||
return undefined;
|
||||
}, [currentAccount?.address, updateToken, token?.chain, token?.id]);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return remoteValue ? remoteValue : token;
|
||||
}, [remoteValue, token]);
|
||||
|
||||
const tips = useMemo(() => {
|
||||
return disabled ? t('component.TokenSelector.chainNotSupport') : undefined;
|
||||
}, [disabled, t]);
|
||||
|
||||
@@ -35,11 +35,7 @@ export const formatAppChain = (app: AppChain): DisplayChainWithWhiteLogo => {
|
||||
isAppChain: true,
|
||||
};
|
||||
};
|
||||
export const useAppChain = (
|
||||
userAddr: string | undefined,
|
||||
visible = true,
|
||||
isTestnet = false
|
||||
) => {
|
||||
export const useAppChain = (userAddr: string | undefined, visible = true) => {
|
||||
const abortProcess = useRef<AbortController>();
|
||||
const [appChains, setAppChains] = useSafeState<AppChainItem[]>([]);
|
||||
const [data, setData] = useSafeState<DisplayedProject[]>([]);
|
||||
@@ -107,11 +103,6 @@ export const useAppChain = (
|
||||
setData([]);
|
||||
setHasValue(false);
|
||||
|
||||
if (isTestnet) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let currentAppChains: AppChainItem[] = [];
|
||||
const matchedAccount = await wallet.getAccountByAddress(userAddr);
|
||||
const shouldPersistAppChainCache = matchedAccount
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
||||
import { useWallet } from '../utils/WalletContext';
|
||||
import { TokenItem } from '@rabby-wallet/rabby-api/dist/types';
|
||||
import {
|
||||
DisplayedToken,
|
||||
encodeProjectTokenId,
|
||||
} from '../utils/portfolio/project';
|
||||
import { encodeProjectTokenId } from '../utils/portfolio/project';
|
||||
import { AbstractPortfolioToken } from '../utils/portfolio/types';
|
||||
import { useRabbyDispatch, useRabbySelector } from 'ui/store';
|
||||
import { isSameAddress } from '../utils';
|
||||
@@ -14,6 +11,7 @@ import { Chain } from '@debank/common';
|
||||
import useSyncStaleValue from './useDebounceValue';
|
||||
import { useRefState } from './useRefState';
|
||||
import { safeBuildRegExp } from '@/utils/string';
|
||||
import { parseTokenItem } from '../utils/portfolio/tokenUtils';
|
||||
|
||||
function isSearchInputWeb3Address(q: string) {
|
||||
return q.length === 42 && q.toLowerCase().startsWith('0x');
|
||||
@@ -101,12 +99,10 @@ export function useOperateCustomToken() {
|
||||
if (!tokenWithAmount) return;
|
||||
|
||||
if (tokenWithAmount.is_core) {
|
||||
return dispatch.account.addBlockedToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
);
|
||||
return dispatch.account.addBlockedToken(parseTokenItem(tokenWithAmount));
|
||||
} else {
|
||||
return dispatch.account.addCustomizeToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
parseTokenItem(tokenWithAmount)
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
@@ -116,11 +112,11 @@ export function useOperateCustomToken() {
|
||||
|
||||
if (tokenWithAmount?.is_core) {
|
||||
return dispatch.account.removeBlockedToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
parseTokenItem(tokenWithAmount)
|
||||
);
|
||||
} else {
|
||||
return dispatch.account.removeCustomizeToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
parseTokenItem(tokenWithAmount)
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
@@ -224,9 +220,7 @@ export function useFindCustomToken(input?: {
|
||||
// });
|
||||
|
||||
lists.portfolioTokenList = [
|
||||
...(lists.tokenList.map(
|
||||
(item) => new DisplayedToken(item)
|
||||
) as AbstractPortfolioToken[]),
|
||||
...lists.tokenList.map(parseTokenItem),
|
||||
// ...matchCustomTokens,
|
||||
].filter((item) => {
|
||||
const isBlocked = !!blocked.find((b) =>
|
||||
@@ -346,9 +340,7 @@ const useSearchToken = (
|
||||
setIsLoading(false);
|
||||
setResult(
|
||||
[
|
||||
...(list.map(
|
||||
(item) => new DisplayedToken(item)
|
||||
) as AbstractPortfolioToken[]),
|
||||
...list.map((item) => parseTokenItem(item)),
|
||||
...matchCustomTokens,
|
||||
].filter((item) => {
|
||||
const isBlocked = !!blocked.find((b) =>
|
||||
|
||||
@@ -1,59 +1,38 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useSafeState } from '../safeState';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useTokens } from './token';
|
||||
import { usePortfolios } from './usePortfolio';
|
||||
|
||||
const Cache_Timeout = 5 * 60;
|
||||
type UseQueryProjectsOptions = {
|
||||
visible?: boolean;
|
||||
lpTokenMode?: boolean;
|
||||
searchMode?: boolean;
|
||||
autoLoad?: boolean;
|
||||
};
|
||||
|
||||
export const useQueryProjects = (
|
||||
userAddr: string | undefined,
|
||||
withHistory = false,
|
||||
visible: boolean,
|
||||
isTestnet = false,
|
||||
lpTokenMode = false,
|
||||
showBlocked = false,
|
||||
searchMode = false,
|
||||
autoLoad = true
|
||||
{
|
||||
visible = false,
|
||||
lpTokenMode = false,
|
||||
searchMode = false,
|
||||
autoLoad = true,
|
||||
}: UseQueryProjectsOptions = {}
|
||||
) => {
|
||||
const [time, setTime] = useSafeState(dayjs().subtract(1, 'day'));
|
||||
const shouldAutoLoad = visible && autoLoad;
|
||||
|
||||
useEffect(() => {
|
||||
if (time!.add(1, 'day').add(Cache_Timeout, 's').isBefore(dayjs())) {
|
||||
// refreshPositions();
|
||||
}
|
||||
}, [time]);
|
||||
|
||||
const historyTime = useMemo(() => (withHistory ? time : undefined), [
|
||||
withHistory,
|
||||
time,
|
||||
]);
|
||||
|
||||
const {
|
||||
tokens,
|
||||
netWorth: tokenNetWorth,
|
||||
isLoading: isTokensLoading,
|
||||
isAllTokenLoading,
|
||||
hasValue: hasTokens,
|
||||
updateData: updateTokens,
|
||||
walletProject,
|
||||
customizeTokens,
|
||||
blockedTokens,
|
||||
} = useTokens(
|
||||
userAddr,
|
||||
historyTime,
|
||||
shouldAutoLoad,
|
||||
0,
|
||||
undefined,
|
||||
isTestnet,
|
||||
lpTokenMode,
|
||||
showBlocked,
|
||||
} = useTokens(userAddr, {
|
||||
visible: shouldAutoLoad,
|
||||
lpTokensOnly: lpTokenMode,
|
||||
searchMode,
|
||||
true // disableRecommended
|
||||
);
|
||||
disableRecommended: true,
|
||||
});
|
||||
|
||||
const {
|
||||
data: portfolios,
|
||||
@@ -62,32 +41,23 @@ export const useQueryProjects = (
|
||||
netWorth: portfolioNetWorth,
|
||||
updateData: updatePortfolio,
|
||||
removeProtocol,
|
||||
} = usePortfolios(userAddr, historyTime, shouldAutoLoad, isTestnet);
|
||||
} = usePortfolios(userAddr, shouldAutoLoad);
|
||||
|
||||
const refreshPositions = useCallback(() => {
|
||||
if (!autoLoad || (!isTokensLoading && !isPortfoliosLoading)) {
|
||||
updatePortfolio();
|
||||
updateTokens();
|
||||
setTime(dayjs().subtract(1, 'day'));
|
||||
}
|
||||
}, [
|
||||
updatePortfolio,
|
||||
updateTokens,
|
||||
isTokensLoading,
|
||||
isPortfoliosLoading,
|
||||
setTime,
|
||||
autoLoad,
|
||||
]);
|
||||
|
||||
const grossNetWorth = useMemo(() => tokenNetWorth + portfolioNetWorth!, [
|
||||
tokenNetWorth,
|
||||
portfolioNetWorth,
|
||||
]);
|
||||
|
||||
return {
|
||||
tokenNetWorth,
|
||||
portfolioNetWorth,
|
||||
grossNetWorth,
|
||||
refreshPositions,
|
||||
refreshTokens: updateTokens,
|
||||
refreshPortfolios: updatePortfolio,
|
||||
@@ -97,10 +67,7 @@ export const useQueryProjects = (
|
||||
hasTokens,
|
||||
hasPortfolios,
|
||||
tokens,
|
||||
customizeTokens,
|
||||
blockedTokens,
|
||||
portfolios,
|
||||
walletProject,
|
||||
removeProtocol,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,113 +1,83 @@
|
||||
import { useRef, useEffect, useMemo, useCallback } from 'react';
|
||||
import produce from 'immer';
|
||||
import { Dayjs } from 'dayjs';
|
||||
import { TokenItem } from '@rabby-wallet/rabby-api/dist/types';
|
||||
import { useRabbyDispatch, useRabbySelector } from 'ui/store';
|
||||
import { CACHE_VALID_DURATION, TOKEN_SYNC_SCENE } from '@/db/constants';
|
||||
import {
|
||||
findChainByEnum,
|
||||
isTestnet as checkIsTestnet,
|
||||
findChain,
|
||||
} from '@/utils/chain';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { isFullVersionAccountType } from '@/utils/account';
|
||||
import { syncDbService } from '@/db/services/syncDbService';
|
||||
import { useRabbyDispatch, useRabbySelector } from 'ui/store';
|
||||
import { tokenDbService } from '@/db/services/tokenDbService';
|
||||
import { useWallet } from '../WalletContext';
|
||||
import { useSafeState } from '../safeState';
|
||||
import { log } from './usePortfolio';
|
||||
import {
|
||||
PortfolioItem,
|
||||
PortfolioItemToken,
|
||||
} from '@rabby-wallet/rabby-api/dist/types';
|
||||
import { uniqBy } from 'lodash';
|
||||
import { DisplayedProject, DisplayedToken } from './project';
|
||||
import { AbstractPortfolioToken } from './types';
|
||||
import { getMissedTokenPrice } from './utils';
|
||||
import {
|
||||
walletProject,
|
||||
batchQueryTokens,
|
||||
batchQueryHistoryTokens,
|
||||
setWalletTokens,
|
||||
queryTokensCache,
|
||||
sortWalletTokens,
|
||||
} from './tokenUtils';
|
||||
import { TokenItem } from '@rabby-wallet/rabby-api/dist/types';
|
||||
import { CACHE_VALID_DURATION, TOKEN_SYNC_SCENE } from '@/db/constants';
|
||||
import { findChain } from '@/utils/chain';
|
||||
|
||||
import { isSameAddress } from '..';
|
||||
import { Token } from 'background/service/preference';
|
||||
import { log } from './usePortfolio';
|
||||
import { useSafeState } from '../safeState';
|
||||
import { useWallet } from '../WalletContext';
|
||||
import { AbstractPortfolioToken } from './types';
|
||||
import {
|
||||
defaultTokenFilter,
|
||||
includeLpTokensFilter,
|
||||
isLpToken,
|
||||
} from './lpToken';
|
||||
import { useAsync } from 'react-use';
|
||||
import {
|
||||
batchQueryTokens,
|
||||
queryTokensCache,
|
||||
filterValidChainTokens,
|
||||
replaceCoreTokens,
|
||||
replaceTokensWithLatest,
|
||||
parseTokenItem,
|
||||
sortTokenItems,
|
||||
} from './tokenUtils';
|
||||
|
||||
let lastResetTokenListAddr = '';
|
||||
// export const tokenChangeLoadingAtom = atom(false);
|
||||
|
||||
const filterDisplayToken = (
|
||||
tokens: AbstractPortfolioToken[],
|
||||
blocked: Token[]
|
||||
) => {
|
||||
return tokens.filter((token) => {
|
||||
const chain = findChain({
|
||||
serverId: token.chain,
|
||||
});
|
||||
return (
|
||||
!blocked.find(
|
||||
(item) =>
|
||||
isSameAddress(token._tokenId, item.address) &&
|
||||
item.chain === token.chain
|
||||
) && findChainByEnum(chain?.enum)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const buildTokenKey = (token: Pick<TokenItem, 'chain' | 'id'>) =>
|
||||
`${token.chain}-${token.id.toLowerCase()}`;
|
||||
|
||||
const uniqTokens = (tokens: TokenItem[]) => {
|
||||
return uniqBy(tokens, buildTokenKey);
|
||||
};
|
||||
|
||||
/** 替换核心 token */
|
||||
const replaceCoreTokens = (tokens: TokenItem[], cacheTokens: TokenItem[]) => {
|
||||
return uniqTokens([
|
||||
...tokens.filter((token) => !token.is_core),
|
||||
...cacheTokens,
|
||||
]);
|
||||
type UseTokensOptions = {
|
||||
visible?: boolean;
|
||||
updateNonce?: number;
|
||||
chainServerId?: string;
|
||||
lpTokensOnly?: boolean;
|
||||
searchMode?: boolean;
|
||||
disableRecommended?: boolean;
|
||||
realtimeMode?: boolean;
|
||||
};
|
||||
|
||||
export const useTokens = (
|
||||
userAddr: string | undefined,
|
||||
timeAt?: Dayjs,
|
||||
visible = true,
|
||||
updateNonce = 0,
|
||||
chainServerId?: string,
|
||||
isTestnet: boolean = chainServerId
|
||||
? !!findChain({ serverId: chainServerId })?.isTestnet
|
||||
: false,
|
||||
lpTokensOnly = false,
|
||||
showBlocked = false,
|
||||
searchMode = false,
|
||||
disableRecommended = false,
|
||||
realtimeMode = false
|
||||
{
|
||||
visible = true,
|
||||
updateNonce = 0,
|
||||
chainServerId,
|
||||
lpTokensOnly = false,
|
||||
searchMode = false,
|
||||
disableRecommended = false,
|
||||
realtimeMode = false,
|
||||
}: UseTokensOptions = {}
|
||||
) => {
|
||||
const abortProcess = useRef<AbortController>();
|
||||
const [data, setData] = useSafeState(walletProject);
|
||||
const [isLoading, setLoading] = useSafeState(true);
|
||||
const [isAllTokenLoading, setIsAllTokenLoading] = useSafeState(true);
|
||||
const historyTime = useRef<number>();
|
||||
const historyLoad = useRef<boolean>(false);
|
||||
const wallet = useWallet();
|
||||
const dispatch = useRabbyDispatch();
|
||||
|
||||
const { mainnetTokens, testnetTokens } = useRabbySelector((store) => ({
|
||||
mainnetTokens: store.account.tokens,
|
||||
testnetTokens: store.account.testnetTokens,
|
||||
}));
|
||||
|
||||
const [hasValue, setHasValue] = useSafeState(false);
|
||||
const [isLoading, setLoading] = useSafeState(true);
|
||||
const [isAllTokenLoading, setIsAllTokenLoading] = useSafeState(true);
|
||||
|
||||
const userAddrRef = useRef('');
|
||||
const chainIdRef = useRef<string | undefined>(undefined);
|
||||
// const setTokenChangeLoading = useSetAtom(tokenChangeLoadingAtom);
|
||||
|
||||
const abortProcess = useRef<AbortController>();
|
||||
const callCountRef = useRef(0);
|
||||
|
||||
const isTestnet = useMemo(() => {
|
||||
return chainServerId
|
||||
? !!findChain({ serverId: chainServerId })?.isTestnet
|
||||
: false;
|
||||
}, [chainServerId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (updateNonce === 0) return;
|
||||
loadProcess();
|
||||
@@ -133,7 +103,7 @@ export const useTokens = (
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setData(undefined);
|
||||
setHasValue(false);
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -144,19 +114,6 @@ export const useTokens = (
|
||||
};
|
||||
}, [userAddr, visible, chainServerId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (timeAt) {
|
||||
historyTime.current = timeAt.unix();
|
||||
|
||||
if (!isLoading) {
|
||||
loadHistory();
|
||||
}
|
||||
} else {
|
||||
historyTime.current = 0;
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [timeAt, isLoading]);
|
||||
|
||||
const loadProcess = async ({ forceRefresh = false } = {}) => {
|
||||
callCountRef.current++;
|
||||
const callCount = callCountRef.current;
|
||||
@@ -183,30 +140,26 @@ export const useTokens = (
|
||||
|
||||
const currentAbort = new AbortController();
|
||||
abortProcess.current = currentAbort;
|
||||
historyLoad.current = false;
|
||||
|
||||
setLoading(true);
|
||||
setIsAllTokenLoading(true);
|
||||
setHasValue(false);
|
||||
log('======Start-Tokens======', userAddr);
|
||||
let _data = produce(walletProject, (draft) => {
|
||||
draft.netWorth = 0;
|
||||
draft._netWorth = '$0';
|
||||
draft._netWorthChange = '-';
|
||||
draft.netWorthChange = 0;
|
||||
draft._netWorthChangePercent = '';
|
||||
});
|
||||
let _tokens: AbstractPortfolioToken[] = [];
|
||||
setData(_data);
|
||||
|
||||
const blocked = showBlocked
|
||||
? []
|
||||
: (await wallet.getBlockedToken()).filter((token) => {
|
||||
if (isTestnet) {
|
||||
return checkIsTestnet(token.chain);
|
||||
} else {
|
||||
return !checkIsTestnet(token.chain);
|
||||
}
|
||||
});
|
||||
const dispatchTokenList = (tokens: AbstractPortfolioToken[]) => {
|
||||
const displayTokens = filterValidChainTokens(tokens);
|
||||
|
||||
if (isTestnet) {
|
||||
dispatch.account.setTestnetTokenList(displayTokens);
|
||||
} else {
|
||||
dispatch.account.setTokenList(displayTokens);
|
||||
}
|
||||
};
|
||||
|
||||
const applyTokenItems = (tokens: TokenItem[]) => {
|
||||
setHasValue(tokens.length > 0);
|
||||
dispatchTokenList(sortTokenItems(tokens));
|
||||
};
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
abortedFn();
|
||||
@@ -215,64 +168,59 @@ export const useTokens = (
|
||||
|
||||
let currentAllTokens: TokenItem[] = [];
|
||||
|
||||
if (!shouldPersistTokenCache) {
|
||||
await Promise.all([
|
||||
tokenDbService.deleteForAddress(userAddr),
|
||||
syncDbService.deleteSceneForAddress({
|
||||
address: userAddr,
|
||||
scene: TOKEN_SYNC_SCENE,
|
||||
}),
|
||||
]);
|
||||
} else {
|
||||
currentAllTokens = await tokenDbService.queryTokens(userAddr);
|
||||
/**
|
||||
* 阶段一:本地 DB 缓存
|
||||
* 能用缓存就从缓存取,否则删除缓存
|
||||
*/
|
||||
try {
|
||||
if (!shouldPersistTokenCache) {
|
||||
await Promise.all([
|
||||
tokenDbService.deleteForAddress(userAddr),
|
||||
syncDbService.deleteSceneForAddress({
|
||||
address: userAddr,
|
||||
scene: TOKEN_SYNC_SCENE,
|
||||
}),
|
||||
]);
|
||||
} else {
|
||||
currentAllTokens = await tokenDbService.queryTokens(userAddr);
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
abortedFn();
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentAllTokens.length) {
|
||||
const chainTokens = currentAllTokens.reduce((m, n) => {
|
||||
m[n.chain] = m[n.chain] || [];
|
||||
m[n.chain].push(n);
|
||||
|
||||
return m;
|
||||
}, {} as Record<string, TokenItem[]>);
|
||||
_data = produce(_data, (draft) => {
|
||||
setWalletTokens(draft, chainTokens);
|
||||
});
|
||||
|
||||
setData(_data);
|
||||
_tokens = sortWalletTokens(_data);
|
||||
if (isTestnet) {
|
||||
dispatch.account.setTestnetTokenList(
|
||||
filterDisplayToken(_tokens, blocked)
|
||||
);
|
||||
} else {
|
||||
dispatch.account.setTokenList(filterDisplayToken(_tokens, blocked));
|
||||
if (currentAbort.signal.aborted) {
|
||||
abortedFn();
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentAllTokens.length) {
|
||||
applyTokenItems(currentAllTokens);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const updatedAt =
|
||||
(await syncDbService.getUpdatedAt({
|
||||
address: userAddr,
|
||||
scene: TOKEN_SYNC_SCENE,
|
||||
})) || 0;
|
||||
|
||||
const shouldUseDbCache =
|
||||
currentAllTokens.length > 0 &&
|
||||
!forceRefresh &&
|
||||
!realtimeMode &&
|
||||
updatedAt > Date.now() - CACHE_VALID_DURATION;
|
||||
|
||||
if (shouldUseDbCache) {
|
||||
log('<<==Tokens-cache-hit==>>', userAddr);
|
||||
setIsAllTokenLoading(false);
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const updatedAt =
|
||||
(await syncDbService.getUpdatedAt({
|
||||
address: userAddr,
|
||||
scene: TOKEN_SYNC_SCENE,
|
||||
})) || 0;
|
||||
|
||||
const shouldUseDbCache =
|
||||
currentAllTokens.length > 0 &&
|
||||
!forceRefresh &&
|
||||
!realtimeMode &&
|
||||
updatedAt > Date.now() - CACHE_VALID_DURATION;
|
||||
|
||||
if (shouldUseDbCache) {
|
||||
log('<<==Tokens-cache-hit==>>', userAddr);
|
||||
setIsAllTokenLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略 db 的影响,直接走线上逻辑
|
||||
log('--Terminate-tokens-db-cache-get', userAddr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 阶段二:接口快照缓存
|
||||
* 完成后再决定是否进入完整刷新。
|
||||
*/
|
||||
const snapshot = await queryTokensCache(userAddr, wallet, isTestnet);
|
||||
|
||||
if (!snapshot) {
|
||||
@@ -290,28 +238,13 @@ export const useTokens = (
|
||||
|
||||
if (snapshot?.length) {
|
||||
currentAllTokens = replaceCoreTokens(currentAllTokens, snapshot);
|
||||
const chainTokens = currentAllTokens.reduce((m, n) => {
|
||||
m[n.chain] = m[n.chain] || [];
|
||||
m[n.chain].push(n);
|
||||
|
||||
return m;
|
||||
}, {} as Record<string, TokenItem[]>);
|
||||
_data = produce(_data, (draft) => {
|
||||
setWalletTokens(draft, chainTokens);
|
||||
});
|
||||
|
||||
setData(_data);
|
||||
_tokens = sortWalletTokens(_data);
|
||||
if (isTestnet) {
|
||||
dispatch.account.setTestnetTokenList(
|
||||
filterDisplayToken(_tokens, blocked)
|
||||
);
|
||||
} else {
|
||||
dispatch.account.setTokenList(filterDisplayToken(_tokens, blocked));
|
||||
}
|
||||
applyTokenItems(currentAllTokens);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 阶段三:完整 token 刷新
|
||||
*/
|
||||
const tokenRes = await batchQueryTokens(
|
||||
userAddr,
|
||||
wallet,
|
||||
@@ -332,258 +265,43 @@ export const useTokens = (
|
||||
return;
|
||||
}
|
||||
|
||||
// customize and blocked tokens
|
||||
const customizeTokens = (await wallet.getCustomizedToken()).filter(
|
||||
(token) => {
|
||||
if (isTestnet) {
|
||||
return checkIsTestnet(token.chain);
|
||||
} else {
|
||||
return !checkIsTestnet(token.chain);
|
||||
}
|
||||
}
|
||||
currentAllTokens = replaceTokensWithLatest(
|
||||
currentAllTokens,
|
||||
tokenRes,
|
||||
chainServerId
|
||||
);
|
||||
const customTokenList: TokenItem[] = [];
|
||||
const blockedTokenList: TokenItem[] = [];
|
||||
tokenRes.forEach((token) => {
|
||||
if (
|
||||
customizeTokens.find(
|
||||
(t) =>
|
||||
isSameAddress(token.id, t.address) &&
|
||||
token.chain === t.chain &&
|
||||
!token.is_core
|
||||
)
|
||||
) {
|
||||
// customize with balance
|
||||
customTokenList.push(token);
|
||||
}
|
||||
if (
|
||||
blocked.find(
|
||||
(t) =>
|
||||
isSameAddress(token.id, t.address) &&
|
||||
token.chain === t.chain &&
|
||||
token.is_core
|
||||
)
|
||||
) {
|
||||
blockedTokenList.push(token);
|
||||
}
|
||||
});
|
||||
const apiProvider = isTestnet ? wallet.testnetOpenapi : wallet.openapi;
|
||||
const noBalanceBlockedTokens = blocked.filter((token) => {
|
||||
return !blockedTokenList.find(
|
||||
(t) => isSameAddress(token.address, t.id) && token.chain === t.chain
|
||||
);
|
||||
});
|
||||
const noBalanceCustomizeTokens = customizeTokens.filter((token) => {
|
||||
return !customTokenList.find(
|
||||
(t) => isSameAddress(token.address, t.id) && token.chain === t.chain
|
||||
);
|
||||
});
|
||||
if (noBalanceCustomizeTokens.length > 0) {
|
||||
const noBalanceCustomTokens = await apiProvider.customListToken(
|
||||
noBalanceCustomizeTokens.map((item) => `${item.chain}:${item.address}`),
|
||||
userAddr
|
||||
);
|
||||
customTokenList.push(
|
||||
...noBalanceCustomTokens.filter((token) => !token.is_core)
|
||||
);
|
||||
}
|
||||
if (noBalanceBlockedTokens.length > 0) {
|
||||
const blockedTokens = await apiProvider.customListToken(
|
||||
noBalanceBlockedTokens.map((item) => `${item.chain}:${item.address}`),
|
||||
userAddr
|
||||
);
|
||||
blockedTokenList.push(...blockedTokens.filter((token) => token.is_core));
|
||||
}
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
abortedFn();
|
||||
return;
|
||||
}
|
||||
|
||||
const formattedCustomTokenList = customTokenList.map(
|
||||
(token) => new DisplayedToken(token) as AbstractPortfolioToken
|
||||
);
|
||||
const formattedBlockedTokenList = blockedTokenList.map(
|
||||
(token) => new DisplayedToken(token) as AbstractPortfolioToken
|
||||
);
|
||||
|
||||
if (isTestnet) {
|
||||
dispatch.account.setTestnetBlockedTokenList(formattedBlockedTokenList);
|
||||
dispatch.account.setTestnetCustomizeTokenList(formattedCustomTokenList);
|
||||
} else {
|
||||
dispatch.account.setBlockedTokenList(formattedBlockedTokenList);
|
||||
dispatch.account.setCustomizeTokenList(formattedCustomTokenList);
|
||||
}
|
||||
|
||||
currentAllTokens = uniqTokens([
|
||||
...(chainServerId
|
||||
? currentAllTokens.filter((token) => token.chain !== chainServerId)
|
||||
: []),
|
||||
...tokenRes,
|
||||
...customTokenList,
|
||||
...blockedTokenList,
|
||||
]);
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
abortedFn();
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldPersistTokenCache) {
|
||||
await tokenDbService.replaceAddressTokens(userAddr, currentAllTokens);
|
||||
try {
|
||||
await tokenDbService.replaceAddressTokens(userAddr, currentAllTokens);
|
||||
|
||||
if (!chainServerId) {
|
||||
await syncDbService.setUpdatedAt({
|
||||
address: userAddr,
|
||||
scene: TOKEN_SYNC_SCENE,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
if (!chainServerId) {
|
||||
await syncDbService.setUpdatedAt({
|
||||
address: userAddr,
|
||||
scene: TOKEN_SYNC_SCENE,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略 db 的影响,不写缓存,直走内存
|
||||
log('--Terminate-tokens-db-cache-set', userAddr);
|
||||
}
|
||||
}
|
||||
|
||||
const tokensDict: Record<string, TokenItem[]> = {};
|
||||
tokenRes.forEach((token) => {
|
||||
if (!tokensDict[token.chain]) {
|
||||
tokensDict[token.chain] = [];
|
||||
}
|
||||
tokensDict[token.chain].push(token);
|
||||
});
|
||||
|
||||
_data = produce(_data, (draft) => {
|
||||
setWalletTokens(draft, tokensDict);
|
||||
});
|
||||
|
||||
setData(_data);
|
||||
_tokens = sortWalletTokens(_data);
|
||||
if (isTestnet) {
|
||||
dispatch.account.setTestnetTokenList([
|
||||
...filterDisplayToken(_tokens, blocked),
|
||||
...formattedCustomTokenList,
|
||||
]);
|
||||
} else {
|
||||
dispatch.account.setTokenList([
|
||||
...filterDisplayToken(_tokens, blocked),
|
||||
...formattedCustomTokenList,
|
||||
]);
|
||||
if (currentAbort.signal.aborted) {
|
||||
log('--Terminate-tokens-db-cache-set', userAddr);
|
||||
abortedFn();
|
||||
return;
|
||||
}
|
||||
|
||||
applyTokenItems(currentAllTokens);
|
||||
|
||||
setLoading(false);
|
||||
setIsAllTokenLoading(false);
|
||||
loadHistory(_data, currentAbort);
|
||||
|
||||
log('<<==Tokens-end==>>', userAddr);
|
||||
};
|
||||
|
||||
const loadHistory = async (
|
||||
pre?: DisplayedProject,
|
||||
currentAbort = new AbortController()
|
||||
) => {
|
||||
if (!historyTime.current || !userAddr || historyLoad.current || isTestnet) {
|
||||
log('middle-tokens-end');
|
||||
return;
|
||||
}
|
||||
|
||||
abortProcess.current = currentAbort;
|
||||
historyLoad.current = true;
|
||||
|
||||
let _data = pre || data!;
|
||||
|
||||
log('===token===batchhistory====', userAddr);
|
||||
// setTokenChangeLoading(true);
|
||||
|
||||
if (currentAbort.signal.aborted || !_data?.netWorth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const historyTokenRes = await batchQueryHistoryTokens(
|
||||
userAddr,
|
||||
historyTime.current,
|
||||
wallet,
|
||||
isTestnet
|
||||
);
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const historyPortfolios: PortfolioItem[] = [];
|
||||
|
||||
historyTokenRes?.forEach((token) => {
|
||||
const chain = token.chain;
|
||||
const index = historyPortfolios.findIndex((p) => p.pool.id === chain);
|
||||
if (index === -1) {
|
||||
historyPortfolios.push({
|
||||
pool: {
|
||||
id: chain,
|
||||
},
|
||||
asset_token_list: [token as PortfolioItemToken],
|
||||
} as PortfolioItem);
|
||||
} else {
|
||||
historyPortfolios[index].asset_token_list?.push(
|
||||
token as PortfolioItemToken
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
_data = produce(_data, (draft) => {
|
||||
draft.patchHistory(historyPortfolios);
|
||||
});
|
||||
|
||||
const tokenList = sortWalletTokens(_data);
|
||||
if (isTestnet) {
|
||||
dispatch.account.setTestnetTokenList(tokenList);
|
||||
} else {
|
||||
dispatch.account.setTokenList(tokenList);
|
||||
}
|
||||
setData(_data);
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const missedTokens = tokenList.reduce((m, n) => {
|
||||
if (n._tokenId && !n._historyPatched) {
|
||||
m[n.chain] = m[n.chain] || new Set();
|
||||
m[n.chain].add(n._tokenId);
|
||||
}
|
||||
|
||||
return m;
|
||||
}, {} as Record<string, Set<string>>);
|
||||
|
||||
const priceDicts = await getMissedTokenPrice(
|
||||
missedTokens,
|
||||
historyTime.current,
|
||||
wallet
|
||||
);
|
||||
|
||||
if (currentAbort.signal.aborted || !priceDicts) {
|
||||
return;
|
||||
}
|
||||
|
||||
_data = produce(_data, (draft) => {
|
||||
Object.entries(priceDicts).forEach(([c, dict]) => {
|
||||
if (!draft._portfolioDict[c]._historyPatched) {
|
||||
draft._portfolioDict[c].patchPrice(dict);
|
||||
if (draft._portfolioDict[c].netWorthChange) {
|
||||
draft.netWorthChange += draft._portfolioDict[c].netWorthChange;
|
||||
}
|
||||
}
|
||||
draft.afterHistoryPatched();
|
||||
});
|
||||
}) as DisplayedProject;
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
setData(_data);
|
||||
if (isTestnet) {
|
||||
dispatch.account.setTestnetTokenList(sortWalletTokens(_data));
|
||||
} else {
|
||||
dispatch.account.setTokenList(sortWalletTokens(_data));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
abortProcess.current?.abort();
|
||||
@@ -630,9 +348,7 @@ export const useTokens = (
|
||||
chainServerId || ''
|
||||
);
|
||||
|
||||
return list.map(
|
||||
(token) => new DisplayedToken(token) as AbstractPortfolioToken
|
||||
);
|
||||
return list.map(parseTokenItem);
|
||||
}, [shouldLoadRecommended, userAddr, chainServerId]);
|
||||
|
||||
const tokens = useMemo(() => {
|
||||
@@ -660,16 +376,10 @@ export const useTokens = (
|
||||
}, [loadProcess]);
|
||||
|
||||
return {
|
||||
netWorth: data?.netWorth || 0,
|
||||
isLoading: isLoading || loadingRecommendedTokens,
|
||||
isAllTokenLoading: isAllTokenLoading || loadingRecommendedTokens,
|
||||
tokens,
|
||||
customizeTokens: isTestnet
|
||||
? testnetTokens.customize
|
||||
: mainnetTokens.customize,
|
||||
blockedTokens: isTestnet ? testnetTokens.blocked : mainnetTokens.blocked,
|
||||
hasValue: !!data?._portfolios?.length,
|
||||
hasValue,
|
||||
updateData: forceRefresh,
|
||||
walletProject: data,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,13 +5,19 @@ import {
|
||||
TokenItemWithEntity,
|
||||
} from '@rabby-wallet/rabby-api/dist/types';
|
||||
import { CHAINS } from 'consts';
|
||||
import { DisplayedProject } from './project';
|
||||
import { DisplayedProject, encodeProjectTokenId } from './project';
|
||||
import { WalletControllerType } from '../WalletContext';
|
||||
import { requestOpenApiWithChainId } from '@/ui/utils/openapi';
|
||||
import { isTestnet as checkIsTestnet } from '@/utils/chain';
|
||||
import {
|
||||
isTestnet as checkIsTestnet,
|
||||
findChain,
|
||||
findChainByEnum,
|
||||
} from '@/utils/chain';
|
||||
import { pQueue } from './utils';
|
||||
import { flatten } from 'lodash';
|
||||
import { isSameAddress } from '..';
|
||||
import { flatten, uniqBy } from 'lodash';
|
||||
import { formatAmount, formatPrice, formatUsdValue, isSameAddress } from '..';
|
||||
import { AbstractPortfolioToken } from './types';
|
||||
import { getTokenSymbol } from '../token';
|
||||
|
||||
export const queryTokensCache = async (
|
||||
user_id: string,
|
||||
@@ -176,3 +182,102 @@ export const scamTokenFilter = (item: {
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const buildTokenKey = (token: Pick<TokenItem, 'chain' | 'id'>) =>
|
||||
`${token.chain}-${token.id.toLowerCase()}`;
|
||||
|
||||
export const uniqTokens = (tokens: TokenItem[]) => {
|
||||
return uniqBy(tokens, buildTokenKey);
|
||||
};
|
||||
|
||||
// 过滤掉无效的链
|
||||
export const filterValidChainTokens = (tokens: AbstractPortfolioToken[]) => {
|
||||
return tokens.filter((token) => {
|
||||
const chain = findChain({
|
||||
serverId: token.chain,
|
||||
});
|
||||
return findChainByEnum(chain?.enum);
|
||||
});
|
||||
};
|
||||
|
||||
/** 替换核心 token (缓存接口没有非 core 的 token */
|
||||
export const replaceCoreTokens = (
|
||||
tokens: TokenItem[],
|
||||
cacheTokens: TokenItem[]
|
||||
) => {
|
||||
return uniqTokens([
|
||||
...tokens.filter((token) => !token.is_core),
|
||||
...cacheTokens,
|
||||
]);
|
||||
};
|
||||
|
||||
export const replaceTokensWithLatest = (
|
||||
tokens: TokenItem[],
|
||||
latestTokens: TokenItem[],
|
||||
chainServerId?: string
|
||||
) => {
|
||||
if (!chainServerId) {
|
||||
return uniqTokens(latestTokens);
|
||||
}
|
||||
|
||||
return uniqTokens([
|
||||
...latestTokens,
|
||||
...tokens.filter((token) => token.chain !== chainServerId),
|
||||
]);
|
||||
};
|
||||
|
||||
export const groupTokensByChain = (tokens: TokenItem[]) => {
|
||||
return tokens.reduce((m, n) => {
|
||||
m[n.chain] = m[n.chain] || [];
|
||||
m[n.chain].push(n);
|
||||
|
||||
return m;
|
||||
}, {} as Record<string, TokenItem[]>);
|
||||
};
|
||||
|
||||
export const parseTokenItem = (token: TokenItem): AbstractPortfolioToken => {
|
||||
const formattedPrice = token.price || 0;
|
||||
const formattedAmount = token.amount || 0;
|
||||
const realUsdValue = formattedPrice * formattedAmount;
|
||||
const usdValue = Math.abs(realUsdValue);
|
||||
return {
|
||||
id: encodeProjectTokenId(token),
|
||||
_tokenId: token.id,
|
||||
chain: token.chain,
|
||||
symbol: getTokenSymbol(token),
|
||||
logo_url: token.logo_url,
|
||||
amount: formattedAmount,
|
||||
price: formattedPrice,
|
||||
_realUsdValue: realUsdValue,
|
||||
// 注意这里,debt 也被处理成正值
|
||||
_usdValue: usdValue,
|
||||
_amountStr: formatAmount(Math.abs(formattedAmount)),
|
||||
_priceStr: formatPrice(formattedPrice),
|
||||
_usdValueStr: formatUsdValue(usdValue),
|
||||
|
||||
decimals: token.decimals,
|
||||
display_symbol: token.display_symbol,
|
||||
name: token.name,
|
||||
optimized_symbol: token.optimized_symbol,
|
||||
is_core: token.is_core,
|
||||
is_wallet: token.is_wallet,
|
||||
is_verified: token.is_verified,
|
||||
is_suspicious: token.is_suspicious,
|
||||
time_at: token.time_at,
|
||||
price_24h_change: token.price_24h_change,
|
||||
low_credit_score: token.low_credit_score,
|
||||
raw_amount_hex_str: token.raw_amount_hex_str,
|
||||
cex_ids: token.cex_ids || [],
|
||||
protocol_id: token.protocol_id,
|
||||
|
||||
_amountChangeStr: '',
|
||||
_usdValueChangeStr: '-',
|
||||
_amountChangeUsdValueStr: '',
|
||||
};
|
||||
};
|
||||
|
||||
export const sortTokenItems = (tokens: TokenItem[]) => {
|
||||
return tokens
|
||||
.map(parseTokenItem)
|
||||
.sort((m, n) => (n._usdValue || 0) - (m._usdValue || 0));
|
||||
};
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import produce from 'immer';
|
||||
import { Dayjs } from 'dayjs';
|
||||
// import { atom, useSetAtom } from 'jotai';
|
||||
|
||||
import { CACHE_VALID_DURATION, DEFI_SYNC_SCENE } from '@/db/constants';
|
||||
import produce from 'immer';
|
||||
import { ComplexProtocol } from '@rabby-wallet/rabby-api/dist/types';
|
||||
|
||||
import { isFullVersionAccountType } from '@/utils/account';
|
||||
import { defiDbService } from '@/db/services/defiDbService';
|
||||
import { syncDbService } from '@/db/services/syncDbService';
|
||||
import { isFullVersionAccountType } from '@/utils/account';
|
||||
import { CHAIN_ID_LIST, syncChainIdList } from 'consts';
|
||||
import { useWallet } from '../WalletContext';
|
||||
import { chunk, loadTestnetPortfolioSnapshot } from './utils';
|
||||
import { CACHE_VALID_DURATION, DEFI_SYNC_SCENE } from '@/db/constants';
|
||||
|
||||
import { chunk } from './utils';
|
||||
import { isSameAddress } from '..';
|
||||
import { useSafeState } from '../safeState';
|
||||
import { useWallet } from '../WalletContext';
|
||||
import { DisplayedProject } from './project';
|
||||
import { getExpandListSwitch } from './expandList';
|
||||
import {
|
||||
batchLoadProjects,
|
||||
batchLoadHistoryProjects,
|
||||
loadPortfolioSnapshot,
|
||||
snapshot2Display,
|
||||
portfolio2Display,
|
||||
patchPortfolioHistory,
|
||||
getMissedTokenPrice,
|
||||
snapshot2Display,
|
||||
} from './utils';
|
||||
import { DisplayedProject } from './project';
|
||||
import { isSameAddress } from '..';
|
||||
import { ComplexProtocol } from '@rabby-wallet/rabby-api/dist/types';
|
||||
|
||||
const chunkSize = 5;
|
||||
|
||||
@@ -47,26 +43,16 @@ export const log = (...args: any) => {
|
||||
// console.log(...args);
|
||||
};
|
||||
|
||||
// export const portfolioChangeLoadingAtom = atom(true);
|
||||
|
||||
export const usePortfolios = (
|
||||
userAddr: string | undefined,
|
||||
timeAt?: Dayjs,
|
||||
visible = true,
|
||||
isTestnet = false
|
||||
) => {
|
||||
export const usePortfolios = (userAddr: string | undefined, visible = true) => {
|
||||
const [data, setData] = useSafeState<DisplayedProject[]>([]);
|
||||
const [netWorth, setNetWorth] = useSafeState(0);
|
||||
const [hasValue, setHasValue] = useSafeState(false);
|
||||
const abortProcess = useRef<AbortController>();
|
||||
const [isLoading, setLoading] = useSafeState(true);
|
||||
const projectDict = useRef<Record<string, DisplayedProject> | null>({});
|
||||
const historyTime = useRef<number>();
|
||||
const historyLoad = useRef<boolean>(false);
|
||||
const realtimeIds = useRef<string[]>([]);
|
||||
const wallet = useWallet();
|
||||
const userAddrRef = useRef('');
|
||||
// const setPortfolioChangeLoading = useSetAtom(portfolioChangeLoadingAtom);
|
||||
|
||||
useEffect(() => {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -93,19 +79,6 @@ export const usePortfolios = (
|
||||
};
|
||||
}, [userAddr, visible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (timeAt) {
|
||||
historyTime.current = timeAt.unix();
|
||||
|
||||
if (!isLoading) {
|
||||
loadHistory();
|
||||
}
|
||||
} else {
|
||||
historyTime.current = 0;
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [timeAt, isLoading]);
|
||||
|
||||
const applyProtocols = (protocols: ComplexProtocol[]) => {
|
||||
const _hasValue = protocols.some((x) => Object.keys(x).length > 0);
|
||||
setHasValue(_hasValue);
|
||||
@@ -137,69 +110,70 @@ export const usePortfolios = (
|
||||
const currentAbort = new AbortController();
|
||||
abortProcess.current = currentAbort;
|
||||
|
||||
historyLoad.current = false;
|
||||
|
||||
setLoading(true);
|
||||
// setPortfolioChangeLoading(withHistory);
|
||||
|
||||
log('======Start-Portfolio======', userAddr);
|
||||
setData([]);
|
||||
setHasValue(false);
|
||||
|
||||
let currentProtocols: ComplexProtocol[] = [];
|
||||
const matchedAccount = isTestnet
|
||||
? null
|
||||
: await wallet.getAccountByAddress(userAddr);
|
||||
const shouldPersistDefiCache =
|
||||
!isTestnet && matchedAccount
|
||||
? isFullVersionAccountType(matchedAccount as any)
|
||||
: false;
|
||||
const matchedAccount = await wallet.getAccountByAddress(userAddr);
|
||||
const shouldPersistDefiCache = matchedAccount
|
||||
? isFullVersionAccountType(matchedAccount as any)
|
||||
: false;
|
||||
|
||||
if (shouldPersistDefiCache) {
|
||||
currentProtocols = await defiDbService.queryProtocols(userAddr);
|
||||
/**
|
||||
* 阶段一:本地 DB 缓存
|
||||
*/
|
||||
try {
|
||||
if (shouldPersistDefiCache) {
|
||||
currentProtocols = await defiDbService.queryProtocols(userAddr);
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
log('--Terminate-portfolio-db-cache-', userAddr);
|
||||
setLoading(false);
|
||||
return;
|
||||
if (currentAbort.signal.aborted) {
|
||||
log('--Terminate-portfolio-db-cache-', userAddr);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentProtocols.length) {
|
||||
applyProtocols(currentProtocols);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const updatedAt =
|
||||
(await syncDbService.getUpdatedAt({
|
||||
address: userAddr,
|
||||
scene: DEFI_SYNC_SCENE,
|
||||
})) || 0;
|
||||
|
||||
const shouldUseDbCache =
|
||||
currentProtocols.length > 0 &&
|
||||
!forceRefresh &&
|
||||
updatedAt > Date.now() - CACHE_VALID_DURATION;
|
||||
|
||||
if (shouldUseDbCache) {
|
||||
log('<<==Defi-cache-hit==>>', userAddr);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await Promise.all([
|
||||
defiDbService.deleteForAddress(userAddr),
|
||||
syncDbService.deleteSceneForAddress({
|
||||
address: userAddr,
|
||||
scene: DEFI_SYNC_SCENE,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
if (currentProtocols.length) {
|
||||
applyProtocols(currentProtocols);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const updatedAt =
|
||||
(await syncDbService.getUpdatedAt({
|
||||
address: userAddr,
|
||||
scene: DEFI_SYNC_SCENE,
|
||||
})) || 0;
|
||||
|
||||
const shouldUseDbCache =
|
||||
currentProtocols.length > 0 &&
|
||||
!forceRefresh &&
|
||||
updatedAt > Date.now() - CACHE_VALID_DURATION;
|
||||
|
||||
if (shouldUseDbCache) {
|
||||
log('<<==Defi-cache-hit==>>', userAddr);
|
||||
return;
|
||||
}
|
||||
} else if (!isTestnet) {
|
||||
await Promise.all([
|
||||
defiDbService.deleteForAddress(userAddr),
|
||||
syncDbService.deleteSceneForAddress({
|
||||
address: userAddr,
|
||||
scene: DEFI_SYNC_SCENE,
|
||||
}),
|
||||
]);
|
||||
} catch (error) {
|
||||
// 忽略 db 的影响,直接走线上逻辑
|
||||
log('--Terminate-portfolio-db-cache-get', userAddr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 阶段二:接口快照缓存
|
||||
*/
|
||||
let snapshotRes: ComplexProtocol[] = [];
|
||||
if (isTestnet) {
|
||||
snapshotRes = await loadTestnetPortfolioSnapshot(userAddr, wallet);
|
||||
} else {
|
||||
snapshotRes = await loadPortfolioSnapshot(userAddr, wallet);
|
||||
}
|
||||
snapshotRes = await loadPortfolioSnapshot(userAddr, wallet);
|
||||
|
||||
if (currentAbort.signal.aborted || !snapshotRes) {
|
||||
log('--Terminate-portfolio-snapshot-', userAddr);
|
||||
@@ -220,12 +194,20 @@ export const usePortfolios = (
|
||||
|
||||
if (!realtimeIds.current.length) {
|
||||
if (shouldPersistDefiCache) {
|
||||
await defiDbService.replaceAddressProtocols(userAddr, currentProtocols);
|
||||
await syncDbService.setUpdatedAt({
|
||||
address: userAddr,
|
||||
scene: DEFI_SYNC_SCENE,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
try {
|
||||
await defiDbService.replaceAddressProtocols(
|
||||
userAddr,
|
||||
currentProtocols
|
||||
);
|
||||
await syncDbService.setUpdatedAt({
|
||||
address: userAddr,
|
||||
scene: DEFI_SYNC_SCENE,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
// 忽略 db 的影响,不写缓存,直走内存
|
||||
log('--Terminate-portfolio-db-cache-set', userAddr);
|
||||
}
|
||||
}
|
||||
|
||||
log('--Terminate-portfolio-loadProjectIds-', userAddr);
|
||||
@@ -234,6 +216,10 @@ export const usePortfolios = (
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 阶段三:完整逐个 id刷新
|
||||
*/
|
||||
|
||||
const chunkIds = chunk(realtimeIds.current, chunkSize);
|
||||
|
||||
let realtimeData: DisplayedProject[] = [];
|
||||
@@ -249,7 +235,7 @@ export const usePortfolios = (
|
||||
userAddr,
|
||||
ids,
|
||||
wallet,
|
||||
isTestnet
|
||||
false
|
||||
);
|
||||
|
||||
const projects = projectListRes;
|
||||
@@ -271,15 +257,34 @@ export const usePortfolios = (
|
||||
})
|
||||
);
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
log('--Terminate-portfolio-realtime-', userAddr);
|
||||
projectDict.current = null;
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
currentProtocols = replaceProtocols(currentProtocols, realtimeProtocols);
|
||||
|
||||
if (shouldPersistDefiCache) {
|
||||
await defiDbService.replaceAddressProtocols(userAddr, currentProtocols);
|
||||
await syncDbService.setUpdatedAt({
|
||||
address: userAddr,
|
||||
scene: DEFI_SYNC_SCENE,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
try {
|
||||
await defiDbService.replaceAddressProtocols(userAddr, currentProtocols);
|
||||
await syncDbService.setUpdatedAt({
|
||||
address: userAddr,
|
||||
scene: DEFI_SYNC_SCENE,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
// 忽略 db 的影响,不写缓存,直走内存
|
||||
log('--Terminate-portfolio-db-cache-set', userAddr);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
log('--Terminate-portfolio-db-cache-set', userAddr);
|
||||
projectDict.current = null;
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
realtimeData = Object.values(projectDict.current)?.sort(
|
||||
@@ -290,126 +295,9 @@ export const usePortfolios = (
|
||||
setNetWorth(realtimeData.reduce((m, n) => m + n.netWorth, 0));
|
||||
setLoading(false);
|
||||
|
||||
loadHistory(currentAbort);
|
||||
log('portfolios-end', userAddr);
|
||||
};
|
||||
|
||||
const loadHistory = async (currentAbort = new AbortController()) => {
|
||||
if (
|
||||
!historyTime.current ||
|
||||
currentAbort.signal.aborted ||
|
||||
!userAddr ||
|
||||
historyLoad.current
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
historyLoad.current = true;
|
||||
syncChainIdList();
|
||||
const historyIds = realtimeIds.current.filter(
|
||||
(x) =>
|
||||
projectDict.current![x].chain &&
|
||||
CHAIN_ID_LIST.get(projectDict.current![x].chain!)?.isSupportHistory
|
||||
);
|
||||
|
||||
const historyIdsArr = chunk(historyIds, chunkSize);
|
||||
|
||||
if (currentAbort.signal.aborted || !historyIdsArr.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
historyIdsArr.map(async (ids) => {
|
||||
const historyProjectListRes = await batchLoadHistoryProjects(
|
||||
userAddr,
|
||||
ids,
|
||||
wallet,
|
||||
historyTime.current,
|
||||
isTestnet
|
||||
);
|
||||
const projects = historyProjectListRes;
|
||||
|
||||
if (!projects?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
projects.forEach((project) => {
|
||||
if (!currentAbort.signal.aborted && projectDict.current) {
|
||||
projectDict.current = produce(projectDict.current, (draft) => {
|
||||
patchPortfolioHistory(project, draft);
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
const historyList = Object.values(projectDict.current!)?.sort(
|
||||
(m, n) => (n.netWorth || 0) - (m.netWorth || 0)
|
||||
);
|
||||
|
||||
setData(historyList);
|
||||
|
||||
// 可能有获取失败的,也需要通过 priceChange 来算大概的变化
|
||||
const notSuportHistoryProjects = realtimeIds.current.filter(
|
||||
(x) => !projectDict.current![x]._historyPatched
|
||||
);
|
||||
|
||||
// 是否存在没有被 patchHistory 的(不支持历史结点 | 获取失败的),需要再去请求它之前的价格来计算大致的 usdChange
|
||||
const missedTokens = notSuportHistoryProjects.reduce((m, n) => {
|
||||
const pChain = projectDict.current![n]?.chain;
|
||||
|
||||
if (!pChain) {
|
||||
return m;
|
||||
}
|
||||
|
||||
m[pChain] = m[pChain] || new Set();
|
||||
|
||||
projectDict.current![n]._portfolios?.forEach((x) => {
|
||||
x._tokenList?.forEach((t) => {
|
||||
m[pChain].add(t._tokenId);
|
||||
});
|
||||
});
|
||||
|
||||
return m;
|
||||
}, {} as Record<string, Set<string>>);
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const priceDicts = await getMissedTokenPrice(
|
||||
missedTokens,
|
||||
historyTime.current,
|
||||
wallet,
|
||||
isTestnet
|
||||
);
|
||||
|
||||
if (currentAbort.signal.aborted || !projectDict.current || !priceDicts) {
|
||||
return;
|
||||
}
|
||||
|
||||
projectDict.current = produce(projectDict.current, (draft) => {
|
||||
notSuportHistoryProjects?.forEach((pId) => {
|
||||
if (priceDicts?.[draft[pId].chain!]) {
|
||||
draft[pId].patchPrice(priceDicts?.[draft[pId].chain!]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (currentAbort.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const priceProjects = Object.values(projectDict.current!)?.sort(
|
||||
(m, n) => (n.netWorth || 0) - (m.netWorth || 0)
|
||||
);
|
||||
setData(priceProjects);
|
||||
// setPortfolioChangeLoading(false);
|
||||
};
|
||||
|
||||
const removeProtocol = useCallback(
|
||||
(id: string) => {
|
||||
setData((pre) => pre?.filter((item) => item.id !== id));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { TokenSearchInput } from './TokenSearchInput';
|
||||
import AddTokenEntry, { AddTokenEntryInst } from './AddTokenEntry';
|
||||
import { useRabbySelector } from '@/ui/store';
|
||||
import { HomeTokenList } from './TokenList';
|
||||
import useSortTokens from 'ui/hooks/useSortTokens';
|
||||
@@ -17,8 +16,6 @@ import { useAppChain } from '@/ui/hooks/useAppChain';
|
||||
import { useCommonPopupView } from '@/ui/utils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LpTokenSwitch } from '../../DesktopProfile/components/TokensTabPane/components/LpTokenSwitch';
|
||||
import clsx from 'clsx';
|
||||
import { ReactComponent as SearchSVG } from '@/ui/assets/search.svg';
|
||||
import { HomePerpsPositionList } from './HomePerpsPositionList';
|
||||
import { uniqBy } from 'lodash';
|
||||
import { concatAndSort } from '@/ui/utils/portfolio/tokenUtils';
|
||||
@@ -28,7 +25,6 @@ interface Props {
|
||||
selectChainId: string | null;
|
||||
visible: boolean;
|
||||
onEmptyAssets: (isEmpty: boolean) => void;
|
||||
isTestnet?: boolean;
|
||||
}
|
||||
|
||||
export const AssetListContainer: React.FC<Props> = ({
|
||||
@@ -36,7 +32,6 @@ export const AssetListContainer: React.FC<Props> = ({
|
||||
selectChainId,
|
||||
visible,
|
||||
onEmptyAssets,
|
||||
isTestnet = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [search, setSearch] = React.useState<string>('');
|
||||
@@ -56,22 +51,16 @@ export const AssetListContainer: React.FC<Props> = ({
|
||||
portfolios,
|
||||
tokens: tokenList,
|
||||
hasTokens,
|
||||
blockedTokens,
|
||||
customizeTokens,
|
||||
removeProtocol,
|
||||
} = useQueryProjects(
|
||||
currentAccount?.address,
|
||||
false,
|
||||
} = useQueryProjects(currentAccount?.address, {
|
||||
visible,
|
||||
isTestnet,
|
||||
lpTokenMode ? lpTokenMode : undefined,
|
||||
undefined,
|
||||
!!search
|
||||
);
|
||||
lpTokenMode: lpTokenMode ? lpTokenMode : undefined,
|
||||
searchMode: !!search,
|
||||
});
|
||||
const {
|
||||
data: appPortfolios,
|
||||
isLoading: isAppPortfoliosLoading,
|
||||
} = useAppChain(currentAccount?.address, visible, isTestnet);
|
||||
} = useAppChain(currentAccount?.address, visible);
|
||||
|
||||
const inputRef = React.useRef<InputRef>(null);
|
||||
const { isLoading: isSearching, list } = useSearchToken(
|
||||
@@ -80,7 +69,7 @@ export const AssetListContainer: React.FC<Props> = ({
|
||||
{
|
||||
chainServerId: selectChainId ? selectChainId : undefined,
|
||||
withBalance: true,
|
||||
isTestnet: isTestnet,
|
||||
isTestnet: false,
|
||||
}
|
||||
);
|
||||
const displayTokenList = useMemo(() => {
|
||||
@@ -107,27 +96,11 @@ export const AssetListContainer: React.FC<Props> = ({
|
||||
return combinedPortfolios;
|
||||
}, [portfolios, appPortfolios, selectChainId]);
|
||||
|
||||
const displayBlockedTokens = useMemo(() => {
|
||||
if (selectChainId) {
|
||||
return blockedTokens?.filter((item) => item.chain === selectChainId);
|
||||
}
|
||||
return blockedTokens;
|
||||
}, [blockedTokens, selectChainId]);
|
||||
|
||||
const displayCustomizeTokens = useMemo(() => {
|
||||
if (selectChainId) {
|
||||
return customizeTokens?.filter((item) => item.chain === selectChainId);
|
||||
}
|
||||
return customizeTokens;
|
||||
}, [customizeTokens, selectChainId]);
|
||||
|
||||
const isEmptyAssets =
|
||||
!isTokensLoading &&
|
||||
!displayTokenList.length &&
|
||||
!isPortfoliosLoading &&
|
||||
!displayPortfolios?.length &&
|
||||
!displayBlockedTokens?.length &&
|
||||
!displayCustomizeTokens?.length &&
|
||||
!isAppPortfoliosLoading &&
|
||||
!appPortfolios?.length;
|
||||
|
||||
@@ -141,10 +114,6 @@ export const AssetListContainer: React.FC<Props> = ({
|
||||
kw: search,
|
||||
});
|
||||
|
||||
const handleFocusInput = React.useCallback(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!visible) {
|
||||
setSearch('');
|
||||
@@ -170,8 +139,6 @@ export const AssetListContainer: React.FC<Props> = ({
|
||||
return [...new Set(appPortfolios?.map((item) => item.id) || [])];
|
||||
}, [appPortfolios]);
|
||||
|
||||
const addTokenEntryRef = React.useRef<AddTokenEntryInst>(null);
|
||||
|
||||
if (isTokensLoading && !hasTokens) {
|
||||
return <TokenListViewSkeleton />;
|
||||
}
|
||||
@@ -202,7 +169,6 @@ export const AssetListContainer: React.FC<Props> = ({
|
||||
onLpTokenModeChange={setLpTokenMode}
|
||||
/>
|
||||
</div>
|
||||
{/* {isFocus || search ? null : <AddTokenEntry ref={addTokenEntryRef} />} */}
|
||||
</div>
|
||||
{isTokensLoading || isSearching || (lpTokenMode && isAllTokenLoading) ? (
|
||||
<TokenListSkeleton />
|
||||
@@ -210,17 +176,9 @@ export const AssetListContainer: React.FC<Props> = ({
|
||||
<div className="mt-[12px]">
|
||||
<HomeTokenList
|
||||
list={sortTokens}
|
||||
onFocusInput={handleFocusInput}
|
||||
onOpenAddEntryPopup={() => {
|
||||
addTokenEntryRef.current?.startAddToken();
|
||||
}}
|
||||
isSearch={!!search}
|
||||
lpTokenMode={lpTokenMode}
|
||||
isNoResults={isNoResults}
|
||||
blockedTokens={displayBlockedTokens}
|
||||
customizeTokens={displayCustomizeTokens}
|
||||
isTestnet={isTestnet}
|
||||
selectChainId={selectChainId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -4,36 +4,17 @@ import { useExpandList } from '@/ui/utils/portfolio/expandList';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { TokenLowValueItem } from './TokenLowValueItem';
|
||||
import { TokenTable } from './components/TokenTable';
|
||||
import { BlockedButton } from './BlockedButton';
|
||||
import { CustomizedButton } from './CustomizedButton';
|
||||
import { TokenListEmpty } from './TokenListEmpty';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface Props {
|
||||
list?: TokenItemProps['item'][];
|
||||
isSearch: boolean;
|
||||
onFocusInput: () => void;
|
||||
onOpenAddEntryPopup: () => void;
|
||||
isNoResults?: boolean;
|
||||
blockedTokens?: TokenItemProps['item'][];
|
||||
customizeTokens?: TokenItemProps['item'][];
|
||||
isTestnet: boolean;
|
||||
selectChainId?: string | null;
|
||||
lpTokenMode?: boolean;
|
||||
}
|
||||
|
||||
export const HomeTokenList = ({
|
||||
list,
|
||||
onFocusInput,
|
||||
onOpenAddEntryPopup,
|
||||
isSearch,
|
||||
isNoResults,
|
||||
blockedTokens,
|
||||
customizeTokens,
|
||||
isTestnet,
|
||||
selectChainId,
|
||||
lpTokenMode,
|
||||
}) => {
|
||||
export const HomeTokenList = ({ list, isSearch, isNoResults, lpTokenMode }) => {
|
||||
const totalValue = React.useMemo(() => {
|
||||
return list
|
||||
?.reduce(
|
||||
@@ -44,19 +25,8 @@ export const HomeTokenList = ({
|
||||
}, [list]);
|
||||
const { result: currentList } = useExpandList(list, totalValue);
|
||||
const lowValueList = React.useMemo(() => {
|
||||
// 排除customized tokens
|
||||
const customizedTokenIds = new Set(
|
||||
customizeTokens?.map((token) => token.id) || []
|
||||
);
|
||||
return list?.filter(
|
||||
(item) =>
|
||||
currentList?.indexOf(item) === -1 &&
|
||||
!customizedTokenIds.has(item.id) &&
|
||||
!blockedTokens?.some(
|
||||
(blocked) => blocked.id === item.id && blocked.chain === item.chain
|
||||
)
|
||||
);
|
||||
}, [currentList, list, isSearch, customizeTokens]);
|
||||
return list?.filter((item) => currentList?.indexOf(item) === -1);
|
||||
}, [currentList, list, isSearch]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (isNoResults) {
|
||||
@@ -82,12 +52,7 @@ export const HomeTokenList = ({
|
||||
);
|
||||
}
|
||||
|
||||
const hasList = !!(
|
||||
list?.length ||
|
||||
currentList?.length ||
|
||||
blockedTokens?.length ||
|
||||
customizeTokens?.length
|
||||
);
|
||||
const hasList = !!(list?.length || currentList?.length);
|
||||
const hasLowValueList = !!lowValueList?.length;
|
||||
|
||||
return (
|
||||
@@ -101,20 +66,6 @@ export const HomeTokenList = ({
|
||||
<TokenLowValueItem list={lowValueList} className="h-[48px]" />
|
||||
)}
|
||||
</div>
|
||||
{/* {!isSearch && hasList && (
|
||||
<div className="flex gap-12 pt-12 mt-[1px]">
|
||||
<CustomizedButton
|
||||
onClickButton={onOpenAddEntryPopup}
|
||||
isTestnet={isTestnet}
|
||||
selectChainId={selectChainId}
|
||||
/>
|
||||
<BlockedButton
|
||||
onClickLink={onFocusInput}
|
||||
isTestnet={isTestnet}
|
||||
selectChainId={selectChainId}
|
||||
/>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,23 +6,20 @@ import React, {
|
||||
useCallback,
|
||||
useRef,
|
||||
useMemo,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
import useCurrentBalance from '@/ui/hooks/useCurrentBalance';
|
||||
import { formatUsdValue, useCommonPopupView, useWallet } from 'ui/utils';
|
||||
import { useCommonPopupView, useWallet } from 'ui/utils';
|
||||
import { KEYRING_TYPE } from 'consts';
|
||||
import { SvgIconOffline } from '@/ui/assets';
|
||||
import clsx from 'clsx';
|
||||
import { Skeleton } from 'antd';
|
||||
import { Chain } from '@debank/common';
|
||||
import { ChainList } from './ChainList';
|
||||
import { formChartData, useCurve } from './useCurve';
|
||||
import { CurvePoint, CurveThumbnail } from './CurveView';
|
||||
import ArrowNextSVG from '@/ui/assets/dashboard/arrow-next.svg';
|
||||
import { ReactComponent as UpdateSVG } from '@/ui/assets/dashboard/update.svg';
|
||||
import { ReactComponent as WarningSVG } from '@/ui/assets/dashboard/warning-1.svg';
|
||||
import { useDebounce } from 'react-use';
|
||||
import { useRabbyDispatch, useRabbySelector } from '@/ui/store';
|
||||
import { useRabbySelector } from '@/ui/store';
|
||||
import { BalanceLabel } from './BalanceLabel';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TooltipWithMagnetArrow } from '@/ui/component/Tooltip/TooltipWithMagnetArrow';
|
||||
@@ -166,16 +163,10 @@ export const BalanceView = ({
|
||||
refreshCurve,
|
||||
isExpired: getCacheExpired,
|
||||
});
|
||||
const { refreshPositions } = useQueryProjects(
|
||||
currentAccount?.address,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
const { refreshPositions } = useQueryProjects(currentAccount?.address, {
|
||||
visible: true,
|
||||
autoLoad: false,
|
||||
});
|
||||
|
||||
// const refreshTimerlegacy = useRef<NodeJS.Timeout>();
|
||||
// only execute once on component mounted or address changed
|
||||
|
||||
@@ -44,8 +44,6 @@ const PAGE_COUNT = 10;
|
||||
interface TokenDetailProps {
|
||||
onClose?(): void;
|
||||
token: TokenItem;
|
||||
addToken(token: TokenItem): void;
|
||||
removeToken(token: TokenItem): void;
|
||||
variant?: 'add';
|
||||
isAdded?: boolean;
|
||||
canClickToken?: boolean;
|
||||
@@ -57,8 +55,6 @@ interface TokenDetailProps {
|
||||
|
||||
const TokenDetail = ({
|
||||
token,
|
||||
addToken,
|
||||
removeToken,
|
||||
variant,
|
||||
isAdded,
|
||||
onClose,
|
||||
|
||||
@@ -6,8 +6,6 @@ import './style.less';
|
||||
import { getUiType, isSameAddress, useWallet } from '@/ui/utils';
|
||||
import { Account, Token } from '@/background/service/preference';
|
||||
import { useRabbyDispatch } from 'ui/store';
|
||||
import { DisplayedToken } from 'ui/utils/portfolio/project';
|
||||
import { AbstractPortfolioToken } from 'ui/utils/portfolio/types';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { DrawerProps } from 'antd';
|
||||
|
||||
@@ -55,36 +53,6 @@ export const TokenDetailPopup = ({
|
||||
const isInSend = location.pathname === '/send-token';
|
||||
const isBridge = location.pathname === '/bridge';
|
||||
|
||||
const handleAddToken = React.useCallback((tokenWithAmount) => {
|
||||
if (!tokenWithAmount) return;
|
||||
|
||||
if (tokenWithAmount.is_core) {
|
||||
dispatch.account.addBlockedToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
);
|
||||
} else {
|
||||
dispatch.account.addCustomizeToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
);
|
||||
}
|
||||
setIsAdded(true);
|
||||
}, []);
|
||||
|
||||
const handleRemoveToken = React.useCallback((tokenWithAmount) => {
|
||||
if (!tokenWithAmount) return;
|
||||
|
||||
if (tokenWithAmount?.is_core) {
|
||||
dispatch.account.removeBlockedToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
);
|
||||
} else {
|
||||
dispatch.account.removeCustomizeToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
);
|
||||
}
|
||||
setIsAdded(false);
|
||||
}, []);
|
||||
|
||||
const checkIsAdded = React.useCallback(async () => {
|
||||
if (!token) return;
|
||||
|
||||
@@ -123,8 +91,6 @@ export const TokenDetailPopup = ({
|
||||
account={account}
|
||||
token={token}
|
||||
popupHeight={popupHeight}
|
||||
addToken={handleAddToken}
|
||||
removeToken={handleRemoveToken}
|
||||
variant={variant}
|
||||
isAdded={isAdded}
|
||||
onClose={onClose}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { Modal, ModalProps } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Modal } from 'antd';
|
||||
import React from 'react';
|
||||
import TokenDetail from '../../../Dashboard/components/TokenDetailPopup/TokenDetail';
|
||||
import { TokenItem } from '@/background/service/openapi';
|
||||
import { Account, Token } from '@/background/service/preference';
|
||||
import { Token } from '@/background/service/preference';
|
||||
import { useCurrentAccount } from '@/ui/hooks/backgroundState/useAccount';
|
||||
import { PopupContainer } from '@/ui/hooks/usePopupContainer';
|
||||
import { SvgIconCross } from 'ui/assets';
|
||||
import { isSameAddress, useWallet } from '@/ui/utils';
|
||||
import { useRabbyDispatch } from '@/ui/store';
|
||||
import { DisplayedToken } from '@/ui/utils/portfolio/project';
|
||||
import { AbstractPortfolioToken } from '@/ui/utils/portfolio/types';
|
||||
interface TokenDetailModalProps {
|
||||
visible?: boolean;
|
||||
onClose?(): void;
|
||||
@@ -30,39 +27,8 @@ export const TokenDetailModal: React.FC<TokenDetailModalProps> = ({
|
||||
}) => {
|
||||
const account = useCurrentAccount();
|
||||
const wallet = useWallet();
|
||||
const dispatch = useRabbyDispatch();
|
||||
const [isAdded, setIsAdded] = React.useState(false);
|
||||
|
||||
const handleAddToken = React.useCallback((tokenWithAmount) => {
|
||||
if (!tokenWithAmount) return;
|
||||
|
||||
if (tokenWithAmount.is_core) {
|
||||
dispatch.account.addBlockedToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
);
|
||||
} else {
|
||||
dispatch.account.addCustomizeToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
);
|
||||
}
|
||||
setIsAdded(true);
|
||||
}, []);
|
||||
|
||||
const handleRemoveToken = React.useCallback((tokenWithAmount) => {
|
||||
if (!tokenWithAmount) return;
|
||||
|
||||
if (tokenWithAmount?.is_core) {
|
||||
dispatch.account.removeBlockedToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
);
|
||||
} else {
|
||||
dispatch.account.removeCustomizeToken(
|
||||
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
|
||||
);
|
||||
}
|
||||
setIsAdded(false);
|
||||
}, []);
|
||||
|
||||
const checkIsAdded = React.useCallback(async () => {
|
||||
if (!token) return;
|
||||
|
||||
@@ -108,8 +74,6 @@ export const TokenDetailModal: React.FC<TokenDetailModalProps> = ({
|
||||
<PopupContainer className="h-[600px] bg-r-neutral-bg-2">
|
||||
<TokenDetail
|
||||
account={account || undefined}
|
||||
addToken={handleAddToken}
|
||||
removeToken={handleRemoveToken}
|
||||
variant="add"
|
||||
isAdded={isAdded}
|
||||
token={token}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import ProtocolList from './ProtocolList';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRabbyDispatch } from '@/ui/store';
|
||||
import {
|
||||
AbstractPortfolioToken,
|
||||
AbstractProject,
|
||||
} from '@/ui/utils/portfolio/types';
|
||||
import { AbstractProject } from '@/ui/utils/portfolio/types';
|
||||
import { useExpandList } from './useExpandList';
|
||||
import ProjectOverview from './ProjectOverview';
|
||||
import { TokenListEmpty } from './TokenListEmpty';
|
||||
@@ -41,11 +37,6 @@ export const DIFITab = ({
|
||||
onProjectOverviewListChange,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useRabbyDispatch();
|
||||
|
||||
const setAllMode = (value: boolean) => {
|
||||
dispatch.preference.setDesktopTokensAllMode(value);
|
||||
};
|
||||
|
||||
const {
|
||||
isExpanded,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useCommonPopupView } from '@/ui/utils';
|
||||
import { useQueryProjects } from '@/ui/utils/portfolio';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
export const useTokenAndDIFIData = ({
|
||||
export const useTokenAndDefiData = ({
|
||||
selectChainId,
|
||||
allTokenMode,
|
||||
}: {
|
||||
@@ -32,21 +32,17 @@ export const useTokenAndDIFIData = ({
|
||||
refreshPositions,
|
||||
refreshTokens,
|
||||
refreshPortfolios,
|
||||
} = useQueryProjects(
|
||||
currentAccount?.address,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
} = useQueryProjects(currentAccount?.address, {
|
||||
visible: true,
|
||||
lpTokenMode,
|
||||
true,
|
||||
allTokenMode
|
||||
);
|
||||
searchMode: allTokenMode,
|
||||
});
|
||||
|
||||
const {
|
||||
data: appPortfolios,
|
||||
netWorth: appPortfolioNetWorth,
|
||||
isLoading: isAppPortfoliosLoading,
|
||||
} = useAppChain(currentAccount?.address, true, false);
|
||||
} = useAppChain(currentAccount?.address, true);
|
||||
|
||||
const currentPortfolioNetWorth = useMemo(() => {
|
||||
return (portfolioNetWorth || 0) + (appPortfolioNetWorth || 0);
|
||||
|
||||
@@ -24,10 +24,7 @@ import { GnosisQueueModal } from './components/GnosisQueueModal';
|
||||
import { ApprovalsTabPane } from './components/ApprovalsTabPane';
|
||||
import { AddressDetailModal } from './components/AddressDetailModal';
|
||||
import { AddressBackupModal } from './components/AddressBackupModal';
|
||||
import { AddAddressModal } from './components/AddAddressModal';
|
||||
import { RcIconBackTopCC } from '@/ui/assets/desktop/profile';
|
||||
import { ReachedEnd } from './components/ReachedEnd';
|
||||
import ThemeIcon from '@/ui/component/ThemeMode/ThemeIcon';
|
||||
import TopShortcut, {
|
||||
PORTFOLIO_LIST_ID,
|
||||
TOP_SHORTCUT_SLOT_ID,
|
||||
@@ -35,14 +32,11 @@ import TopShortcut, {
|
||||
import { AbstractProject } from '@/ui/utils/portfolio/types';
|
||||
import { NFTTabPane } from './components/NFTTabPane';
|
||||
import { useEventBusListener } from '@/ui/hooks/useEventBusListener';
|
||||
import { matomoRequestEvent } from '@/utils/matomo-request';
|
||||
import { ga4 } from '@/utils/ga4';
|
||||
import { DesktopPending } from './components/DesktopPending';
|
||||
import { TokenTab } from './components/TokensTabPane/TokenTab';
|
||||
import { DIFITab } from './components/TokensTabPane/DifiTab';
|
||||
import { useTokenAndDIFIData } from './components/TokensTabPane/hook';
|
||||
import { useTokenAndDefiData } from './components/TokensTabPane/hook';
|
||||
import { DesktopPageWrap } from '@/ui/component/DesktopPageWrap';
|
||||
import { SwitchThemeBtn } from './components/SwitchThemeBtn';
|
||||
const DESKTOP_NAV_HEIGHT = 0;
|
||||
|
||||
const StickyBorderTop = () => (
|
||||
@@ -137,23 +131,13 @@ export const DesktopProfile: React.FC<{
|
||||
const [searchValue, setSearchValue] = React.useState('');
|
||||
|
||||
const {
|
||||
// useQueryProjects
|
||||
isTokensLoading,
|
||||
isAllTokenLoading,
|
||||
isPortfoliosLoading,
|
||||
portfolios,
|
||||
tokenList,
|
||||
hasTokens,
|
||||
removeProtocol,
|
||||
portfolioNetWorth,
|
||||
// useQueryProjects end
|
||||
// useAppChain
|
||||
appPortfolios,
|
||||
appPortfolioNetWorth,
|
||||
isAppPortfoliosLoading,
|
||||
// useAppChain end
|
||||
currentPortfolioNetWorth,
|
||||
displayTokenList,
|
||||
displayPortfolios,
|
||||
sortTokens,
|
||||
lpTokenMode,
|
||||
@@ -162,7 +146,7 @@ export const DesktopProfile: React.FC<{
|
||||
isNoResults,
|
||||
refreshPositions,
|
||||
refreshTokens,
|
||||
} = useTokenAndDIFIData({
|
||||
} = useTokenAndDefiData({
|
||||
selectChainId: chainInfo?.serverId,
|
||||
allTokenMode: !!searchValue,
|
||||
});
|
||||
@@ -279,7 +263,7 @@ export const DesktopProfile: React.FC<{
|
||||
}
|
||||
isNoResults={isNoResults}
|
||||
sortTokens={sortTokens}
|
||||
hasTokens={hasTokens}
|
||||
hasTokens={!!hasTokens}
|
||||
lpTokenMode={lpTokenMode}
|
||||
setLpTokenMode={setLpTokenMode}
|
||||
selectChainId={chainInfo?.serverId}
|
||||
|
||||
@@ -115,19 +115,10 @@ const DesktopSmallSwapContent: React.FC = () => {
|
||||
tokens: allTokens,
|
||||
isLoading: isLoadingAllTokens,
|
||||
updateData: updateAllTokens,
|
||||
} = useTokens(
|
||||
chainServerId ? currentAccount?.address : undefined,
|
||||
undefined,
|
||||
true,
|
||||
undefined,
|
||||
} = useTokens(chainServerId ? currentAccount?.address : undefined, {
|
||||
chainServerId,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
realtimeMode: true,
|
||||
});
|
||||
|
||||
const isSupportDB = isSupportDBAccount(currentAccount);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user