Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99a7fb3383 | ||
|
|
7f4dbf9346 | ||
|
|
09b00c9974 | ||
|
|
b74fb8174d | ||
|
|
a7ec5a64b7 | ||
|
|
c619dcf65d | ||
|
|
1221d88e13 | ||
|
|
48d2ead71d | ||
|
|
ed7099bfd6 | ||
|
|
2604cdfdae | ||
|
|
94dc389812 | ||
|
|
4a8c621f46 | ||
|
|
477af8af4e | ||
|
|
a9a7d524aa | ||
|
|
1cdaff8ddf | ||
|
|
eeea3d2dcc | ||
|
|
f46b6a0697 | ||
|
|
622581ee0a |
1
.env
1
.env
@@ -5,3 +5,4 @@ REACT_APP_AWS_API_ACCESS_KEY="AKIAYJJWW6AQ47ODATHN"
|
||||
REACT_APP_AWS_API_ACCESS_SECRET="V9PoU0FhBP3cX760rPs9jMG/MIuDNLX6hYvVcaYO"
|
||||
REACT_APP_AWS_X_API_KEY="z9dReS5UtHu7iTrUsTuWRozLthi3AxOZlvobrIdr14"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
|
||||
50
cypress/e2e/wallet-dropdown.test.ts
Normal file
50
cypress/e2e/wallet-dropdown.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Wallet Dropdown', () => {
|
||||
before(() => {
|
||||
cy.visit('/', { featureFlags: [FeatureFlag.navBar, FeatureFlag.tokenSafety] })
|
||||
})
|
||||
|
||||
it('should change the theme', () => {
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).contains('Light theme').should('exist')
|
||||
})
|
||||
|
||||
it('should select a language', () => {
|
||||
cy.get(getTestSelector('wallet-select-language')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should be able to view transactions', () => {
|
||||
cy.get(getTestSelector('wallet-transactions')).click()
|
||||
cy.get(getTestSelector('wallet-empty-transaction-text')).should('exist')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should change the theme when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-disconnect')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).contains('Dark theme').should('exist')
|
||||
})
|
||||
|
||||
it('should select a language when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-select-language')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should open the wallet connect modal from the drop down when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-connect-wallet')).click()
|
||||
cy.get(getTestSelector('wallet-modal')).should('exist')
|
||||
cy.get(getTestSelector('wallet-modal-close')).click()
|
||||
})
|
||||
})
|
||||
@@ -9,6 +9,8 @@
|
||||
import { injected } from './ethereum'
|
||||
import assert = require('assert')
|
||||
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
@@ -17,6 +19,7 @@ declare global {
|
||||
}
|
||||
interface VisitOptions {
|
||||
serviceWorker?: true
|
||||
featureFlags?: Array<FeatureFlag>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +39,18 @@ Cypress.Commands.overwrite(
|
||||
options?.onBeforeLoad?.(win)
|
||||
win.localStorage.clear()
|
||||
win.localStorage.setItem('redux_localstorage_simple_user', '{"selectedWallet":"INJECTED"}')
|
||||
|
||||
if (options?.featureFlags) {
|
||||
const featureFlags = options.featureFlags.reduce(
|
||||
(flags, flag) => ({
|
||||
...flags,
|
||||
[flag]: 'enabled',
|
||||
}),
|
||||
{}
|
||||
)
|
||||
win.localStorage.setItem('featureFlags', JSON.stringify(featureFlags))
|
||||
}
|
||||
|
||||
win.ethereum = injected
|
||||
},
|
||||
})
|
||||
|
||||
1
cypress/utils/index.ts
Normal file
1
cypress/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const getTestSelector = (selectorId: string) => `[data-testid=${selectorId}]`
|
||||
@@ -137,7 +137,7 @@
|
||||
"@uniswap/redux-multicall": "^1.1.5",
|
||||
"@uniswap/router-sdk": "^1.3.0",
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.9.2",
|
||||
"@uniswap/smart-order-router": "^2.10.0",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.30",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
|
||||
@@ -4,9 +4,10 @@ import useInterval from 'lib/hooks/useInterval'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { usePopper } from 'react-popper'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Z_INDEX } from 'theme'
|
||||
|
||||
const PopoverContainer = styled.div<{ show: boolean }>`
|
||||
z-index: 9999;
|
||||
z-index: ${Z_INDEX.absoluteTop};
|
||||
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
|
||||
opacity: ${(props) => (props.show ? 1 : 0)};
|
||||
transition: visibility 150ms linear, opacity 150ms linear;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
import { useUserAddedTokens } from 'state/user/hooks'
|
||||
|
||||
import useLast from '../../hooks/useLast'
|
||||
import { useWindowSize } from '../../hooks/useWindowSize'
|
||||
import Modal from '../Modal'
|
||||
import { CurrencySearch } from './CurrencySearch'
|
||||
import { ImportList } from './ImportList'
|
||||
@@ -97,11 +98,16 @@ export default memo(function CurrencySearchModal({
|
||||
[setModalView, prevView]
|
||||
)
|
||||
|
||||
const { height: windowHeight } = useWindowSize()
|
||||
// change min height if not searching
|
||||
let minHeight: number | undefined = 80
|
||||
let modalHeight: number | undefined = 80
|
||||
let content = null
|
||||
switch (modalView) {
|
||||
case CurrencyModalView.search:
|
||||
if (windowHeight) {
|
||||
// Converts pixel units to vh for Modal component
|
||||
modalHeight = Math.min(Math.round((680 / windowHeight) * 100), 80)
|
||||
}
|
||||
content = (
|
||||
<CurrencySearch
|
||||
isOpen={isOpen}
|
||||
@@ -119,7 +125,7 @@ export default memo(function CurrencySearchModal({
|
||||
)
|
||||
break
|
||||
case CurrencyModalView.tokenSafety:
|
||||
minHeight = undefined
|
||||
modalHeight = undefined
|
||||
if (tokenSafetyFlag === TokenSafetyVariant.Enabled && warningToken) {
|
||||
content = (
|
||||
<TokenSafety
|
||||
@@ -133,7 +139,7 @@ export default memo(function CurrencySearchModal({
|
||||
break
|
||||
case CurrencyModalView.importToken:
|
||||
if (importToken) {
|
||||
minHeight = undefined
|
||||
modalHeight = undefined
|
||||
if (tokenSafetyFlag === TokenSafetyVariant.Enabled) {
|
||||
showTokenSafetySpeedbump(importToken)
|
||||
}
|
||||
@@ -149,7 +155,7 @@ export default memo(function CurrencySearchModal({
|
||||
}
|
||||
break
|
||||
case CurrencyModalView.importList:
|
||||
minHeight = 40
|
||||
modalHeight = 40
|
||||
if (importList && listURL) {
|
||||
content = <ImportList list={importList} listURL={listURL} onDismiss={onDismiss} setModalView={setModalView} />
|
||||
}
|
||||
@@ -167,7 +173,7 @@ export default memo(function CurrencySearchModal({
|
||||
break
|
||||
}
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={80} minHeight={minHeight}>
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={modalHeight} minHeight={modalHeight}>
|
||||
{content}
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import Modal from '../Modal'
|
||||
import TokenSafety from '.'
|
||||
import TokenSafety, { TokenSafetyProps } from '.'
|
||||
|
||||
interface TokenSafetyModalProps {
|
||||
interface TokenSafetyModalProps extends TokenSafetyProps {
|
||||
isOpen: boolean
|
||||
tokenAddress: string | null
|
||||
secondTokenAddress?: string
|
||||
onContinue: () => void
|
||||
onCancel: () => void
|
||||
showCancel?: boolean
|
||||
}
|
||||
|
||||
export default function TokenSafetyModal({
|
||||
@@ -16,6 +11,7 @@ export default function TokenSafetyModal({
|
||||
secondTokenAddress,
|
||||
onContinue,
|
||||
onCancel,
|
||||
onBlocked,
|
||||
showCancel,
|
||||
}: TokenSafetyModalProps) {
|
||||
return (
|
||||
@@ -23,8 +19,9 @@ export default function TokenSafetyModal({
|
||||
<TokenSafety
|
||||
tokenAddress={tokenAddress}
|
||||
secondTokenAddress={secondTokenAddress}
|
||||
onCancel={onCancel}
|
||||
onContinue={onContinue}
|
||||
onBlocked={onBlocked}
|
||||
onCancel={onCancel}
|
||||
showCancel={showCancel}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@@ -73,11 +73,13 @@ const Buttons = ({
|
||||
warning,
|
||||
onContinue,
|
||||
onCancel,
|
||||
onBlocked,
|
||||
showCancel,
|
||||
}: {
|
||||
warning: Warning
|
||||
onContinue: () => void
|
||||
onCancel: () => void
|
||||
onBlocked?: () => void
|
||||
showCancel?: boolean
|
||||
}) => {
|
||||
return warning.canProceed ? (
|
||||
@@ -88,7 +90,7 @@ const Buttons = ({
|
||||
{showCancel && <StyledCancelButton onClick={onCancel}>Cancel</StyledCancelButton>}
|
||||
</>
|
||||
) : (
|
||||
<StyledCloseButton onClick={onCancel}>
|
||||
<StyledCloseButton onClick={onBlocked ?? onCancel}>
|
||||
<Trans>Close</Trans>
|
||||
</StyledCloseButton>
|
||||
)
|
||||
@@ -184,11 +186,12 @@ const StyledExternalLink = styled(ExternalLink)`
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
interface TokenSafetyProps {
|
||||
export interface TokenSafetyProps {
|
||||
tokenAddress: string | null
|
||||
secondTokenAddress?: string
|
||||
onContinue: () => void
|
||||
onCancel: () => void
|
||||
onBlocked?: () => void
|
||||
showCancel?: boolean
|
||||
}
|
||||
|
||||
@@ -197,6 +200,7 @@ export default function TokenSafety({
|
||||
secondTokenAddress,
|
||||
onContinue,
|
||||
onCancel,
|
||||
onBlocked,
|
||||
showCancel,
|
||||
}: TokenSafetyProps) {
|
||||
const logos = []
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { formatToDecimal } from 'components/AmplitudeAnalytics/utils'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import { useTokenBalance } from 'lib/hooks/useCurrencyBalance'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import NetworkBalance from './NetworkBalance'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const BalancesCard = styled.div`
|
||||
width: 100%;
|
||||
@@ -33,14 +33,9 @@ const ErrorText = styled.span`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
const NetworkBalancesSection = styled.div`
|
||||
height: fit-content;
|
||||
`
|
||||
|
||||
const TotalBalanceSection = styled.div`
|
||||
height: fit-content;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 20px;
|
||||
`
|
||||
const TotalBalance = styled.div`
|
||||
display: flex;
|
||||
@@ -54,58 +49,35 @@ const TotalBalanceItem = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
|
||||
export default function BalanceSummary({
|
||||
address,
|
||||
networkBalances,
|
||||
totalBalance,
|
||||
}: {
|
||||
address: string
|
||||
networkBalances: (JSX.Element | null)[] | null
|
||||
totalBalance: number
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const tokenSymbol = useToken(address)?.symbol
|
||||
const { loading, error, data } = useNetworkTokenBalances({ address })
|
||||
export default function BalanceSummary({ address }: { address: string }) {
|
||||
const token = useToken(address)
|
||||
const { loading, error } = useNetworkTokenBalances({ address })
|
||||
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
const balance = useTokenBalance(account, token ?? undefined)
|
||||
const balanceNumber = balance ? formatToDecimal(balance, Math.min(balance.currency.decimals, 6)) : undefined
|
||||
const balanceUsd = useStablecoinValue(balance)?.toFixed(2)
|
||||
const balanceUsdNumber = balanceUsd ? parseFloat(balanceUsd) : undefined
|
||||
|
||||
const { label: connectedLabel, logoUrl: connectedLogoUrl } = getChainInfoOrDefault(connectedChainId)
|
||||
const connectedFiatValue = 1
|
||||
const multipleBalances = true // for testing purposes
|
||||
|
||||
if (loading) return null
|
||||
if (loading || (!error && !balanceNumber && !balanceUsdNumber)) return null
|
||||
return (
|
||||
<BalancesCard>
|
||||
{error ? (
|
||||
<ErrorState>
|
||||
<AlertTriangle size={24} />
|
||||
<ErrorText>
|
||||
<Trans>There was an error loading your {tokenSymbol} balance</Trans>
|
||||
<Trans>There was an error loading your {token?.symbol} balance</Trans>
|
||||
</ErrorText>
|
||||
</ErrorState>
|
||||
) : multipleBalances ? (
|
||||
<>
|
||||
<TotalBalanceSection>
|
||||
Your balance across all networks
|
||||
<TotalBalance>
|
||||
<TotalBalanceItem>{`${totalBalance} ${tokenSymbol}`}</TotalBalanceItem>
|
||||
<TotalBalanceItem>$4,210.12</TotalBalanceItem>
|
||||
</TotalBalance>
|
||||
</TotalBalanceSection>
|
||||
<NetworkBalancesSection>Your balances by network</NetworkBalancesSection>
|
||||
{data && networkBalances}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Your balance on {connectedLabel}
|
||||
<NetworkBalance
|
||||
logoUrl={connectedLogoUrl}
|
||||
balance={'1'}
|
||||
tokenSymbol={tokenSymbol ?? 'XXX'}
|
||||
fiatValue={connectedFiatValue}
|
||||
label={connectedLabel}
|
||||
networkColor={theme.textPrimary}
|
||||
/>
|
||||
<TotalBalanceSection>
|
||||
Your balance
|
||||
<TotalBalance>
|
||||
<TotalBalanceItem>{`${balanceNumber} ${token?.symbol}`}</TotalBalanceItem>
|
||||
<TotalBalanceItem>{`$${balanceUsdNumber}`}</TotalBalanceItem>
|
||||
</TotalBalance>
|
||||
</TotalBalanceSection>
|
||||
</>
|
||||
)}
|
||||
</BalancesCard>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EventType } from '@visx/event/lib/types'
|
||||
import { GlyphCircle } from '@visx/glyph'
|
||||
import { Line } from '@visx/shape'
|
||||
import { filterTimeAtom } from 'components/Tokens/state'
|
||||
import { bisect, curveCardinal, NumberValue, scaleLinear } from 'd3'
|
||||
import { bisect, curveCardinal, NumberValue, scaleLinear, timeDay, timeHour, timeMinute, timeMonth } from 'd3'
|
||||
import { useTokenPriceQuery } from 'graphql/data/TokenPriceQuery'
|
||||
import { TimePeriod } from 'graphql/data/TopTokenQuery'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
@@ -117,35 +117,6 @@ const TimeButton = styled.button<{ active: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
function getTicks(startTimestamp: number, endTimestamp: number, numTicks = 5) {
|
||||
return Array.from(
|
||||
{ length: numTicks },
|
||||
(v, i) => endTimestamp - ((endTimestamp - startTimestamp) / (numTicks + 1)) * (i + 1)
|
||||
)
|
||||
}
|
||||
|
||||
function tickFormat(
|
||||
startTimestamp: number,
|
||||
endTimestamp: number,
|
||||
timePeriod: TimePeriod,
|
||||
locale: string
|
||||
): [TickFormatter<NumberValue>, (v: number) => string, number[]] {
|
||||
switch (timePeriod) {
|
||||
case TimePeriod.HOUR:
|
||||
return [hourFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.DAY:
|
||||
return [hourFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.WEEK:
|
||||
return [weekFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp, 6)]
|
||||
case TimePeriod.MONTH:
|
||||
return [monthDayFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.YEAR:
|
||||
return [monthTickFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.ALL:
|
||||
return [monthYearFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
}
|
||||
}
|
||||
|
||||
const margin = { top: 100, bottom: 48, crosshair: 72 }
|
||||
const timeOptionsHeight = 44
|
||||
const crosshairDateOverhang = 80
|
||||
@@ -181,10 +152,58 @@ export function PriceChart({ width, height, token }: PriceChartProps) {
|
||||
|
||||
// Defining scales
|
||||
// x scale
|
||||
const timeScale = scaleLinear().domain([startingPrice.timestamp, endingPrice.timestamp]).range([0, width])
|
||||
const timeScale = scaleLinear().domain([startingPrice.timestamp, endingPrice.timestamp]).range([0, width]).nice()
|
||||
// y scale
|
||||
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([graphInnerHeight, 0])
|
||||
|
||||
function tickFormat(
|
||||
startTimestamp: number,
|
||||
endTimestamp: number,
|
||||
timePeriod: TimePeriod,
|
||||
locale: string
|
||||
): [TickFormatter<NumberValue>, (v: number) => string, NumberValue[]] {
|
||||
const startDate = new Date(startingPrice.timestamp.valueOf() * 1000)
|
||||
const endDate = new Date(endingPrice.timestamp.valueOf() * 1000)
|
||||
switch (timePeriod) {
|
||||
case TimePeriod.HOUR:
|
||||
return [
|
||||
hourFormatter(locale),
|
||||
dayHourFormatter(locale),
|
||||
timeMinute.range(startDate, endDate, 10).map((x) => x.valueOf() / 1000),
|
||||
]
|
||||
case TimePeriod.DAY:
|
||||
return [
|
||||
hourFormatter(locale),
|
||||
dayHourFormatter(locale),
|
||||
timeHour.range(startDate, endDate, 4).map((x) => x.valueOf() / 1000),
|
||||
]
|
||||
case TimePeriod.WEEK:
|
||||
return [
|
||||
weekFormatter(locale),
|
||||
dayHourFormatter(locale),
|
||||
timeDay.range(startDate, endDate, 1).map((x) => x.valueOf() / 1000),
|
||||
]
|
||||
case TimePeriod.MONTH:
|
||||
return [
|
||||
monthDayFormatter(locale),
|
||||
dayHourFormatter(locale),
|
||||
timeDay.range(startDate, endDate, 7).map((x) => x.valueOf() / 1000),
|
||||
]
|
||||
case TimePeriod.YEAR:
|
||||
return [
|
||||
monthTickFormatter(locale),
|
||||
monthYearDayFormatter(locale),
|
||||
timeMonth.range(startDate, endDate, 2).map((x) => x.valueOf() / 1000),
|
||||
]
|
||||
case TimePeriod.ALL:
|
||||
return [
|
||||
monthYearFormatter(locale),
|
||||
monthYearDayFormatter(locale),
|
||||
timeMonth.range(startDate, endDate, 3).map((x) => x.valueOf() / 1000),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const handleHover = useCallback(
|
||||
(event: Element | EventType) => {
|
||||
const { x } = localPoint(event) || { x: 0 }
|
||||
|
||||
@@ -4,24 +4,22 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import PriceChart from 'components/Tokens/TokenDetails/PriceChart'
|
||||
import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
import { checkWarning, WARNING_LEVEL } from 'constants/tokenSafety'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { chainIdToChainName, useTokenDetailQuery } from 'graphql/data/TokenDetailQuery'
|
||||
import { useCurrency, useIsUserAddedToken, useToken } from 'hooks/Tokens'
|
||||
import { useCurrency, useToken } from 'hooks/Tokens'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { darken } from 'polished'
|
||||
import { Suspense, useCallback } from 'react'
|
||||
import { Suspense } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { ArrowLeft, Heart } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ClickableStyle, CopyContractAddress } from 'theme'
|
||||
import { CopyContractAddress } from 'theme'
|
||||
import { formatDollarAmount } from 'utils/formatDollarAmt'
|
||||
|
||||
import { favoritesAtom, filterNetworkAtom, useToggleFavorite } from '../state'
|
||||
import { ClickFavorited } from '../TokenTable/TokenRow'
|
||||
import { filterNetworkAtom, useIsFavorited, useToggleFavorite } from '../state'
|
||||
import { ClickFavorited, FavoriteIcon } from '../TokenTable/TokenRow'
|
||||
import LoadingTokenDetail from './LoadingTokenDetail'
|
||||
import Resource from './Resource'
|
||||
import ShareButton from './ShareButton'
|
||||
@@ -81,13 +79,7 @@ const NetworkBadge = styled.div<{ networkColor?: string; backgroundColor?: strin
|
||||
color: ${({ theme, networkColor }) => networkColor ?? theme.textPrimary};
|
||||
background-color: ${({ theme, backgroundColor }) => backgroundColor ?? theme.backgroundSurface};
|
||||
`
|
||||
const FavoriteIcon = styled(Heart)<{ isFavorited: boolean }>`
|
||||
${ClickableStyle}
|
||||
height: 22px;
|
||||
width: 24px;
|
||||
color: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : theme.textSecondary)};
|
||||
fill: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : 'transparent')};
|
||||
`
|
||||
|
||||
const NoInfoAvailable = styled.span`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-weight: 400;
|
||||
@@ -181,21 +173,9 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const token = useToken(address)
|
||||
let currency = useCurrency(address)
|
||||
const favoriteTokens = useAtomValue<string[]>(favoritesAtom)
|
||||
const isFavorited = favoriteTokens.includes(address)
|
||||
const isFavorited = useIsFavorited(address)
|
||||
const toggleFavorite = useToggleFavorite(address)
|
||||
const warning = checkWarning(address)
|
||||
const navigate = useNavigate()
|
||||
const isUserAddedToken = useIsUserAddedToken(token)
|
||||
const [warningModalOpen, setWarningModalOpen] = useState(!!warning && !isUserAddedToken)
|
||||
|
||||
const handleDismissWarning = useCallback(() => {
|
||||
setWarningModalOpen(false)
|
||||
}, [setWarningModalOpen])
|
||||
const handleCancel = useCallback(() => {
|
||||
setWarningModalOpen(false)
|
||||
warning && warning.level === WARNING_LEVEL.BLOCKED && navigate(-1)
|
||||
}, [setWarningModalOpen, navigate, warning])
|
||||
const chainInfo = getChainInfo(token?.chainId)
|
||||
const networkLabel = chainInfo?.label
|
||||
const networkBadgebackgroundColor = chainInfo?.backgroundColor
|
||||
@@ -293,12 +273,6 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
|
||||
</ContractAddress>
|
||||
</Contract>
|
||||
</ContractAddressSection>
|
||||
<TokenSafetyModal
|
||||
isOpen={warningModalOpen}
|
||||
tokenAddress={address}
|
||||
onCancel={handleCancel}
|
||||
onContinue={handleDismissWarning}
|
||||
/>
|
||||
</TopArea>
|
||||
</Suspense>
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ const StyledFavoriteButton = styled.button<{ active: boolean }>`
|
||||
padding: 0px 16px;
|
||||
border-radius: 12px;
|
||||
background-color: ${({ theme, active }) => (active ? theme.accentActiveSoft : theme.backgroundInteractive)};
|
||||
border: none;
|
||||
border: ${({ active, theme }) => (active ? `1px solid ${theme.accentActive}` : 'none')};
|
||||
color: ${({ theme, active }) => (active ? theme.accentActive : theme.textPrimary)};
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
@@ -24,6 +24,7 @@ const StyledFavoriteButton = styled.button<{ active: boolean }>`
|
||||
|
||||
:hover {
|
||||
background-color: ${({ theme, active }) => !active && theme.backgroundModule};
|
||||
opacity: ${({ active }) => (active ? '60%' : '100%')};
|
||||
}
|
||||
`
|
||||
const FavoriteText = styled.span`
|
||||
@@ -38,11 +39,7 @@ export default function FavoriteButton() {
|
||||
return (
|
||||
<StyledFavoriteButton onClick={() => setShowFavorites(!showFavorites)} active={showFavorites}>
|
||||
<FavoriteButtonContent>
|
||||
<Heart
|
||||
size={17}
|
||||
color={showFavorites ? theme.accentActive : theme.textPrimary}
|
||||
fill={showFavorites ? theme.accentActive : 'transparent'}
|
||||
/>
|
||||
<Heart size={17} color={showFavorites ? theme.accentActive : theme.textPrimary} />
|
||||
<FavoriteText>
|
||||
<Trans>Favorites</Trans>
|
||||
</FavoriteText>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import searchIcon from 'assets/svg/search.svg'
|
||||
import xIcon from 'assets/svg/x.svg'
|
||||
import { useAtom } from 'jotai'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { useEffect, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
|
||||
@@ -56,7 +58,14 @@ const SearchInput = styled.input`
|
||||
`
|
||||
|
||||
export default function SearchBar() {
|
||||
const [filterString, setFilterString] = useAtom(filterStringAtom)
|
||||
const [localFilterString, setLocalFilterString] = useState('')
|
||||
const setFilterString = useUpdateAtom(filterStringAtom)
|
||||
const debouncedLocalFilterString = useDebounce(localFilterString, 300)
|
||||
|
||||
useEffect(() => {
|
||||
setFilterString(debouncedLocalFilterString)
|
||||
}, [debouncedLocalFilterString, setFilterString])
|
||||
|
||||
return (
|
||||
<SearchBarContainer>
|
||||
<Trans
|
||||
@@ -66,8 +75,8 @@ export default function SearchBar() {
|
||||
placeholder={`${translation}`}
|
||||
id="searchBar"
|
||||
autoComplete="off"
|
||||
value={filterString}
|
||||
onChange={({ target: { value } }) => setFilterString(value)}
|
||||
value={localFilterString}
|
||||
onChange={({ target: { value } }) => setLocalFilterString(value)}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -7,12 +7,12 @@ import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { TimePeriod, TokenData } from 'graphql/data/TopTokenQuery'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { ReactNode } from 'react'
|
||||
import { ArrowDown, ArrowUp, Heart } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
import { ClickableStyle } from 'theme'
|
||||
import { formatDollarAmount } from 'utils/formatDollarAmt'
|
||||
|
||||
import {
|
||||
@@ -23,12 +23,12 @@ import {
|
||||
} from '../constants'
|
||||
import { LoadingBubble } from '../loading'
|
||||
import {
|
||||
favoritesAtom,
|
||||
filterNetworkAtom,
|
||||
filterStringAtom,
|
||||
filterTimeAtom,
|
||||
sortCategoryAtom,
|
||||
sortDirectionAtom,
|
||||
useIsFavorited,
|
||||
useSetSortCategory,
|
||||
useToggleFavorite,
|
||||
} from '../state'
|
||||
@@ -109,6 +109,14 @@ export const ClickFavorited = styled.span`
|
||||
}
|
||||
`
|
||||
|
||||
export const FavoriteIcon = styled(Heart)<{ isFavorited: boolean }>`
|
||||
${ClickableStyle}
|
||||
height: 22px;
|
||||
width: 24px;
|
||||
color: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : theme.textSecondary)};
|
||||
fill: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : 'transparent')};
|
||||
`
|
||||
|
||||
const ClickableContent = styled.div`
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
@@ -461,9 +469,7 @@ export default function LoadedRow({
|
||||
const currency = useCurrency(tokenAddress)
|
||||
const tokenName = tokenData.name
|
||||
const tokenSymbol = tokenData.symbol
|
||||
const theme = useTheme()
|
||||
const [favoriteTokens] = useAtom(favoritesAtom)
|
||||
const isFavorited = favoriteTokens.includes(tokenAddress)
|
||||
const isFavorited = useIsFavorited(tokenAddress)
|
||||
const toggleFavorite = useToggleFavorite(tokenAddress)
|
||||
const filterString = useAtomValue(filterStringAtom)
|
||||
const filterNetwork = useAtomValue(filterNetworkAtom)
|
||||
@@ -482,7 +488,6 @@ export default function LoadedRow({
|
||||
search_token_address_input: filterString,
|
||||
}
|
||||
|
||||
const heartColor = isFavorited ? theme.accentActive : undefined
|
||||
// TODO: currency logo sizing mobile (32px) vs. desktop (24px)
|
||||
return (
|
||||
<StyledLink
|
||||
@@ -498,7 +503,7 @@ export default function LoadedRow({
|
||||
toggleFavorite()
|
||||
}}
|
||||
>
|
||||
<Heart size={18} color={heartColor} fill={heartColor} />
|
||||
<FavoriteIcon isFavorited={isFavorited} />
|
||||
</ClickFavorited>
|
||||
}
|
||||
listNumber={tokenListIndex + 1}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { TimePeriod } from 'graphql/data/TopTokenQuery'
|
||||
import { atom, useAtom } from 'jotai'
|
||||
import { atomWithReset, atomWithStorage } from 'jotai/utils'
|
||||
import { useCallback } from 'react'
|
||||
import { atomWithReset, atomWithStorage, useAtomValue } from 'jotai/utils'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import { Category, SortDirection } from './types'
|
||||
|
||||
@@ -20,12 +20,12 @@ export function useToggleFavorite(tokenAddress: string) {
|
||||
|
||||
return useCallback(() => {
|
||||
let updatedFavoriteTokens
|
||||
if (favoriteTokens.includes(tokenAddress)) {
|
||||
if (favoriteTokens.includes(tokenAddress.toLocaleLowerCase())) {
|
||||
updatedFavoriteTokens = favoriteTokens.filter((address: string) => {
|
||||
return address !== tokenAddress
|
||||
return address !== tokenAddress.toLocaleLowerCase()
|
||||
})
|
||||
} else {
|
||||
updatedFavoriteTokens = [...favoriteTokens, tokenAddress]
|
||||
updatedFavoriteTokens = [...favoriteTokens, tokenAddress.toLocaleLowerCase()]
|
||||
}
|
||||
updateFavoriteTokens(updatedFavoriteTokens)
|
||||
}, [favoriteTokens, tokenAddress, updateFavoriteTokens])
|
||||
@@ -47,3 +47,9 @@ export function useSetSortCategory(category: Category) {
|
||||
}
|
||||
}, [category, sortCategory, setSortCategory, sortDirection, setDirectionCategory])
|
||||
}
|
||||
|
||||
export function useIsFavorited(tokenAddress: string) {
|
||||
const favoritedTokens = useAtomValue<string[]>(favoritesAtom)
|
||||
|
||||
return useMemo(() => favoritedTokens.includes(tokenAddress.toLocaleLowerCase()), [favoritedTokens, tokenAddress])
|
||||
}
|
||||
|
||||
@@ -132,7 +132,12 @@ const AuthenticatedHeader = () => {
|
||||
<IconContainer>
|
||||
<IconButton onClick={copy} Icon={Copy} text={isCopied ? <Trans>Copied!</Trans> : <Trans>Copy</Trans>} />
|
||||
<IconButton href={`${explorer}address/${account}`} Icon={ExternalLink} text={<Trans>Explore</Trans>} />
|
||||
<IconButton onClick={disconnect} Icon={Power} text={<Trans>Disconnect</Trans>} />
|
||||
<IconButton
|
||||
dataTestId="wallet-disconnect"
|
||||
onClick={disconnect}
|
||||
Icon={Power}
|
||||
text={<Trans>Disconnect</Trans>}
|
||||
/>
|
||||
</IconContainer>
|
||||
</HeaderWrapper>
|
||||
<Column>
|
||||
|
||||
@@ -114,11 +114,13 @@ const WalletDropdown = ({ setMenu }: { setMenu: (state: MenuState) => void }) =>
|
||||
{isAuthenticated ? (
|
||||
<AuthenticatedHeader />
|
||||
) : (
|
||||
<ConnectButton onClick={toggleWalletModal}>Connect wallet</ConnectButton>
|
||||
<ConnectButton data-testid="wallet-connect-wallet" onClick={toggleWalletModal}>
|
||||
Connect wallet
|
||||
</ConnectButton>
|
||||
)}
|
||||
<Divider />
|
||||
{isAuthenticated && (
|
||||
<ToggleMenuItem onClick={() => setMenu(MenuState.TRANSACTIONS)}>
|
||||
<ToggleMenuItem data-testid="wallet-transactions" onClick={() => setMenu(MenuState.TRANSACTIONS)}>
|
||||
<DefaultText>
|
||||
<Trans>Transactions</Trans>{' '}
|
||||
{pendingTransactions.length > 0 && (
|
||||
@@ -132,7 +134,7 @@ const WalletDropdown = ({ setMenu }: { setMenu: (state: MenuState) => void }) =>
|
||||
</IconWrap>
|
||||
</ToggleMenuItem>
|
||||
)}
|
||||
<ToggleMenuItem onClick={() => setMenu(MenuState.LANGUAGE)}>
|
||||
<ToggleMenuItem data-testid="wallet-select-language" onClick={() => setMenu(MenuState.LANGUAGE)}>
|
||||
<DefaultText>
|
||||
<Trans>Language</Trans>
|
||||
</DefaultText>
|
||||
@@ -145,7 +147,7 @@ const WalletDropdown = ({ setMenu }: { setMenu: (state: MenuState) => void }) =>
|
||||
</IconWrap>
|
||||
</FlexContainer>
|
||||
</ToggleMenuItem>
|
||||
<ToggleMenuItem onClick={toggleDarkMode}>
|
||||
<ToggleMenuItem data-testid="wallet-select-theme" onClick={toggleDarkMode}>
|
||||
<DefaultText>{darkMode ? <Trans> Light theme</Trans> : <Trans>Dark theme</Trans>}</DefaultText>
|
||||
<IconWrap>{darkMode ? <Sun size={16} /> : <Moon size={16} />}</IconWrap>
|
||||
</ToggleMenuItem>
|
||||
|
||||
@@ -64,18 +64,19 @@ interface IconButtonProps {
|
||||
Icon: Icon
|
||||
onClick?: () => void
|
||||
href?: string
|
||||
dataTestId?: string
|
||||
}
|
||||
|
||||
const IconButton = ({ Icon, onClick, text, href }: IconButtonProps) => {
|
||||
const IconButton = ({ Icon, onClick, text, href, dataTestId }: IconButtonProps) => {
|
||||
return href ? (
|
||||
<IconBlockLink href={href} target="_blank">
|
||||
<IconBlockLink data-testId={dataTestId} href={href} target="_blank">
|
||||
<IconWrapper>
|
||||
<Icon strokeWidth={1.5} size={16} />
|
||||
<IconHoverText>{text}</IconHoverText>
|
||||
</IconWrapper>
|
||||
</IconBlockLink>
|
||||
) : (
|
||||
<IconBlockButton onClick={onClick}>
|
||||
<IconBlockButton data-testId={dataTestId} onClick={onClick}>
|
||||
<IconWrapper>
|
||||
<Icon strokeWidth={1.5} size={16} />
|
||||
<IconHoverText>{text}</IconHoverText>
|
||||
|
||||
@@ -45,7 +45,7 @@ function LanguageMenuItem({ locale, isActive }: { locale: SupportedLocale; isAct
|
||||
|
||||
return (
|
||||
<InternalLinkMenuItem onClick={onClick} to={to}>
|
||||
<Text fontSize={16} fontWeight={400} lineHeight="24px">
|
||||
<Text data-testid="wallet-language-item" fontSize={16} fontWeight={400} lineHeight="24px">
|
||||
{LOCALE_LABEL[locale]}
|
||||
</Text>
|
||||
{isActive && <Check color={theme.accentActive} opacity={1} size={20} />}
|
||||
|
||||
@@ -101,8 +101,8 @@ export const SlideOutMenu = ({
|
||||
<Menu>
|
||||
<BackSection>
|
||||
<BackSectionContainer>
|
||||
<StyledChevron onClick={onClose} size={24} />
|
||||
<Header>{title}</Header>
|
||||
<StyledChevron data-testid="wallet-back" onClick={onClose} size={24} />
|
||||
<Header data-testid="wallet-header">{title}</Header>
|
||||
{onClear && <ClearAll onClick={onClear}>Clear All</ClearAll>}
|
||||
</BackSectionContainer>
|
||||
</BackSection>
|
||||
|
||||
@@ -158,7 +158,7 @@ export const TransactionHistoryMenu = ({ onClose }: { onClose: () => void }) =>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<EmptyTransaction>
|
||||
<EmptyTransaction data-testid="wallet-empty-transaction-text">
|
||||
<Trans>Your transactions will appear here</Trans>
|
||||
</EmptyTransaction>
|
||||
)}
|
||||
|
||||
@@ -37,8 +37,8 @@ export enum MenuState {
|
||||
}
|
||||
|
||||
const WalletDropdownWrapper = styled.div`
|
||||
position: absolute;
|
||||
top: 65px;
|
||||
position: fixed;
|
||||
top: 72px;
|
||||
right: 20px;
|
||||
z-index: ${Z_INDEX.dropdown};
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@ export default function WalletModal({
|
||||
|
||||
return (
|
||||
<UpperSection>
|
||||
<CloseIcon onClick={toggleWalletModal}>
|
||||
<CloseIcon data-testid="wallet-modal-close" onClick={toggleWalletModal}>
|
||||
<CloseColor />
|
||||
</CloseIcon>
|
||||
{headerRow}
|
||||
@@ -363,7 +363,9 @@ export default function WalletModal({
|
||||
maxHeight={90}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
<Wrapper redesignFlag={redesignFlagEnabled}>{getModalContent()}</Wrapper>
|
||||
<Wrapper data-testid="wallet-modal" redesignFlag={redesignFlagEnabled}>
|
||||
{getModalContent()}
|
||||
</Wrapper>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ function Web3StatusInner() {
|
||||
} else if (account) {
|
||||
return (
|
||||
<Web3StatusConnected data-testid="web3-status-connected" onClick={toggleWallet} pending={hasPendingTransactions}>
|
||||
{navbarFlagEnabled && !hasPendingTransactions && <StatusIcon connectionType={connectionType} />}
|
||||
{navbarFlagEnabled && !hasPendingTransactions && <StatusIcon size={24} connectionType={connectionType} />}
|
||||
{hasPendingTransactions ? (
|
||||
<RowBetween>
|
||||
<Text>
|
||||
@@ -252,14 +252,22 @@ function Web3StatusInner() {
|
||||
>
|
||||
{navbarFlagEnabled ? (
|
||||
<Web3StatusConnectNavbar faded={!account}>
|
||||
<StyledConnect onClick={toggleWalletModal}>
|
||||
<StyledConnect data-testid="navbar-connect-wallet" onClick={toggleWalletModal}>
|
||||
<Trans>Connect</Trans>
|
||||
</StyledConnect>
|
||||
<VerticalDivider />
|
||||
{walletIsOpen ? (
|
||||
<StyledChevronUp customColor={theme.accentAction} onClick={toggleWalletDropdown} />
|
||||
<StyledChevronUp
|
||||
data-testid="navbar-wallet-dropdown"
|
||||
customColor={theme.accentAction}
|
||||
onClick={toggleWalletDropdown}
|
||||
/>
|
||||
) : (
|
||||
<StyledChevronDown customColor={theme.accentAction} onClick={toggleWalletDropdown} />
|
||||
<StyledChevronDown
|
||||
data-testid="navbar-wallet-dropdown"
|
||||
customColor={theme.accentAction}
|
||||
onClick={toggleWalletDropdown}
|
||||
/>
|
||||
)}
|
||||
</Web3StatusConnectNavbar>
|
||||
) : (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Currency, SwapWidget } from '@uniswap/widgets'
|
||||
import { Currency, OnReviewSwapClick, SwapWidget } from '@uniswap/widgets'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { RPC_URLS } from 'constants/networks'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
@@ -16,9 +16,10 @@ const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/'
|
||||
|
||||
export interface WidgetProps {
|
||||
defaultToken?: Currency
|
||||
onReviewSwapClick?: OnReviewSwapClick
|
||||
}
|
||||
|
||||
export default function Widget({ defaultToken }: WidgetProps) {
|
||||
export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) {
|
||||
const locale = useActiveLocale()
|
||||
const darkMode = useIsDarkMode()
|
||||
const theme = useMemo(() => (darkMode ? DARK_THEME : LIGHT_THEME), [darkMode])
|
||||
@@ -38,6 +39,7 @@ export default function Widget({ defaultToken }: WidgetProps) {
|
||||
width={WIDGET_WIDTH}
|
||||
locale={locale}
|
||||
theme={theme}
|
||||
onReviewSwapClick={onReviewSwapClick}
|
||||
// defaultChainId is excluded - it is always inferred from the passed provider
|
||||
provider={provider}
|
||||
{...inputs}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { WalletConnect } from '@web3-react/walletconnect'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
|
||||
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
|
||||
import { RPC_URLS } from '../constants/networks'
|
||||
import { RPC_PROVIDERS, RPC_URLS } from '../constants/networks'
|
||||
|
||||
export enum ConnectionType {
|
||||
INJECTED = 'INJECTED',
|
||||
@@ -29,7 +29,7 @@ function onError(error: Error) {
|
||||
}
|
||||
|
||||
const [web3Network, web3NetworkHooks] = initializeConnector<Network>(
|
||||
(actions) => new Network({ actions, urlMap: RPC_URLS, defaultChainId: 1 })
|
||||
(actions) => new Network({ actions, urlMap: RPC_PROVIDERS, defaultChainId: 1 })
|
||||
)
|
||||
export const networkConnection: Connection = {
|
||||
connector: web3Network,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { StaticJsonRpcProvider } from '@ethersproject/providers'
|
||||
|
||||
import { SupportedChainId } from './chains'
|
||||
|
||||
@@ -7,8 +7,6 @@ if (typeof INFURA_KEY === 'undefined') {
|
||||
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
|
||||
}
|
||||
|
||||
export const MAINNET_PROVIDER = new JsonRpcProvider(`https://mainnet.infura.io/v3/${INFURA_KEY}`)
|
||||
|
||||
/**
|
||||
* These are the network URLs used by the interface when there is not another available source of chain data
|
||||
*/
|
||||
@@ -27,3 +25,19 @@ export const RPC_URLS: { [key in SupportedChainId]: string } = {
|
||||
[SupportedChainId.CELO]: `https://forno.celo.org`,
|
||||
[SupportedChainId.CELO_ALFAJORES]: `https://alfajores-forno.celo-testnet.org`,
|
||||
}
|
||||
|
||||
export const RPC_PROVIDERS: { [key in SupportedChainId]: StaticJsonRpcProvider } = {
|
||||
[SupportedChainId.MAINNET]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.MAINNET]),
|
||||
[SupportedChainId.RINKEBY]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.RINKEBY]),
|
||||
[SupportedChainId.ROPSTEN]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.ROPSTEN]),
|
||||
[SupportedChainId.GOERLI]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.GOERLI]),
|
||||
[SupportedChainId.KOVAN]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.KOVAN]),
|
||||
[SupportedChainId.OPTIMISM]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.OPTIMISM]),
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.OPTIMISTIC_KOVAN]),
|
||||
[SupportedChainId.ARBITRUM_ONE]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.ARBITRUM_ONE]),
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.ARBITRUM_RINKEBY]),
|
||||
[SupportedChainId.POLYGON]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.POLYGON]),
|
||||
[SupportedChainId.POLYGON_MUMBAI]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.POLYGON_MUMBAI]),
|
||||
[SupportedChainId.CELO]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.CELO]),
|
||||
[SupportedChainId.CELO_ALFAJORES]: new StaticJsonRpcProvider(RPC_URLS[SupportedChainId.CELO_ALFAJORES]),
|
||||
}
|
||||
|
||||
8
src/featureFlags/flags/featureFlags.ts
Normal file
8
src/featureFlags/flags/featureFlags.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export enum FeatureFlag {
|
||||
navBar = 'navBar',
|
||||
nft = 'nfts',
|
||||
redesign = 'redesign',
|
||||
tokens = 'tokens',
|
||||
tokensNetworkFilter = 'tokensNetworkFilter',
|
||||
tokenSafety = 'tokenSafety',
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useAtom } from 'jotai'
|
||||
import { atomWithStorage, useAtomValue } from 'jotai/utils'
|
||||
import { createContext, ReactNode, useCallback, useContext } from 'react'
|
||||
export { FeatureFlag } from './flags/featureFlags'
|
||||
|
||||
interface FeatureFlagsContextType {
|
||||
isLoaded: boolean
|
||||
@@ -53,16 +54,6 @@ export enum BaseVariant {
|
||||
Enabled = 'enabled',
|
||||
}
|
||||
|
||||
export enum FeatureFlag {
|
||||
navBar = 'navBar',
|
||||
wallet = 'wallet',
|
||||
nft = 'nfts',
|
||||
redesign = 'redesign',
|
||||
tokens = 'tokens',
|
||||
tokensNetworkFilter = 'tokensNetworkFilter',
|
||||
tokenSafety = 'tokenSafety',
|
||||
}
|
||||
|
||||
export function useBaseFlag(flag: string): BaseVariant {
|
||||
switch (useFeatureFlagsContext().flags[flag]) {
|
||||
case 'enabled':
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { DAI, USDC_MAINNET } from 'constants/tokens'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
import { TradeState } from 'state/routing/types'
|
||||
import { useClientSideRouter } from 'state/user/hooks'
|
||||
|
||||
import { useRoutingAPITrade } from '../state/routing/useRoutingAPITrade'
|
||||
import useAutoRouterSupported from './useAutoRouterSupported'
|
||||
@@ -25,6 +27,7 @@ const mockUseAutoRouterSupported = useAutoRouterSupported as jest.MockedFunction
|
||||
const mockUseIsWindowVisible = useIsWindowVisible as jest.MockedFunction<typeof useIsWindowVisible>
|
||||
|
||||
const mockUseRoutingAPITrade = useRoutingAPITrade as jest.MockedFunction<typeof useRoutingAPITrade>
|
||||
const mockUseClientSideRouter = useClientSideRouter as jest.MockedFunction<typeof useClientSideRouter>
|
||||
const mockUseClientSideV3Trade = useClientSideV3Trade as jest.MockedFunction<typeof useClientSideV3Trade>
|
||||
|
||||
// helpers to set mock expectations
|
||||
@@ -42,6 +45,7 @@ beforeEach(() => {
|
||||
|
||||
mockUseIsWindowVisible.mockReturnValue(true)
|
||||
mockUseAutoRouterSupported.mockReturnValue(true)
|
||||
mockUseClientSideRouter.mockReturnValue([true, () => undefined])
|
||||
})
|
||||
|
||||
describe('#useBestV3Trade ExactIn', () => {
|
||||
@@ -52,7 +56,7 @@ describe('#useBestV3Trade ExactIn', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI)
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI, RouterPreference.CLIENT)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, USDCAmount, DAI)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
@@ -64,7 +68,7 @@ describe('#useBestV3Trade ExactIn', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI)
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI, RouterPreference.CLIENT)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, USDCAmount, DAI)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
@@ -128,7 +132,12 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC_MAINNET)
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(
|
||||
TradeType.EXACT_OUTPUT,
|
||||
undefined,
|
||||
USDC_MAINNET,
|
||||
RouterPreference.CLIENT
|
||||
)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
@@ -140,7 +149,12 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC_MAINNET)
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(
|
||||
TradeType.EXACT_OUTPUT,
|
||||
undefined,
|
||||
USDC_MAINNET,
|
||||
RouterPreference.CLIENT
|
||||
)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { useMemo } from 'react'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
import { InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
|
||||
import { useClientSideRouter } from 'state/user/hooks'
|
||||
|
||||
import useAutoRouterSupported from './useAutoRouterSupported'
|
||||
import { useClientSideV3Trade } from './useClientSideV3Trade'
|
||||
@@ -30,10 +32,12 @@ export function useBestTrade(
|
||||
200
|
||||
)
|
||||
|
||||
const [clientSideRouter] = useClientSideRouter()
|
||||
const routingAPITrade = useRoutingAPITrade(
|
||||
tradeType,
|
||||
autoRouterSupported && isWindowVisible ? debouncedAmount : undefined,
|
||||
debouncedOtherCurrency
|
||||
debouncedOtherCurrency,
|
||||
clientSideRouter ? RouterPreference.CLIENT : RouterPreference.API
|
||||
)
|
||||
|
||||
const isLoading = routingAPITrade.state === TradeState.LOADING
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { nanoid } from '@reduxjs/toolkit'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { MAINNET_PROVIDER } from 'constants/networks'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { RPC_PROVIDERS } from 'constants/networks'
|
||||
import getTokenList from 'lib/hooks/useTokenList/fetchTokenList'
|
||||
import resolveENSContentHash from 'lib/utils/resolveENSContentHash'
|
||||
import { useCallback } from 'react'
|
||||
@@ -16,7 +17,9 @@ export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean
|
||||
async (listUrl: string, sendDispatch = true) => {
|
||||
const requestId = nanoid()
|
||||
sendDispatch && dispatch(fetchTokenList.pending({ requestId, url: listUrl }))
|
||||
return getTokenList(listUrl, (ensName: string) => resolveENSContentHash(ensName, MAINNET_PROVIDER))
|
||||
return getTokenList(listUrl, (ensName: string) =>
|
||||
resolveENSContentHash(ensName, RPC_PROVIDERS[SupportedChainId.MAINNET])
|
||||
)
|
||||
.then((tokenList) => {
|
||||
sendDispatch && dispatch(fetchTokenList.fulfilled({ url: listUrl, tokenList, requestId }))
|
||||
return tokenList
|
||||
|
||||
@@ -2,7 +2,8 @@ import { Currency, CurrencyAmount, Price, Token, TradeType } from '@uniswap/sdk-
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { RouterPreference, useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
|
||||
|
||||
import { SupportedChainId } from '../constants/chains'
|
||||
import { CUSD_CELO, DAI_OPTIMISM, USDC_ARBITRUM, USDC_MAINNET, USDC_POLYGON } from '../constants/tokens'
|
||||
@@ -27,7 +28,7 @@ export default function useStablecoinPrice(currency?: Currency): Price<Currency,
|
||||
const amountOut = chainId ? STABLECOIN_AMOUNT_OUT[chainId] : undefined
|
||||
const stablecoin = amountOut?.currency
|
||||
|
||||
const { trade } = useRoutingAPITrade(TradeType.EXACT_OUTPUT, amountOut, currency, RouterPreference.CLIENT)
|
||||
const { trade } = useRoutingAPITrade(TradeType.EXACT_OUTPUT, amountOut, currency, RouterPreference.PRICE)
|
||||
const price = useMemo(() => {
|
||||
if (!currency || !stablecoin) {
|
||||
return undefined
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { CHAIN_IDS_TO_NAMES, SupportedChainId } from 'constants/chains'
|
||||
import { CHAIN_IDS_TO_NAMES } from 'constants/chains'
|
||||
import { ParsedQs } from 'qs'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { replaceURLParam } from 'utils/routes'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
|
||||
import useParsedQueryString from './useParsedQueryString'
|
||||
import usePrevious from './usePrevious'
|
||||
@@ -22,15 +21,8 @@ function getParsedChainId(parsedQs?: ParsedQs) {
|
||||
return getChainIdFromName(chain)
|
||||
}
|
||||
|
||||
function getChainNameFromId(id: string | number) {
|
||||
// casting here may not be right but fine to return undefined if it's not a supported chain ID
|
||||
return CHAIN_IDS_TO_NAMES[id as SupportedChainId] || ''
|
||||
}
|
||||
|
||||
export default function useSyncChainQuery() {
|
||||
const { chainId, isActive } = useWeb3React()
|
||||
const navigate = useNavigate()
|
||||
const { search } = useLocation()
|
||||
const parsedQs = useParsedQueryString()
|
||||
|
||||
const urlChainId = getParsedChainId(parsedQs)
|
||||
@@ -46,35 +38,16 @@ export default function useSyncChainQuery() {
|
||||
}
|
||||
}, [chainId, previousChainId])
|
||||
|
||||
const replaceURLChainParam = useCallback(() => {
|
||||
if (chainId) {
|
||||
navigate({ search: replaceURLParam(search, 'chain', getChainNameFromId(chainId)) }, { replace: true })
|
||||
}
|
||||
}, [chainId, search, navigate])
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
|
||||
const chainQueryUnpopulated = !urlChainId && chainId
|
||||
const chainChanged = chainId !== previousChainId
|
||||
const chainQueryStale = urlChainId !== chainId
|
||||
const chainQueryManuallyUpdated = urlChainId && urlChainId !== previousUrlChainId && isActive
|
||||
|
||||
return useEffect(() => {
|
||||
if (chainQueryUnpopulated) {
|
||||
// If there is no chain query param, set it to the current chain
|
||||
replaceURLChainParam()
|
||||
} else if (chainChanged && chainQueryStale) {
|
||||
// If the chain changed but the query param is stale, update to the current chain
|
||||
replaceURLChainParam()
|
||||
} else if (chainQueryManuallyUpdated) {
|
||||
if (chainQueryManuallyUpdated) {
|
||||
// If the query param changed, and the chain didn't change, then activate the new chain
|
||||
selectChain(urlChainId)
|
||||
searchParams.delete('chain')
|
||||
setSearchParams(searchParams)
|
||||
}
|
||||
}, [
|
||||
chainQueryUnpopulated,
|
||||
chainChanged,
|
||||
chainQueryStale,
|
||||
chainQueryManuallyUpdated,
|
||||
urlChainId,
|
||||
selectChain,
|
||||
replaceURLChainParam,
|
||||
])
|
||||
}, [chainQueryManuallyUpdated, urlChainId, selectChain, searchParams, setSearchParams])
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BigintIsh, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
|
||||
// This file is lazy-loaded, so the import of smart-order-router is intentional.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { AlphaRouter, AlphaRouterConfig, AlphaRouterParams, ChainId } from '@uniswap/smart-order-router'
|
||||
import { AlphaRouter, AlphaRouterConfig, ChainId } from '@uniswap/smart-order-router'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import JSBI from 'jsbi'
|
||||
import { GetQuoteResult } from 'state/routing/types'
|
||||
@@ -29,11 +29,9 @@ async function getQuote(
|
||||
tokenOut: { address: string; chainId: number; decimals: number; symbol?: string }
|
||||
amount: BigintIsh
|
||||
},
|
||||
routerParams: AlphaRouterParams,
|
||||
routerConfig: Partial<AlphaRouterConfig>
|
||||
router: AlphaRouter,
|
||||
config: Partial<AlphaRouterConfig>
|
||||
): Promise<{ data: GetQuoteResult; error?: unknown }> {
|
||||
const router = new AlphaRouter(routerParams)
|
||||
|
||||
const currencyIn = new Token(tokenIn.chainId, tokenIn.address, tokenIn.decimals, tokenIn.symbol)
|
||||
const currencyOut = new Token(tokenOut.chainId, tokenOut.address, tokenOut.decimals, tokenOut.symbol)
|
||||
|
||||
@@ -46,7 +44,7 @@ async function getQuote(
|
||||
quoteCurrency,
|
||||
type === 'exactIn' ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
|
||||
/*swapConfig=*/ undefined,
|
||||
routerConfig
|
||||
config
|
||||
)
|
||||
|
||||
if (!swapRoute) throw new Error('Failed to generate client side quote')
|
||||
@@ -80,8 +78,8 @@ export async function getClientSideQuote(
|
||||
amount,
|
||||
type,
|
||||
}: QuoteArguments,
|
||||
routerParams: AlphaRouterParams,
|
||||
routerConfig: Partial<AlphaRouterConfig>
|
||||
router: AlphaRouter,
|
||||
config: Partial<AlphaRouterConfig>
|
||||
) {
|
||||
return getQuote(
|
||||
{
|
||||
@@ -100,7 +98,7 @@ export async function getClientSideQuote(
|
||||
},
|
||||
amount,
|
||||
},
|
||||
routerParams,
|
||||
routerConfig
|
||||
router,
|
||||
config
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { useMemo } from 'react'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
|
||||
/**
|
||||
* Returns query arguments for the Routing API query or undefined if the
|
||||
@@ -11,13 +12,13 @@ export function useRoutingAPIArguments({
|
||||
tokenOut,
|
||||
amount,
|
||||
tradeType,
|
||||
useClientSideRouter,
|
||||
routerPreference,
|
||||
}: {
|
||||
tokenIn: Currency | undefined
|
||||
tokenOut: Currency | undefined
|
||||
amount: CurrencyAmount<Currency> | undefined
|
||||
tradeType: TradeType
|
||||
useClientSideRouter: boolean
|
||||
routerPreference: RouterPreference
|
||||
}) {
|
||||
return useMemo(
|
||||
() =>
|
||||
@@ -33,9 +34,9 @@ export function useRoutingAPIArguments({
|
||||
tokenOutChainId: tokenOut.wrapped.chainId,
|
||||
tokenOutDecimals: tokenOut.wrapped.decimals,
|
||||
tokenOutSymbol: tokenOut.wrapped.symbol,
|
||||
useClientSideRouter,
|
||||
routerPreference,
|
||||
type: (tradeType === TradeType.EXACT_INPUT ? 'exactIn' : 'exactOut') as 'exactIn' | 'exactOut',
|
||||
},
|
||||
[amount, tokenIn, tokenOut, tradeType, useClientSideRouter]
|
||||
[amount, routerPreference, tokenIn, tokenOut, tradeType]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) {
|
||||
})
|
||||
|
||||
provider.on('block', onBlock)
|
||||
|
||||
return () => {
|
||||
stale = true
|
||||
provider.removeListener('block', onBlock)
|
||||
@@ -69,11 +68,7 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) {
|
||||
return void 0
|
||||
}, [activeChainId, provider, onBlock, setChainBlock, windowVisible])
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
value: chainId === activeChainId ? block : undefined,
|
||||
}),
|
||||
[activeChainId, block, chainId]
|
||||
)
|
||||
const blockValue = useMemo(() => (chainId === activeChainId ? block : undefined), [activeChainId, block, chainId])
|
||||
const value = useMemo(() => ({ value: blockValue }), [blockValue])
|
||||
return <BlockNumberContext.Provider value={value}>{children}</BlockNumberContext.Provider>
|
||||
}
|
||||
|
||||
35
src/nft/components/details/CollectionProfile.tsx
Normal file
35
src/nft/components/details/CollectionProfile.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { badge, subheadSmall } from '../../css/common.css'
|
||||
import { Box, BoxProps } from '../Box'
|
||||
import { Row } from '../Flex'
|
||||
import { VerifiedIcon } from '../icons'
|
||||
|
||||
export const CollectionProfile = ({
|
||||
label,
|
||||
isVerified,
|
||||
name,
|
||||
avatarUrl,
|
||||
...props
|
||||
}: {
|
||||
isVerified?: boolean
|
||||
label: string
|
||||
name: string
|
||||
avatarUrl: string
|
||||
} & BoxProps) => {
|
||||
return (
|
||||
<Row {...props}>
|
||||
{avatarUrl ? (
|
||||
<Box as="img" src={avatarUrl} height="36" width="36" marginRight="12" borderRadius="round" />
|
||||
) : (
|
||||
<Box role="img" background="fallbackGradient" height="36" width="36" marginRight="12" borderRadius="round" />
|
||||
)}
|
||||
<div>
|
||||
<Box as="span" color="darkGray" style={{ textTransform: 'uppercase' }} className={badge}>
|
||||
{label}
|
||||
</Box>
|
||||
<Row marginTop="4" className={subheadSmall} color="blackBlue">
|
||||
{name} {isVerified && <VerifiedIcon />}
|
||||
</Row>
|
||||
</div>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
71
src/nft/components/details/Details.tsx
Normal file
71
src/nft/components/details/Details.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { bodySmall } from '../../css/common.css'
|
||||
import { shortenAddress } from '../../utils/address'
|
||||
import { Box, BoxProps } from '../Box'
|
||||
import { Column, Row } from '../Flex'
|
||||
|
||||
const DetailItemLabel = (props: BoxProps) => <Box as="span" fontSize="14" color="darkGray" {...props} />
|
||||
|
||||
const DetailItemValue = (props: BoxProps) => <Box as="span" fontSize="14" marginLeft="4" color="blackBlue" {...props} />
|
||||
|
||||
const Detail = (props: BoxProps) => (
|
||||
<Row justifyContent="space-between" width="full" style={{ minWidth: '224px' }} {...props} />
|
||||
)
|
||||
|
||||
export const Details = ({
|
||||
contractAddress,
|
||||
tokenId,
|
||||
metadataUrl,
|
||||
tokenType,
|
||||
totalSupply,
|
||||
blockchain,
|
||||
}: {
|
||||
contractAddress: string
|
||||
tokenId: string
|
||||
metadataUrl: string
|
||||
tokenType: string
|
||||
totalSupply: number
|
||||
blockchain: string
|
||||
}) => (
|
||||
<Row gap={{ md: '32', sm: '16' }} width="full" justifyContent="space-between" alignItems="flex-start" flexWrap="wrap">
|
||||
<Column width={{ sm: 'full', md: 'auto' }} gap="10">
|
||||
<Detail>
|
||||
<DetailItemLabel>Contract Address: </DetailItemLabel>
|
||||
<a
|
||||
href={`https://etherscan.io/token/${contractAddress}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<DetailItemValue>{shortenAddress(contractAddress)}</DetailItemValue>
|
||||
</a>
|
||||
</Detail>
|
||||
<Detail>
|
||||
<DetailItemLabel>Token ID:</DetailItemLabel>
|
||||
<DetailItemValue className={bodySmall}>{tokenId}</DetailItemValue>
|
||||
</Detail>
|
||||
{metadataUrl ? (
|
||||
<Detail>
|
||||
<DetailItemLabel>Metadata:</DetailItemLabel>
|
||||
<a href={metadataUrl} target="_blank" rel="noreferrer" style={{ textDecoration: 'none' }}>
|
||||
<DetailItemValue>{metadataUrl.slice(0, 12)}...</DetailItemValue>
|
||||
</a>
|
||||
</Detail>
|
||||
) : null}
|
||||
</Column>
|
||||
|
||||
<Column width={{ sm: 'full', md: 'auto' }} gap="10">
|
||||
<Detail>
|
||||
<DetailItemLabel>Contract type:</DetailItemLabel>
|
||||
<DetailItemValue>{tokenType}</DetailItemValue>
|
||||
</Detail>
|
||||
<Detail>
|
||||
<DetailItemLabel>Total supply:</DetailItemLabel>
|
||||
<DetailItemValue>{totalSupply}</DetailItemValue>
|
||||
</Detail>
|
||||
<Detail>
|
||||
<DetailItemLabel>Blockchain:</DetailItemLabel>
|
||||
<DetailItemValue>{blockchain}</DetailItemValue>
|
||||
</Detail>
|
||||
</Column>
|
||||
</Row>
|
||||
)
|
||||
18
src/nft/components/details/Traits.css.ts
Normal file
18
src/nft/components/details/Traits.css.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
import { sprinkles } from '../../css/sprinkles.css'
|
||||
|
||||
export const grid = style([
|
||||
sprinkles({ gap: '16', display: 'grid' }),
|
||||
{
|
||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||
'@media': {
|
||||
'(max-width: 1536px)': {
|
||||
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||
},
|
||||
'(max-width: 640px)': {
|
||||
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
75
src/nft/components/details/Traits.tsx
Normal file
75
src/nft/components/details/Traits.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import qs from 'query-string'
|
||||
|
||||
import { badge } from '../../css/common.css'
|
||||
import { Box } from '../Box'
|
||||
import { Column } from '../Flex'
|
||||
import * as styles from './Traits.css'
|
||||
|
||||
interface TraitProps {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const Trait: React.FC<TraitProps> = ({ label, value }: TraitProps) => (
|
||||
<Column backgroundColor="lightGray" padding="16" gap="4" borderRadius="12">
|
||||
<Box
|
||||
as="span"
|
||||
className={badge}
|
||||
color="darkGray"
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
style={{ textTransform: 'uppercase' }}
|
||||
maxWidth={{ sm: '120', md: '160' }}
|
||||
>
|
||||
{label}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
as="span"
|
||||
color="blackBlue"
|
||||
fontSize="16"
|
||||
fontWeight="normal"
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
maxWidth={{ sm: '120', md: '160' }}
|
||||
>
|
||||
{value}
|
||||
</Box>
|
||||
</Column>
|
||||
)
|
||||
|
||||
export const Traits = ({
|
||||
traits,
|
||||
collectionAddress,
|
||||
}: {
|
||||
traits: {
|
||||
value: string
|
||||
trait_type: string
|
||||
}[]
|
||||
collectionAddress: string
|
||||
}) => (
|
||||
<div className={styles.grid}>
|
||||
{traits.length === 0
|
||||
? 'No traits'
|
||||
: traits.map((item) => {
|
||||
const params = qs.stringify(
|
||||
{ traits: [`("${item.trait_type}","${item.value}")`] },
|
||||
{
|
||||
arrayFormat: 'comma',
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<a
|
||||
key={`${item.trait_type}-${item.value}`}
|
||||
href={`#/nft/collection/${collectionAddress}?${params}`}
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<Trait label={item.trait_type} value={item.value} />
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
@@ -1,5 +1,38 @@
|
||||
import { useQuery } from 'react-query'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import { Details } from '../../components/details/Details'
|
||||
import { fetchSingleAsset } from '../../queries'
|
||||
import { CollectionInfoForAsset, GenieAsset } from '../../types'
|
||||
|
||||
const Asset = () => {
|
||||
return <div>NFT Details Page</div>
|
||||
const { tokenId = '', contractAddress = '' } = useParams()
|
||||
|
||||
const { data } = useQuery(['assetDetail', contractAddress, tokenId], () =>
|
||||
fetchSingleAsset({ contractAddress, tokenId })
|
||||
)
|
||||
|
||||
let asset = {} as GenieAsset
|
||||
let collection = {} as CollectionInfoForAsset
|
||||
|
||||
if (data) {
|
||||
asset = data[0] || {}
|
||||
collection = data[1] || {}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{' '}
|
||||
<Details
|
||||
contractAddress={contractAddress}
|
||||
tokenId={tokenId}
|
||||
tokenType={asset.tokenType}
|
||||
blockchain="Ethereum"
|
||||
metadataUrl={asset.externalLink}
|
||||
totalSupply={collection.totalSupply}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Asset
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FungibleToken } from '../../types'
|
||||
|
||||
export const fetchSearchTokens = async (tokenQuery: string): Promise<FungibleToken[]> => {
|
||||
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/searchTokens?tokenQuery=${tokenQuery}`
|
||||
const url = `${process.env.REACT_APP_TEMP_API_URL}/tokens/search?tokenQuery=${tokenQuery}`
|
||||
|
||||
const r = await fetch(url, {
|
||||
method: 'GET',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FungibleToken } from '../../types'
|
||||
|
||||
export const fetchTrendingTokens = async (numTokens?: number): Promise<FungibleToken[]> => {
|
||||
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/tokens/trending${numTokens ? `?numTokens=${numTokens}` : ''}`
|
||||
const url = `${process.env.REACT_APP_TEMP_API_URL}/tokens/trending${numTokens ? `?numTokens=${numTokens}` : ''}`
|
||||
|
||||
const r = await fetch(url, {
|
||||
method: 'GET',
|
||||
|
||||
@@ -12,6 +12,7 @@ import { lazy, Suspense, useEffect } from 'react'
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Z_INDEX } from 'theme'
|
||||
import { SpinnerSVG } from 'theme/components'
|
||||
import { getBrowser } from 'utils/browser'
|
||||
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
||||
@@ -73,7 +74,7 @@ const HeaderWrapper = styled.div`
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
z-index: ${Z_INDEX.absoluteTop};
|
||||
`
|
||||
|
||||
const Marginer = styled.div`
|
||||
|
||||
@@ -11,14 +11,15 @@ import LoadingTokenDetail from 'components/Tokens/TokenDetails/LoadingTokenDetai
|
||||
import NetworkBalance from 'components/Tokens/TokenDetails/NetworkBalance'
|
||||
import TokenDetail from 'components/Tokens/TokenDetails/TokenDetail'
|
||||
import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage'
|
||||
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
||||
import Widget, { WIDGET_WIDTH } from 'components/Widget'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { L1_CHAIN_IDS, L2_CHAIN_IDS, SupportedChainId, TESTNET_CHAIN_IDS } from 'constants/chains'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { useIsUserAddedToken, useToken } from 'hooks/Tokens'
|
||||
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
|
||||
import { useMemo } from 'react'
|
||||
import { Navigate, useLocation, useParams } from 'react-router-dom'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { Navigate, useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const Footer = styled.div`
|
||||
@@ -79,10 +80,28 @@ export default function TokenDetails() {
|
||||
const location = useLocation()
|
||||
const { tokenAddress } = useParams<{ tokenAddress?: string }>()
|
||||
const token = useToken(tokenAddress)
|
||||
const tokenWarning = tokenAddress ? checkWarning(tokenAddress) : null
|
||||
const isBlockedToken = tokenWarning?.canProceed === false
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike<boolean>) => void }>()
|
||||
const shouldShowSpeedbump = !useIsUserAddedToken(token) && tokenWarning !== null
|
||||
// Show token safety modal if Swap-reviewing a warning token, at all times if the current token is blocked
|
||||
const onReviewSwap = useCallback(() => {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
shouldShowSpeedbump ? setContinueSwap({ resolve }) : resolve(true)
|
||||
})
|
||||
}, [shouldShowSpeedbump])
|
||||
|
||||
const onResolveSwap = useCallback(
|
||||
(value: boolean) => {
|
||||
continueSwap?.resolve(value)
|
||||
setContinueSwap(undefined)
|
||||
},
|
||||
[continueSwap, setContinueSwap]
|
||||
)
|
||||
|
||||
const tokenWarning = token ? checkWarning(token.address) : null
|
||||
/* network balance handling */
|
||||
|
||||
const { data: networkData } = NetworkBalances(token?.address)
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const totalBalance = 4.3 // dummy data
|
||||
@@ -128,9 +147,9 @@ export default function TokenDetails() {
|
||||
<>
|
||||
<TokenDetail address={token.address} />
|
||||
<RightPanel>
|
||||
<Widget defaultToken={token ?? undefined} />
|
||||
<Widget defaultToken={token ?? undefined} onReviewSwapClick={onReviewSwap} />
|
||||
{tokenWarning && <TokenSafetyMessage tokenAddress={token.address} warning={tokenWarning} />}
|
||||
<BalanceSummary address={token.address} totalBalance={totalBalance} networkBalances={balancesByNetwork} />
|
||||
<BalanceSummary address={token.address} />
|
||||
</RightPanel>
|
||||
<Footer>
|
||||
<FooterBalanceSummary
|
||||
@@ -139,6 +158,14 @@ export default function TokenDetails() {
|
||||
networkBalances={balancesByNetwork}
|
||||
/>
|
||||
</Footer>
|
||||
<TokenSafetyModal
|
||||
isOpen={isBlockedToken || !!continueSwap}
|
||||
tokenAddress={token.address}
|
||||
onContinue={() => onResolveSwap(true)}
|
||||
onBlocked={() => navigate(-1)}
|
||||
onCancel={() => onResolveSwap(false)}
|
||||
showCancel={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</TokenDetailsLayout>
|
||||
|
||||
@@ -87,8 +87,9 @@ const Tokens = () => {
|
||||
</TitleContainer>
|
||||
<FiltersWrapper>
|
||||
<FiltersContainer>
|
||||
{tokensNetworkFilterFlag === TokensNetworkFilterVariant.Enabled && <NetworkFilter />}
|
||||
<FavoriteButton />
|
||||
{tokensNetworkFilterFlag === TokensNetworkFilterVariant.Enabled && <NetworkFilter />}
|
||||
|
||||
<TimeSelector />
|
||||
</FiltersContainer>
|
||||
<SearchContainer>
|
||||
@@ -116,8 +117,8 @@ export const LoadingTokens = () => {
|
||||
</TitleContainer>
|
||||
<FiltersWrapper>
|
||||
<FiltersContainer>
|
||||
{tokensNetworkFilterFlag === TokensNetworkFilterVariant.Enabled && <NetworkFilter />}
|
||||
<FavoriteButton />
|
||||
{tokensNetworkFilterFlag === TokensNetworkFilterVariant.Enabled && <NetworkFilter />}
|
||||
<TimeSelector />
|
||||
</FiltersContainer>
|
||||
<SearchContainer>
|
||||
|
||||
@@ -1,36 +1,67 @@
|
||||
import { BaseProvider, JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { createApi, fetchBaseQuery, FetchBaseQueryError } from '@reduxjs/toolkit/query/react'
|
||||
import { Protocol } from '@uniswap/router-sdk'
|
||||
import { ChainId } from '@uniswap/smart-order-router'
|
||||
import { RPC_URLS } from 'constants/networks'
|
||||
import { AlphaRouter, ChainId } from '@uniswap/smart-order-router'
|
||||
import { RPC_PROVIDERS } from 'constants/networks'
|
||||
import { getClientSideQuote, toSupportedChainId } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
import ms from 'ms.macro'
|
||||
import qs from 'qs'
|
||||
|
||||
import { GetQuoteResult } from './types'
|
||||
|
||||
const routerProviders = new Map<ChainId, BaseProvider>()
|
||||
function getRouterProvider(chainId: ChainId): BaseProvider {
|
||||
const provider = routerProviders.get(chainId)
|
||||
if (provider) return provider
|
||||
export enum RouterPreference {
|
||||
API = 'api',
|
||||
CLIENT = 'client',
|
||||
PRICE = 'price',
|
||||
}
|
||||
|
||||
const routers = new Map<ChainId, AlphaRouter>()
|
||||
function getRouter(chainId: ChainId): AlphaRouter {
|
||||
const router = routers.get(chainId)
|
||||
if (router) return router
|
||||
|
||||
const supportedChainId = toSupportedChainId(chainId)
|
||||
if (supportedChainId) {
|
||||
const provider = new JsonRpcProvider(RPC_URLS[supportedChainId])
|
||||
routerProviders.set(chainId, provider)
|
||||
return provider
|
||||
const provider = RPC_PROVIDERS[supportedChainId]
|
||||
const router = new AlphaRouter({ chainId, provider })
|
||||
routers.set(chainId, router)
|
||||
return router
|
||||
}
|
||||
|
||||
throw new Error(`Router does not support this chain (chainId: ${chainId}).`)
|
||||
}
|
||||
|
||||
const protocols: Protocol[] = [Protocol.V2, Protocol.V3, Protocol.MIXED]
|
||||
|
||||
const DEFAULT_QUERY_PARAMS = {
|
||||
protocols: protocols.map((p) => p.toLowerCase()).join(','),
|
||||
// example other params
|
||||
// forceCrossProtocol: 'true',
|
||||
// minSplits: '5',
|
||||
// routing API quote params: https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/schema/quote-schema.ts
|
||||
const API_QUERY_PARAMS = {
|
||||
protocols: 'v2,v3,mixed',
|
||||
}
|
||||
const CLIENT_PARAMS = {
|
||||
protocols: [Protocol.V2, Protocol.V3, Protocol.MIXED],
|
||||
}
|
||||
// Price queries are tuned down to minimize the required RPCs to respond to them.
|
||||
// TODO(zzmp): This will be used after testing router caching.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const PRICE_PARAMS = {
|
||||
protocols: [Protocol.V2, Protocol.V3],
|
||||
v2PoolSelection: {
|
||||
topN: 2,
|
||||
topNDirectSwaps: 1,
|
||||
topNTokenInOut: 2,
|
||||
topNSecondHop: 1,
|
||||
topNWithEachBaseToken: 2,
|
||||
topNWithBaseToken: 2,
|
||||
},
|
||||
v3PoolSelection: {
|
||||
topN: 2,
|
||||
topNDirectSwaps: 1,
|
||||
topNTokenInOut: 2,
|
||||
topNSecondHop: 1,
|
||||
topNWithEachBaseToken: 2,
|
||||
topNWithBaseToken: 2,
|
||||
},
|
||||
maxSwapsPerPath: 2,
|
||||
minSplits: 1,
|
||||
maxSplits: 1,
|
||||
distributionPercent: 100,
|
||||
}
|
||||
|
||||
export const routingApi = createApi({
|
||||
@@ -51,24 +82,20 @@ export const routingApi = createApi({
|
||||
tokenOutDecimals: number
|
||||
tokenOutSymbol?: string
|
||||
amount: string
|
||||
useClientSideRouter: boolean // included in key to invalidate on change
|
||||
routerPreference: RouterPreference
|
||||
type: 'exactIn' | 'exactOut'
|
||||
}
|
||||
>({
|
||||
async queryFn(args, _api, _extraOptions, fetch) {
|
||||
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, useClientSideRouter, type } =
|
||||
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, routerPreference, type } =
|
||||
args
|
||||
|
||||
let result
|
||||
|
||||
try {
|
||||
if (useClientSideRouter) {
|
||||
const chainId = args.tokenInChainId
|
||||
const params = { chainId, provider: getRouterProvider(chainId) }
|
||||
result = await getClientSideQuote(args, params, { protocols })
|
||||
} else {
|
||||
if (routerPreference === RouterPreference.API) {
|
||||
const query = qs.stringify({
|
||||
...DEFAULT_QUERY_PARAMS,
|
||||
...API_QUERY_PARAMS,
|
||||
tokenInAddress,
|
||||
tokenInChainId,
|
||||
tokenOutAddress,
|
||||
@@ -77,6 +104,15 @@ export const routingApi = createApi({
|
||||
type,
|
||||
})
|
||||
result = await fetch(`quote?${query}`)
|
||||
} else {
|
||||
const router = getRouter(args.tokenInChainId)
|
||||
result = await getClientSideQuote(
|
||||
args,
|
||||
router,
|
||||
// TODO(zzmp): Use PRICE_PARAMS for RouterPreference.PRICE.
|
||||
// This change is intentionally being deferred to first see what effect router caching has.
|
||||
CLIENT_PARAMS
|
||||
)
|
||||
}
|
||||
|
||||
return { data: result.data as GetQuoteResult }
|
||||
|
||||
@@ -7,17 +7,11 @@ import { useRoutingAPIArguments } from 'lib/hooks/routing/useRoutingAPIArguments
|
||||
import useIsValidBlock from 'lib/hooks/useIsValidBlock'
|
||||
import ms from 'ms.macro'
|
||||
import { useMemo } from 'react'
|
||||
import { useGetQuoteQuery } from 'state/routing/slice'
|
||||
import { useClientSideRouter } from 'state/user/hooks'
|
||||
import { RouterPreference, useGetQuoteQuery } from 'state/routing/slice'
|
||||
|
||||
import { GetQuoteResult, InterfaceTrade, TradeState } from './types'
|
||||
import { computeRoutes, transformRoutesToTrade } from './utils'
|
||||
|
||||
export enum RouterPreference {
|
||||
CLIENT = 'client',
|
||||
API = 'api',
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best trade by invoking the routing api or the smart order router on the client
|
||||
* @param tradeType whether the swap is an exact in/out
|
||||
@@ -26,9 +20,9 @@ export enum RouterPreference {
|
||||
*/
|
||||
export function useRoutingAPITrade<TTradeType extends TradeType>(
|
||||
tradeType: TTradeType,
|
||||
amountSpecified?: CurrencyAmount<Currency>,
|
||||
otherCurrency?: Currency,
|
||||
routerPreference?: RouterPreference
|
||||
amountSpecified: CurrencyAmount<Currency> | undefined,
|
||||
otherCurrency: Currency | undefined,
|
||||
routerPreference: RouterPreference
|
||||
): {
|
||||
state: TradeState
|
||||
trade: InterfaceTrade<Currency, Currency, TTradeType> | undefined
|
||||
@@ -41,22 +35,16 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
|
||||
[amountSpecified, otherCurrency, tradeType]
|
||||
)
|
||||
|
||||
const [clientSideRouterStoredPreference] = useClientSideRouter()
|
||||
const clientSideRouter = routerPreference
|
||||
? routerPreference === RouterPreference.CLIENT
|
||||
: clientSideRouterStoredPreference
|
||||
|
||||
const queryArgs = useRoutingAPIArguments({
|
||||
tokenIn: currencyIn,
|
||||
tokenOut: currencyOut,
|
||||
amount: amountSpecified,
|
||||
tradeType,
|
||||
useClientSideRouter: clientSideRouter,
|
||||
routerPreference,
|
||||
})
|
||||
|
||||
const { isLoading, isError, data, currentData } = useGetQuoteQuery(queryArgs ?? skipToken, {
|
||||
pollingInterval: ms`15s`,
|
||||
refetchOnFocus: true,
|
||||
})
|
||||
|
||||
const quoteResult: GetQuoteResult | undefined = useIsValidBlock(Number(data?.blockNumber) || 0) ? data : undefined
|
||||
|
||||
@@ -212,6 +212,8 @@ export function ExternalLinkIcon({
|
||||
)
|
||||
}
|
||||
|
||||
export const MAX_Z_INDEX = 9999
|
||||
|
||||
const ToolTipWrapper = styled.div<{ isCopyContractTooltip?: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -219,7 +221,7 @@ const ToolTipWrapper = styled.div<{ isCopyContractTooltip?: boolean }>`
|
||||
position: ${({ isCopyContractTooltip }) => (isCopyContractTooltip ? 'relative' : 'absolute')};
|
||||
right: ${({ isCopyContractTooltip }) => isCopyContractTooltip && '50%'};
|
||||
transform: translate(5px, 32px);
|
||||
z-index: 9999;
|
||||
z-index: ${MAX_Z_INDEX};
|
||||
`
|
||||
|
||||
const StyledTooltipTriangle = styled(TooltipTriangle)`
|
||||
|
||||
@@ -13,6 +13,7 @@ import { darkTheme } from '../nft/themes/darkTheme'
|
||||
import { lightTheme } from '../nft/themes/lightTheme'
|
||||
import { useIsDarkMode } from '../state/user/hooks'
|
||||
import { colors as ColorsPalette, colorsDark, colorsLight } from './colors'
|
||||
import { MAX_Z_INDEX } from './components'
|
||||
import { AllColors, Colors, ThemeColors } from './styled'
|
||||
import { opacify } from './utils'
|
||||
|
||||
@@ -63,6 +64,7 @@ export enum Z_INDEX {
|
||||
modal = 1060,
|
||||
popover = 1070,
|
||||
tooltip = 1080,
|
||||
absoluteTop = MAX_Z_INDEX,
|
||||
}
|
||||
|
||||
const deprecated_mediaWidthTemplates: { [width in keyof typeof MEDIA_WIDTHS]: typeof css } = Object.keys(
|
||||
|
||||
14
yarn.lock
14
yarn.lock
@@ -4079,10 +4079,10 @@
|
||||
tiny-invariant "^1.1.0"
|
||||
toformat "^2.0.0"
|
||||
|
||||
"@uniswap/smart-order-router@^2.5.26", "@uniswap/smart-order-router@^2.9.2":
|
||||
version "2.9.2"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/smart-order-router/-/smart-order-router-2.9.2.tgz#3c9296b5b3821e191b6759a870330e4b10a9e9df"
|
||||
integrity sha512-t+ruGvZTOvOJcVjxNPSU4o3GuPU/RYHr8KSKZlAHkZfusjbWrOLrO/aHzy/ncoRMNQz1UMBWQ2n3LDzqBxbTkA==
|
||||
"@uniswap/smart-order-router@^2.10.0", "@uniswap/smart-order-router@^2.5.26":
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/smart-order-router/-/smart-order-router-2.10.0.tgz#f9f13bd9a940fc2ee123a6dbe6c64a6fab19a365"
|
||||
integrity sha512-7dfFlPbg36goZOWlRowTDDrRc1vWwKLhAuhftf6sN+ECJ4CeqRgDDZgxw/ZJhfDSl1RC6IYN71CfEcmhnbRDlw==
|
||||
dependencies:
|
||||
"@uniswap/default-token-list" "^2.0.0"
|
||||
"@uniswap/router-sdk" "^1.3.0"
|
||||
@@ -5103,9 +5103,9 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^8.0.1:
|
||||
version "8.6.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571"
|
||||
integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==
|
||||
version "8.11.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
|
||||
integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
json-schema-traverse "^1.0.0"
|
||||
|
||||
Reference in New Issue
Block a user