diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx b/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx index 9f906a3931..a5907ed2f7 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx @@ -21,6 +21,7 @@ import { gqlToCurrency, logSentryErrorForUnsupportedChain, supportedChainIdFromG import ms from 'ms' import { useEffect, useState } from 'react' import { isAddress } from 'utils' +import { isSameAddress } from 'utils/addresses' import { NumberType, useFormatter } from 'utils/formatNumbers' import { MOONPAY_SENDER_ADDRESSES, OrderStatusTable, OrderTextTable } from '../constants' @@ -77,10 +78,6 @@ const COMMON_CONTRACTS: { [key: string]: Partial | undefined } = { }, } -function isSameAddress(a?: string, b?: string) { - return a === b || a?.toLowerCase() === b?.toLowerCase() // Lazy-lowercases the addresses -} - function callsPositionManagerContract(assetActivity: TransactionActivity) { const supportedChain = supportedChainIdFromGQLChain(assetActivity.chain) if (!supportedChain) return false diff --git a/src/components/FiatOnrampModal/constants.ts b/src/components/FiatOnrampModal/constants.ts new file mode 100644 index 0000000000..db0ea4b7a5 --- /dev/null +++ b/src/components/FiatOnrampModal/constants.ts @@ -0,0 +1,17 @@ +export const MOONPAY_SUPPORTED_CURRENCY_CODES = [ + 'eth', + 'eth_arbitrum', + 'eth_optimism', + 'eth_polygon', + 'weth', + 'wbtc', + 'matic_polygon', + 'polygon', + 'usdc_arbitrum', + 'usdc_optimism', + 'usdc_polygon', + 'usdc', + 'usdt', +] as const + +export type MoonpaySupportedCurrencyCode = (typeof MOONPAY_SUPPORTED_CURRENCY_CODES)[number] diff --git a/src/components/FiatOnrampModal/index.tsx b/src/components/FiatOnrampModal/index.tsx index 17adfec653..9e77ec955f 100644 --- a/src/components/FiatOnrampModal/index.tsx +++ b/src/components/FiatOnrampModal/index.tsx @@ -10,6 +10,8 @@ import { useIsDarkMode } from 'theme/components/ThemeToggle' import Circle from '../../assets/images/blue-loader.svg' import Modal from '../Modal' +import { MOONPAY_SUPPORTED_CURRENCY_CODES } from './constants' +import { getDefaultCurrencyCode, parsePathParts } from './utils' const MOONPAY_DARK_BACKGROUND = '#1c1c1e' const Wrapper = styled.div<{ isDarkMode: boolean }>` @@ -55,20 +57,6 @@ const StyledSpinner = styled(CustomLightSpinner)` top: 0; ` -const MOONPAY_SUPPORTED_CURRENCY_CODES = [ - 'eth', - 'eth_arbitrum', - 'eth_optimism', - 'eth_polygon', - 'weth', - 'wbtc', - 'matic_polygon', - 'polygon', - 'usdc_arbitrum', - 'usdc_optimism', - 'usdc_polygon', -] - export default function FiatOnrampModal() { const { account } = useWeb3React() const theme = useTheme() @@ -76,6 +64,8 @@ export default function FiatOnrampModal() { const closeModal = useCloseModal() const fiatOnrampModalOpen = useModalIsOpen(ApplicationModal.FIAT_ONRAMP) + const { network, tokenAddress } = parsePathParts(location.pathname) + const [signedIframeUrl, setSignedIframeUrl] = useState(null) const [error, setError] = useState(null) const [loading, setLoading] = useState(false) @@ -100,7 +90,7 @@ export default function FiatOnrampModal() { body: JSON.stringify({ theme: isDarkMode ? 'dark' : 'light', colorCode: theme.accent1, - defaultCurrencyCode: 'eth', + defaultCurrencyCode: getDefaultCurrencyCode(tokenAddress, network), redirectUrl: swapUrl, walletAddresses: JSON.stringify( MOONPAY_SUPPORTED_CURRENCY_CODES.reduce( @@ -121,7 +111,7 @@ export default function FiatOnrampModal() { } finally { setLoading(false) } - }, [account, isDarkMode, swapUrl, theme.accent1]) + }, [account, isDarkMode, network, swapUrl, theme.accent1, tokenAddress]) useEffect(() => { fetchSignedIframeUrl() diff --git a/src/components/FiatOnrampModal/utils.test.ts b/src/components/FiatOnrampModal/utils.test.ts new file mode 100644 index 0000000000..4cca77699c --- /dev/null +++ b/src/components/FiatOnrampModal/utils.test.ts @@ -0,0 +1,78 @@ +import { ChainId, WETH9 } from '@uniswap/sdk-core' +import { + MATIC, + USDC_ARBITRUM, + USDC_MAINNET, + USDC_OPTIMISM, + USDC_POLYGON, + USDT, + WBTC, + WETH_POLYGON, +} from 'constants/tokens' + +import { getDefaultCurrencyCode, parsePathParts } from './utils' + +describe('getDefaultCurrencyCode', () => { + it('NATIVE/arbitrum should return the correct currency code', () => { + expect(getDefaultCurrencyCode('NATIVE', 'arbitrum')).toBe('eth_arbitrum') + }) + it('NATIVE/optimism should return the correct currency code', () => { + expect(getDefaultCurrencyCode('NATIVE', 'optimism')).toBe('eth_optimism') + }) + it('WETH/polygon should return the correct currency code', () => { + expect(getDefaultCurrencyCode(WETH_POLYGON.address, 'polygon')).toBe('eth_polygon') + }) + it('WETH/ethereum should return the correct currency code', () => { + expect(getDefaultCurrencyCode(WETH9[ChainId.MAINNET].address, 'ethereum')).toBe('weth') + }) + it('WBTC/ethereum should return the correct currency code', () => { + expect(getDefaultCurrencyCode(WBTC.address, 'ethereum')).toBe('wbtc') + }) + it('NATIVE/polygon should return the correct currency code', () => { + expect(getDefaultCurrencyCode('NATIVE', 'polygon')).toBe('matic_polygon') + }) + it('MATIC/ethereum should return the correct currency code', () => { + expect(getDefaultCurrencyCode(MATIC.address, 'ethereum')).toBe('polygon') + }) + it('USDC/arbitrum should return the correct currency code', () => { + expect(getDefaultCurrencyCode(USDC_ARBITRUM.address, 'arbitrum')).toBe('usdc_arbitrum') + }) + it('USDC/optimism should return the correct currency code', () => { + expect(getDefaultCurrencyCode(USDC_OPTIMISM.address, 'optimism')).toBe('usdc_optimism') + }) + it('USDC/polygon should return the correct currency code', () => { + expect(getDefaultCurrencyCode(USDC_POLYGON.address, 'polygon')).toBe('usdc_polygon') + }) + it('native/ethereum should return the correct currency code', () => { + expect(getDefaultCurrencyCode('NATIVE', 'ethereum')).toBe('eth') + }) + it('usdc/ethereum should return the correct currency code', () => { + expect(getDefaultCurrencyCode(USDC_MAINNET.address, 'ethereum')).toBe('usdc') + }) + it('usdt/ethereum should return the correct currency code', () => { + expect(getDefaultCurrencyCode(USDT.address, 'ethereum')).toBe('usdt') + }) + it('chain/token mismatch should default to eth', () => { + expect(getDefaultCurrencyCode(USDC_ARBITRUM.address, 'ethereum')).toBe('eth') + expect(getDefaultCurrencyCode(USDC_OPTIMISM.address, 'ethereum')).toBe('eth') + expect(getDefaultCurrencyCode(USDC_POLYGON.address, 'ethereum')).toBe('eth') + expect(getDefaultCurrencyCode(MATIC.address, 'arbitrum')).toBe('eth') + }) +}) + +describe('parseLocation', () => { + it('should parse the URL correctly', () => { + expect(parsePathParts('/tokens/ethereum/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599')).toEqual({ + network: 'ethereum', + tokenAddress: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', + }) + expect(parsePathParts('tokens/ethereum/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599')).toEqual({ + network: 'ethereum', + tokenAddress: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', + }) + expect(parsePathParts('/swap')).toEqual({ + network: undefined, + tokenAddress: undefined, + }) + }) +}) diff --git a/src/components/FiatOnrampModal/utils.ts b/src/components/FiatOnrampModal/utils.ts new file mode 100644 index 0000000000..032d7c1184 --- /dev/null +++ b/src/components/FiatOnrampModal/utils.ts @@ -0,0 +1,74 @@ +import { ChainId, WETH9 } from '@uniswap/sdk-core' +import { + BRIDGED_USDC_ARBITRUM, + MATIC, + USDC_ARBITRUM, + USDC_MAINNET, + USDC_OPTIMISM, + USDC_POLYGON, + USDT, + WBTC, + WETH_POLYGON, +} from 'constants/tokens' +import { Chain } from 'graphql/data/__generated__/types-and-hooks' +import { validateUrlChainParam } from 'graphql/data/util' + +import { MoonpaySupportedCurrencyCode } from './constants' + +type MoonpaySupportedChain = Chain.Ethereum | Chain.Polygon | Chain.Arbitrum | Chain.Optimism +const moonPaySupportedChains = [Chain.Ethereum, Chain.Polygon, Chain.Arbitrum, Chain.Optimism] + +const CURRENCY_CODES: { + [K in MoonpaySupportedChain]: { + [key: string]: MoonpaySupportedCurrencyCode + native: MoonpaySupportedCurrencyCode + } +} = { + [Chain.Ethereum]: { + [WETH9[ChainId.MAINNET]?.address.toLowerCase()]: 'weth', + [USDC_MAINNET.address.toLowerCase()]: 'usdc', + [USDT.address.toLowerCase()]: 'usdt', + [WBTC.address.toLowerCase()]: 'wbtc', + [MATIC.address.toLowerCase()]: 'polygon', + native: 'eth', + }, + [Chain.Arbitrum]: { + [USDC_ARBITRUM.address.toLowerCase()]: 'usdc_arbitrum', + [BRIDGED_USDC_ARBITRUM.address.toLowerCase()]: 'usdc_arbitrum', + native: 'eth_arbitrum', + }, + [Chain.Optimism]: { + [USDC_OPTIMISM.address.toLowerCase()]: 'usdc_optimism', + native: 'eth_optimism', + }, + [Chain.Polygon]: { + [USDC_POLYGON.address.toLowerCase()]: 'usdc_polygon', + [WETH_POLYGON.address.toLowerCase()]: 'eth_polygon', + native: 'matic_polygon', + }, +} + +export function getDefaultCurrencyCode( + address: string | undefined, + chainName: string | undefined +): MoonpaySupportedCurrencyCode { + const chain = validateUrlChainParam(chainName) + if (!address || !chain) return 'eth' + if (moonPaySupportedChains.includes(chain)) { + const code = CURRENCY_CODES[chain as MoonpaySupportedChain]?.[address.toLowerCase()] + return code ?? 'eth' + } + return 'eth' +} + +/** + * You should use useParams() from react-router-dom instead of this function if possible. + * This function is only used in the case where we need to parse the path outside the scope of the router. + */ +export function parsePathParts(pathname: string): { network?: string; tokenAddress?: string } { + const pathParts = pathname.split('/') + // Matches the /tokens// path. + const network = pathParts.length > 2 ? pathParts[pathParts.length - 2] : undefined + const tokenAddress = pathParts.length > 2 ? pathParts[pathParts.length - 1] : undefined + return { network, tokenAddress } +} diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 494493013f..f09ead0a1e 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -96,6 +96,13 @@ export const DAI_OPTIMISM = new Token( 'DAI', 'Dai stable coin' ) +export const MATIC = new Token( + ChainId.MAINNET, + '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0', + 18, + 'MATIC', + 'Polygon Matic' +) export const DAI_POLYGON = new Token( ChainId.POLYGON, '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063', diff --git a/src/utils/addresses.ts b/src/utils/addresses.ts index 48e954bd36..263530eddc 100644 --- a/src/utils/addresses.ts +++ b/src/utils/addresses.ts @@ -11,6 +11,10 @@ export function isAddress(value: any): string | false { } } +export function isSameAddress(a?: string, b?: string) { + return a === b || a?.toLowerCase() === b?.toLowerCase() // Lazy-lowercases the addresses +} + // Shortens an Ethereum address export function shortenAddress(address = '', charsStart = 4, charsEnd = 4): string { const parsed = isAddress(address)