Commit 102a935f authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

fix: Handle new and unsupported chains passed in from GQL BE (#6878)

* add subset chain type and checks

* add sentry logging for invalid chain errors

* add test cases

* dynamic token balances

* add test for BE adding a new chain

* rename and use slice

* address comments

* undo yarn.lock changes

* make copy in utils

* chore: Declare GQL variables as readonly (#6889)

* declare gql variables as readonly

* remove console log

* Merge branch 'cab/error_supported_chain' into tina/gql-readonly

---------
Co-authored-by: default avatarCharlie B <charles@bachmeier.io>

---------
Co-authored-by: default avatarCharlie Bachmeier <charlie.bachmeier@Charlies-MacBook-Pro.local>
Co-authored-by: default avatarTina <59578595+tinaszheng@users.noreply.github.com>
parent 614c1524
......@@ -15,6 +15,7 @@ const config: CodegenConfig = {
withHooks: true,
// This avoid all generated schemas being wrapped in Maybe https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#maybevalue-string-default-value-t--null
maybeValue: 'T',
immutableTypes: true,
},
},
},
......
......@@ -12,6 +12,7 @@ import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
import Tooltip from 'components/Tooltip'
import { getConnection } from 'connection'
import { usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
import { GQL_MAINNET_CHAINS } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
......@@ -227,9 +228,10 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
const closeFiatOnrampUnavailableTooltip = useCallback(() => setShow(false), [setShow])
const { data: portfolioBalances } = usePortfolioBalancesQuery({
variables: { ownerAddress: account ?? '' },
variables: { ownerAddress: account ?? '', chains: GQL_MAINNET_CHAINS },
fetchPolicy: 'cache-only', // PrefetchBalancesWrapper handles balance fetching/staleness; this component only reads from cache
})
const portfolio = portfolioBalances?.portfolios?.[0]
const totalBalance = portfolio?.tokensTotalDenominatedValue?.value
const absoluteChange = portfolio?.tokensTotalDenominatedValueChange?.absolute?.value
......
......@@ -14,7 +14,7 @@ import {
TokenApprovalPartsFragment,
TokenTransferPartsFragment,
} from 'graphql/data/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'graphql/data/util'
import { logSentryErrorForUnsupportedChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
import ms from 'ms.macro'
import { useEffect, useState } from 'react'
import { isAddress } from 'utils'
......@@ -76,10 +76,9 @@ function isSameAddress(a?: string, b?: string) {
}
function callsPositionManagerContract(assetActivity: AssetActivityPartsFragment) {
return isSameAddress(
assetActivity.transaction.to,
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[fromGraphQLChain(assetActivity.chain)]
)
const supportedChain = supportedChainIdFromGQLChain(assetActivity.chain)
if (!supportedChain) return false
return isSameAddress(assetActivity.transaction.to, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[supportedChain])
}
// Gets counts for number of NFTs in each collection present
......@@ -93,15 +92,24 @@ function getCollectionCounts(nftTransfers: NftTransferPartsFragment[]): { [key:
}, {} as { [key: string]: number | undefined })
}
function getSwapTitle(sent: TokenTransferPartsFragment, received: TokenTransferPartsFragment) {
function getSwapTitle(sent: TokenTransferPartsFragment, received: TokenTransferPartsFragment): string | undefined {
const supportedSentChain = supportedChainIdFromGQLChain(sent.asset.chain)
const supportedReceivedChain = supportedChainIdFromGQLChain(received.asset.chain)
if (!supportedSentChain || !supportedReceivedChain) {
logSentryErrorForUnsupportedChain({
extras: { sentAsset: sent.asset, receivedAsset: received.asset },
errorMessage: 'Invalid activity from unsupported chain received from GQL',
})
return undefined
}
if (
sent.tokenStandard === 'NATIVE' &&
isSameAddress(nativeOnChain(fromGraphQLChain(sent.asset.chain)).wrapped.address, received.asset.address)
isSameAddress(nativeOnChain(supportedSentChain).wrapped.address, received.asset.address)
)
return t`Wrapped`
else if (
received.tokenStandard === 'NATIVE' &&
isSameAddress(nativeOnChain(fromGraphQLChain(received.asset.chain)).wrapped.address, received.asset.address)
isSameAddress(nativeOnChain(supportedReceivedChain).wrapped.address, received.asset.address)
) {
return t`Unwrapped`
} else {
......@@ -269,9 +277,17 @@ function parseRemoteActivity(assetActivity: AssetActivityPartsFragment): Activit
},
{ NftTransfer: [], TokenTransfer: [], TokenApproval: [], NftApproval: [], NftApproveForAll: [] }
)
const supportedChain = supportedChainIdFromGQLChain(assetActivity.chain)
if (!supportedChain) {
logSentryErrorForUnsupportedChain({
extras: { assetActivity },
errorMessage: 'Invalid activity from unsupported chain received from GQL',
})
return undefined
}
const defaultFields = {
hash: assetActivity.transaction.hash,
chainId: fromGraphQLChain(assetActivity.chain),
chainId: supportedChain,
status: assetActivity.transaction.status,
timestamp: assetActivity.timestamp,
logos: getLogoSrcs(changes),
......@@ -289,7 +305,7 @@ function parseRemoteActivity(assetActivity: AssetActivityPartsFragment): Activit
}
}
export function parseRemoteActivities(assetActivities?: AssetActivityPartsFragment[]) {
export function parseRemoteActivities(assetActivities?: readonly AssetActivityPartsFragment[]) {
return assetActivities?.reduce((acc: { [hash: string]: Activity }, assetActivity) => {
const activity = parseRemoteActivity(assetActivity)
if (activity) acc[activity.hash] = activity
......
......@@ -4,7 +4,12 @@ import { formatNumber, NumberType } from '@uniswap/conedison/format'
import Row from 'components/Row'
import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
import { PortfolioBalancesQuery, usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
import { getTokenDetailsURL, gqlToCurrency } from 'graphql/data/util'
import {
getTokenDetailsURL,
GQL_MAINNET_CHAINS,
gqlToCurrency,
logSentryErrorForUnsupportedChain,
} from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
import { useCallback, useMemo, useState } from 'react'
......@@ -31,7 +36,7 @@ export default function Tokens({ account }: { account: string }) {
const [showHiddenTokens, setShowHiddenTokens] = useState(false)
const { data } = usePortfolioBalancesQuery({
variables: { ownerAddress: account },
variables: { ownerAddress: account, chains: GQL_MAINNET_CHAINS },
fetchPolicy: 'cache-only', // PrefetchBalancesWrapper handles balance fetching/staleness; this component only reads from cache
errorPolicy: 'all',
})
......@@ -103,6 +108,13 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok
}, [navigate, token, toggleWalletDrawer])
const currency = gqlToCurrency(token)
if (!currency) {
logSentryErrorForUnsupportedChain({
extras: { token },
errorMessage: 'Token from unsupported chain received from Mini Portfolio Token Balance Query',
})
return null
}
return (
<TraceEvent
events={[BrowserEvent.onClick]}
......
import { useWeb3React } from '@web3-react/core'
import { usePortfolioBalancesLazyQuery } from 'graphql/data/__generated__/types-and-hooks'
import { GQL_MAINNET_CHAINS } from 'graphql/data/util'
import usePrevious from 'hooks/usePrevious'
import { PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react'
import { useAllTransactions } from 'state/transactions/hooks'
......@@ -44,7 +45,7 @@ export default function PrefetchBalancesWrapper({ children }: PropsWithChildren)
const [hasUnfetchedBalances, setHasUnfetchedBalances] = useState(true)
const fetchBalances = useCallback(() => {
if (account) {
prefetchPortfolioBalances({ variables: { ownerAddress: account } })
prefetchPortfolioBalances({ variables: { ownerAddress: account, chains: GQL_MAINNET_CHAINS } })
setHasUnfetchedBalances(false)
}
}, [account, prefetchPortfolioBalances])
......
......@@ -3,7 +3,7 @@ import { TokenStandard } from 'graphql/data/__generated__/types-and-hooks'
import { SearchToken } from 'graphql/data/SearchTokens'
import { TokenQueryData } from 'graphql/data/Token'
import { TopToken } from 'graphql/data/TopTokens'
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
import { supportedChainIdFromGQLChain } from 'graphql/data/util'
import AssetLogo, { AssetLogoBaseProps } from './AssetLogo'
......@@ -12,7 +12,7 @@ export default function QueryTokenLogo(
token?: TopToken | TokenQueryData | SearchToken
}
) {
const chainId = props.token?.chain ? CHAIN_NAME_TO_CHAIN_ID[props.token?.chain] : undefined
const chainId = props.token?.chain ? supportedChainIdFromGQLChain(props.token?.chain) : undefined
return (
<AssetLogo
......
import { SupportedChainId } from 'constants/chains'
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
import { Chain, NftCollection, useRecentlySearchedAssetsQuery } from 'graphql/data/__generated__/types-and-hooks'
import { SearchToken } from 'graphql/data/SearchTokens'
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
import { logSentryErrorForUnsupportedChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
import { useAtom } from 'jotai'
import { atomWithStorage, useAtomValue } from 'jotai/utils'
import { GenieCollection } from 'nft/types'
......@@ -86,7 +85,15 @@ export function useRecentlySearchedAssets() {
shortenedHistory.forEach((asset) => {
if (asset.address === 'NATIVE') {
// Handles special case where wMATIC data needs to be used for MATIC
const native = nativeOnChain(CHAIN_NAME_TO_CHAIN_ID[asset.chain] ?? SupportedChainId.MAINNET)
const chain = supportedChainIdFromGQLChain(asset.chain)
if (!chain) {
logSentryErrorForUnsupportedChain({
extras: { asset },
errorMessage: 'Invalid chain retrieved from Seach Token/Collection Query',
})
return
}
const native = nativeOnChain(chain)
const queryAddress = getQueryAddress(asset.chain)?.toLowerCase() ?? `NATIVE-${asset.chain}`
const result = resultsMap[queryAddress]
if (result) data.push({ ...result, address: 'NATIVE', ...native })
......
......@@ -26,7 +26,7 @@ import { checkWarning } from 'constants/tokenSafety'
import { TokenPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token'
import { QueryToken } from 'graphql/data/Token'
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util'
import { getTokenDetailsURL, InterfaceGqlChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency'
import { Swap } from 'pages/Swap'
......@@ -89,7 +89,7 @@ function useRelevantToken(
type TokenDetailsProps = {
urlAddress?: string
inputTokenAddress?: string
chain: Chain
chain: InterfaceGqlChain
tokenQuery: TokenQuery
tokenPriceQuery?: TokenPriceQuery
onChangeTimePeriod: OnChangeTimePeriod
......@@ -111,8 +111,7 @@ export default function TokenDetails({
)
const { chainId: connectedChainId } = useWeb3React()
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const pageChainId = supportedChainIdFromGQLChain(chain)
const tokenQueryData = tokenQuery.token
const crossChainMap = useMemo(
() =>
......@@ -185,6 +184,7 @@ export default function TokenDetails({
},
[continueSwap, setContinueSwap]
)
// address will never be undefined if token is defined; address is checked here to appease typechecker
if (detailedToken === undefined || !address) {
return <InvalidTokenDetails pageChainId={pageChainId} isInvalidAddress={!address} />
......
import Badge from 'components/Badge'
import { getChainInfo } from 'constants/chainInfo'
import { BACKEND_CHAIN_NAMES, CHAIN_NAME_TO_CHAIN_ID, validateUrlChainParam } from 'graphql/data/util'
import { Chain } from 'graphql/data/__generated__/types-and-hooks'
import { BACKEND_CHAIN_NAMES, supportedChainIdFromGQLChain, validateUrlChainParam } from 'graphql/data/util'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useRef } from 'react'
import { Check, ChevronDown, ChevronUp } from 'react-feather'
......@@ -116,8 +117,8 @@ export default function NetworkFilter() {
const { chainName } = useParams<{ chainName?: string }>()
const currentChainName = validateUrlChainParam(chainName)
const chainInfo = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[currentChainName])
const BNBChainInfo = getChainInfo(CHAIN_NAME_TO_CHAIN_ID.BNB)
const chainInfo = getChainInfo(supportedChainIdFromGQLChain(currentChainName))
const BNBChainInfo = getChainInfo(supportedChainIdFromGQLChain(Chain.Bnb))
return (
<StyledMenu ref={node}>
......@@ -129,7 +130,7 @@ export default function NetworkFilter() {
>
<StyledMenuContent>
<NetworkLabel>
<Logo src={chainInfo?.logoUrl} /> {chainInfo?.label}
<Logo src={chainInfo.logoUrl} /> {chainInfo.label}
</NetworkLabel>
<Chevron open={open}>
{open ? (
......@@ -143,8 +144,7 @@ export default function NetworkFilter() {
{open && (
<MenuTimeFlyout>
{BACKEND_CHAIN_NAMES.map((network) => {
const chainInfo = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[network])
if (!chainInfo) return null
const chainInfo = getChainInfo(supportedChainIdFromGQLChain(network))
return (
<InternalLinkMenuItem
key={network}
......
......@@ -7,7 +7,7 @@ import SparklineChart from 'components/Charts/SparklineChart'
import QueryTokenLogo from 'components/Logo/QueryTokenLogo'
import { MouseoverTooltip } from 'components/Tooltip'
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL, validateUrlChainParam } from 'graphql/data/util'
import { getTokenDetailsURL, supportedChainIdFromGQLChain, validateUrlChainParam } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import { ForwardedRef, forwardRef } from 'react'
import { CSSProperties, ReactNode } from 'react'
......@@ -435,7 +435,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
const filterString = useAtomValue(filterStringAtom)
const filterNetwork = validateUrlChainParam(useParams<{ chainName?: string }>().chainName?.toUpperCase())
const chainId = CHAIN_NAME_TO_CHAIN_ID[filterNetwork]
const chainId = supportedChainIdFromGQLChain(filterNetwork)
const timePeriod = useAtomValue(filterTimeAtom)
const delta = token.market?.pricePercentChange?.value
const arrow = getDeltaArrow(delta)
......
......@@ -3,7 +3,7 @@ import gql from 'graphql-tag'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import { TokenQuery } from './__generated__/types-and-hooks'
import { CHAIN_NAME_TO_CHAIN_ID } from './util'
import { supportedChainIdFromGQLChain } from './util'
// The difference between Token and TokenProject:
// Token: an on-chain entity referring to a contract (e.g. uni token on ethereum 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984)
......@@ -70,13 +70,16 @@ export type TokenQueryData = TokenQuery['token']
// TODO: Return a QueryToken from useTokenQuery instead of TokenQueryData to make it more usable in Currency-centric interfaces.
export class QueryToken extends WrappedTokenInfo {
constructor(address: string, data: NonNullable<TokenQueryData>, logoSrc?: string) {
super({
chainId: CHAIN_NAME_TO_CHAIN_ID[data.chain],
address,
decimals: data.decimals ?? DEFAULT_ERC20_DECIMALS,
symbol: data.symbol ?? '',
name: data.name ?? '',
logoURI: logoSrc ?? data.project?.logoUrl ?? undefined,
})
const chainId = supportedChainIdFromGQLChain(data.chain)
if (chainId) {
super({
chainId,
address,
decimals: data.decimals ?? DEFAULT_ERC20_DECIMALS,
symbol: data.symbol ?? '',
name: data.name ?? '',
logoURI: logoSrc ?? data.project?.logoUrl ?? undefined,
})
}
}
}
......@@ -16,10 +16,10 @@ import {
useTopTokensSparklineQuery,
} from './__generated__/types-and-hooks'
import {
CHAIN_NAME_TO_CHAIN_ID,
isPricePoint,
PollingInterval,
PricePoint,
supportedChainIdFromGQLChain,
toHistoryDuration,
unwrapToken,
usePollQueryWhileMounted,
......@@ -140,14 +140,14 @@ export type SparklineMap = { [key: string]: PricePoint[] | undefined }
export type TopToken = NonNullable<NonNullable<TopTokens100Query>['topTokens']>[number]
interface UseTopTokensReturnValue {
tokens?: TopToken[]
tokens?: readonly TopToken[]
tokenSortRank: Record<string, number>
loadingTokens: boolean
sparklines: SparklineMap
}
export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
const chainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const chainId = supportedChainIdFromGQLChain(chain)
const duration = toHistoryDuration(useAtomValue(filterTimeAtom))
const { data: sparklineQuery } = usePollQueryWhileMounted(
......@@ -158,7 +158,7 @@ export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
)
const sparklines = useMemo(() => {
const unwrappedTokens = sparklineQuery?.topTokens?.map((topToken) => unwrapToken(chainId, topToken))
const unwrappedTokens = chainId && sparklineQuery?.topTokens?.map((topToken) => unwrapToken(chainId, topToken))
const map: SparklineMap = {}
unwrappedTokens?.forEach(
(current) => current?.address && (map[current.address] = current?.market?.priceHistory?.filter(isPricePoint))
......@@ -173,7 +173,10 @@ export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
PollingInterval.Fast
)
const unwrappedTokens = useMemo(() => data?.topTokens?.map((token) => unwrapToken(chainId, token)), [chainId, data])
const unwrappedTokens = useMemo(
() => chainId && data?.topTokens?.map((token) => unwrapToken(chainId, token)),
[chainId, data]
)
const sortedTokens = useSortedTokens(unwrappedTokens)
const tokenSortRank = useMemo(
() =>
......
import gql from 'graphql-tag'
gql`
query PortfolioBalances($ownerAddress: String!) {
portfolios(ownerAddresses: [$ownerAddress], chains: [ETHEREUM, POLYGON, ARBITRUM, OPTIMISM, BNB]) {
query PortfolioBalances($ownerAddress: String!, $chains: [Chain!]!) {
portfolios(ownerAddresses: [$ownerAddress], chains: $chains) {
id
tokensTotalDenominatedValue {
id
......
import { SupportedChainId } from 'constants/chains'
import { Chain } from './__generated__/types-and-hooks'
import { isSupportedGQLChain, supportedChainIdFromGQLChain } from './util'
describe('fromGraphQLChain', () => {
it('should return the corresponding chain ID for supported chains', () => {
expect(supportedChainIdFromGQLChain(Chain.Ethereum)).toBe(SupportedChainId.MAINNET)
for (const chain of Object.values(Chain)) {
if (!isSupportedGQLChain(chain)) continue
expect(supportedChainIdFromGQLChain(chain)).not.toBe(undefined)
}
})
it('should return undefined for unsupported chains', () => {
expect(supportedChainIdFromGQLChain(Chain.UnknownChain)).toBe(undefined)
for (const chain of Object.values(Chain)) {
if (isSupportedGQLChain(chain)) continue
expect(supportedChainIdFromGQLChain(chain)).toBe(undefined)
}
})
it('should not crash when a new BE chain is added', () => {
enum NewChain {
NewChain = 'NEW_CHAIN',
}
const ExpandedChainList = [...Object.values(Chain), NewChain.NewChain as unknown as Chain]
for (const chain of ExpandedChainList) {
if (isSupportedGQLChain(chain)) continue
expect(supportedChainIdFromGQLChain(chain)).toBe(undefined)
}
})
})
import { QueryResult } from '@apollo/client'
import * as Sentry from '@sentry/react'
import { Currency, Token } from '@uniswap/sdk-core'
import { SupportedChainId } from 'constants/chains'
import { NATIVE_CHAIN_ID, nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
......@@ -56,10 +57,24 @@ export function isPricePoint(p: PricePoint | null): p is PricePoint {
return p !== null
}
// TODO(DAT-33) Update when BE adds Ethereum Sepolia to supported chains
export const CHAIN_ID_TO_BACKEND_NAME: { [key: number]: Chain } = {
export const GQL_MAINNET_CHAINS = [
Chain.Ethereum,
Chain.Polygon,
Chain.Celo,
Chain.Optimism,
Chain.Arbitrum,
Chain.Bnb,
] as const
const GQL_TESTNET_CHAINS = [Chain.EthereumGoerli, Chain.EthereumSepolia] as const
const UX_SUPPORTED_GQL_CHAINS = [...GQL_MAINNET_CHAINS, ...GQL_TESTNET_CHAINS] as const
export type InterfaceGqlChain = typeof UX_SUPPORTED_GQL_CHAINS[number]
export const CHAIN_ID_TO_BACKEND_NAME: { [key: number]: InterfaceGqlChain } = {
[SupportedChainId.MAINNET]: Chain.Ethereum,
[SupportedChainId.GOERLI]: Chain.EthereumGoerli,
[SupportedChainId.SEPOLIA]: Chain.EthereumSepolia,
[SupportedChainId.POLYGON]: Chain.Polygon,
[SupportedChainId.POLYGON_MUMBAI]: Chain.Polygon,
[SupportedChainId.CELO]: Chain.Celo,
......@@ -100,13 +115,14 @@ export function gqlToCurrency(token: {
decimals?: number
name?: string
symbol?: string
}): Currency {
const chainId = fromGraphQLChain(token.chain)
}): Currency | undefined {
const chainId = supportedChainIdFromGQLChain(token.chain)
if (!chainId) return undefined
if (token.standard === TokenStandard.Native || !token.address) return nativeOnChain(chainId)
else return new Token(chainId, token.address, token.decimals ?? 18, token.name, token.symbol)
}
const URL_CHAIN_PARAM_TO_BACKEND: { [key: string]: Chain } = {
const URL_CHAIN_PARAM_TO_BACKEND: { [key: string]: InterfaceGqlChain } = {
ethereum: Chain.Ethereum,
polygon: Chain.Polygon,
celo: Chain.Celo,
......@@ -119,8 +135,7 @@ export function validateUrlChainParam(chainName: string | undefined) {
return chainName && URL_CHAIN_PARAM_TO_BACKEND[chainName] ? URL_CHAIN_PARAM_TO_BACKEND[chainName] : Chain.Ethereum
}
// TODO(cartcrom): refactor into safer lookup & replace usage
export const CHAIN_NAME_TO_CHAIN_ID: { [key in Chain]: SupportedChainId } = {
const CHAIN_NAME_TO_CHAIN_ID: { [key in InterfaceGqlChain]: SupportedChainId } = {
[Chain.Ethereum]: SupportedChainId.MAINNET,
[Chain.EthereumGoerli]: SupportedChainId.GOERLI,
[Chain.EthereumSepolia]: SupportedChainId.SEPOLIA,
......@@ -128,15 +143,42 @@ export const CHAIN_NAME_TO_CHAIN_ID: { [key in Chain]: SupportedChainId } = {
[Chain.Celo]: SupportedChainId.CELO,
[Chain.Optimism]: SupportedChainId.OPTIMISM,
[Chain.Arbitrum]: SupportedChainId.ARBITRUM_ONE,
[Chain.UnknownChain]: SupportedChainId.MAINNET,
[Chain.Bnb]: SupportedChainId.BNB,
}
export function fromGraphQLChain(chain: Chain): SupportedChainId {
return CHAIN_NAME_TO_CHAIN_ID[chain]
export function isSupportedGQLChain(chain: Chain): chain is InterfaceGqlChain {
return (UX_SUPPORTED_GQL_CHAINS as ReadonlyArray<Chain>).includes(chain)
}
export function supportedChainIdFromGQLChain(chain: InterfaceGqlChain): SupportedChainId
export function supportedChainIdFromGQLChain(chain: Chain): SupportedChainId | undefined
export function supportedChainIdFromGQLChain(chain: Chain): SupportedChainId | undefined {
return isSupportedGQLChain(chain) ? CHAIN_NAME_TO_CHAIN_ID[chain] : undefined
}
export const BACKEND_CHAIN_NAMES: Chain[] = [Chain.Ethereum, Chain.Polygon, Chain.Optimism, Chain.Arbitrum, Chain.Celo]
export function logSentryErrorForUnsupportedChain({
extras,
errorMessage,
}: {
extras?: Record<string, any>
errorMessage: string
}) {
Sentry.withScope((scope) => {
extras &&
Object.entries(extras).map(([k, v]) => {
scope.setExtra(k, v)
})
Sentry.captureException(new Error(errorMessage))
})
}
export const BACKEND_CHAIN_NAMES: InterfaceGqlChain[] = [
Chain.Ethereum,
Chain.Polygon,
Chain.Optimism,
Chain.Arbitrum,
Chain.Celo,
]
export function getTokenDetailsURL({
address,
......
......@@ -32,7 +32,7 @@ function buildRoutingItem(routingItem: NftTrade): RoutingItem {
}
}
function buildRoutingItems(routingItems: NftTrade[]): RoutingItem[] {
function buildRoutingItems(routingItems: readonly NftTrade[]): RoutingItem[] {
return routingItems.map(buildRoutingItem)
}
......
......@@ -3,7 +3,7 @@ import { SupportedChainId } from 'constants/chains'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
import { TokenStandard } from 'graphql/data/__generated__/types-and-hooks'
import { Chain } from 'graphql/data/Token'
import { fromGraphQLChain } from 'graphql/data/util'
import { supportedChainIdFromGQLChain } from 'graphql/data/util'
export type CurrencyKey = string
......@@ -21,8 +21,9 @@ export function currencyKeyFromGraphQL(contract: {
chain: Chain
standard?: TokenStandard
}): CurrencyKey {
const chainId = fromGraphQLChain(contract.chain)
const chainId = supportedChainIdFromGQLChain(contract.chain)
const address = contract.standard === TokenStandard.Native ? NATIVE_CHAIN_ID : contract.address
if (!address) throw new Error('Non-native token missing address')
if (!chainId) throw new Error('Unsupported chain from pools query')
return buildCurrencyKey(chainId, address)
}
import { nativeOnChain } from 'constants/tokens'
import { Chain } from 'graphql/data/__generated__/types-and-hooks'
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
import { supportedChainIdFromGQLChain } from 'graphql/data/util'
export function getNativeTokenDBAddress(chain: Chain): string | undefined {
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const pageChainId = supportedChainIdFromGQLChain(chain)
if (pageChainId === undefined) {
return undefined
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment