From fea36b233a21012d84b59106ed8dd6de7945eadd Mon Sep 17 00:00:00 2001 From: Petromir Petrov Date: Wed, 24 Jul 2024 17:15:52 +0300 Subject: [PATCH 1/2] add: safetyLevel flag to tokens --- src/libs/portfolio/helpers.ts | 30 ++++++++++++++++++++++++++++++ src/libs/portfolio/interfaces.ts | 7 ++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/libs/portfolio/helpers.ts b/src/libs/portfolio/helpers.ts index 78020b4be..9f422591e 100644 --- a/src/libs/portfolio/helpers.ts +++ b/src/libs/portfolio/helpers.ts @@ -2,6 +2,7 @@ import { Contract, ZeroAddress } from 'ethers' import IERC20 from '../../../contracts/compiled/IERC20.json' import gasTankFeeTokens from '../../consts/gasTankFeeTokens' +import humanizerInfo from '../../consts/humanizer/humanizerInfo.json' import { PINNED_TOKENS } from '../../consts/pinnedTokens' import { Account, AccountId } from '../../interfaces/account' import { NetworkId } from '../../interfaces/network' @@ -347,6 +348,32 @@ export const tokenFilter = ( return isInAdditionalHints || pinnedRequested } +const getTokenSafetyLevel = (token: TokenResult) => { + let safetyLevel = 'unknown' as 'unknown' | 'trusted' | 'spoof' + + if (token.address === ZeroAddress) { + return 'trusted' + } + + Object.values(humanizerInfo.knownAddresses).forEach((value) => { + const isMatching = value.address.toLowerCase() === token.address.toLowerCase() + + if (!isMatching) return + + const isTrusted = value.name.toLowerCase().includes(token.symbol.toLowerCase()) + + if (isTrusted) { + safetyLevel = 'trusted' + return + } + if (!isTrusted && safetyLevel !== 'trusted') { + safetyLevel = 'spoof' + } + }) + + return safetyLevel +} + /** * Filter the TokenResult[] by certain criteria (please refer to `tokenFilter` for more details) * and set the token.isHidden flag. @@ -364,6 +391,7 @@ export const processTokens = ( return tokenResults.reduce((tokens, tokenResult) => { const token = { ...tokenResult } + const safetyLevel = getTokenSafetyLevel(token) const preference = tokenPreferences?.find((tokenPreference) => { return tokenPreference.address === token.address && tokenPreference.networkId === network.id @@ -373,6 +401,8 @@ export const processTokens = ( token.isHidden = preference.isHidden } + token.flags.safetyLevel = safetyLevel + if (tokenFilter(token, nativeToken!, network, hasNonZeroTokens, additionalHints, !!preference)) tokens.push(token) diff --git a/src/libs/portfolio/interfaces.ts b/src/libs/portfolio/interfaces.ts index 7c4f0b920..3f815c724 100644 --- a/src/libs/portfolio/interfaces.ts +++ b/src/libs/portfolio/interfaces.ts @@ -1,5 +1,5 @@ import { Account, AccountId } from '../../interfaces/account' -import { Network, NetworkId } from '../../interfaces/network' +import { NetworkId } from '../../interfaces/network' import { AccountOp } from '../accountOp/accountOp' import { CustomToken } from './customToken' @@ -23,6 +23,7 @@ export type TokenResult = Omit & { rewardsType: 'wallet-vesting' | 'wallet-rewards' | null canTopUpGasTank: boolean isFeeToken: boolean + safetyLevel?: 'trusted' | 'unknown' | 'spoof' } } @@ -100,7 +101,7 @@ export type ClaimableRewardsData = { // Create the final type with some properties optional export type AdditionalPortfolioNetworkResult = Partial & Pick & { - total: Total, + total: Total claimableRewardsData?: ClaimableRewardsData } @@ -111,7 +112,7 @@ export type NetworkState = { isLoading: boolean criticalError?: ExtendedError errors: ExtendedError[] - result?: PortfolioNetworkResult | AdditionalPortfolioNetworkResult + result?: PortfolioNetworkResult | AdditionalPortfolioNetworkResult // We store the previously simulated AccountOps only for the pending state. // Prior to triggering a pending state update, we compare the newly passed AccountOp[] (updateSelectedAccount) with the cached version. // If there are no differences, the update is canceled unless the `forceUpdate` flag is set. From 3d870c9dccb987777f54804d90b21dfc824522f6 Mon Sep 17 00:00:00 2001 From: Petromir Petrov Date: Thu, 8 Aug 2024 16:51:10 +0300 Subject: [PATCH 2/2] update: getTokenSafetyLevel to actually work --- src/libs/portfolio/helpers.ts | 38 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/libs/portfolio/helpers.ts b/src/libs/portfolio/helpers.ts index 9f422591e..5b838f6b9 100644 --- a/src/libs/portfolio/helpers.ts +++ b/src/libs/portfolio/helpers.ts @@ -3,11 +3,13 @@ import { Contract, ZeroAddress } from 'ethers' import IERC20 from '../../../contracts/compiled/IERC20.json' import gasTankFeeTokens from '../../consts/gasTankFeeTokens' import humanizerInfo from '../../consts/humanizer/humanizerInfo.json' +import { networks } from '../../consts/networks' import { PINNED_TOKENS } from '../../consts/pinnedTokens' import { Account, AccountId } from '../../interfaces/account' import { NetworkId } from '../../interfaces/network' import { RPCProvider } from '../../interfaces/provider' import { isSmartAccount } from '../account/account' +import { HumanizerMeta } from '../humanizer/interfaces' import { CustomToken } from './customToken' import { AdditionalPortfolioNetworkResult, @@ -348,29 +350,41 @@ export const tokenFilter = ( return isInAdditionalHints || pinnedRequested } -const getTokenSafetyLevel = (token: TokenResult) => { - let safetyLevel = 'unknown' as 'unknown' | 'trusted' | 'spoof' +const getTokenSafetyLevel = (token: TokenResult): TokenResult['flags']['safetyLevel'] => { + let safetyLevel: TokenResult['flags']['safetyLevel'] = 'unknown' - if (token.address === ZeroAddress) { - return 'trusted' - } + const hasFlags = token.flags.canTopUpGasTank || token.flags.isFeeToken || token.flags.onGasTank + + if (token.address === ZeroAddress || hasFlags) return 'trusted' - Object.values(humanizerInfo.knownAddresses).forEach((value) => { - const isMatching = value.address.toLowerCase() === token.address.toLowerCase() + const canTokenBeChecked = networks.some((network) => network.id === token.networkId) - if (!isMatching) return + // If the token is on a custom network we can't check if it's spoof + // thus we can't trust it + if (!canTokenBeChecked) return undefined - const isTrusted = value.name.toLowerCase().includes(token.symbol.toLowerCase()) + Object.values(humanizerInfo.knownAddresses as unknown as HumanizerMeta).some((value) => { + // Skip non-token values + if (!value.token) return false - if (isTrusted) { + // The token is found in the humanizer + if (value.address.toLowerCase() === token.address.toLowerCase()) { safetyLevel = 'trusted' - return + return true } - if (!isTrusted && safetyLevel !== 'trusted') { + + // The token is found in the humanizer with a different address + if (value.token?.symbol?.toLowerCase() === token.symbol.toLowerCase()) { safetyLevel = 'spoof' + // Don't break the loop, as we want to check if there's a trusted token with the same symbol } + + return false }) + // As long as the token isn't spoof and there is a price we can trust it + if (safetyLevel === 'unknown' && token.priceIn.length) return 'trusted' + return safetyLevel }