Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1297b2aa3 | ||
|
|
6ab6f4daa5 | ||
|
|
ae559d164a | ||
|
|
a90318cbe9 | ||
|
|
93cf4b358e | ||
|
|
ad8aff0b90 | ||
|
|
8757e413bb | ||
|
|
78ac7650ee | ||
|
|
f3e9b513ba | ||
|
|
412a10e0d9 | ||
|
|
411b690ae4 | ||
|
|
b721824c7f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@
|
||||
/src/locales/**/pseudo.po
|
||||
|
||||
# generated graphql types
|
||||
/src/graphql/**/__generated__
|
||||
schema.graphql
|
||||
|
||||
# dependencies
|
||||
|
||||
@@ -5,9 +5,7 @@ const BONSAI_COLLECTION_ADDRESS = '0xec9c519d49856fd2f8133a0741b4dbe002ce211b'
|
||||
|
||||
describe('Testing nfts', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/').then(() => {
|
||||
cy.get(getTestSelector('FiatOnrampAnnouncement-close')).first().click()
|
||||
})
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('should load nft leaderboard', () => {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Pool', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/pool').then(() => {
|
||||
@@ -8,18 +6,12 @@ describe('Pool', () => {
|
||||
})
|
||||
|
||||
it('add liquidity links to /add/ETH', () => {
|
||||
cy.get('body')
|
||||
.then((body) => {
|
||||
if (body.find(getTestSelector('FiatOnrampAnnouncement-close')).length > 0) {
|
||||
cy.get(getTestSelector('FiatOnrampAnnouncement-close')).click()
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
cy.get('#join-pool-button')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
})
|
||||
})
|
||||
cy.get('body').then(() => {
|
||||
cy.get('#join-pool-button')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
"@sentry/react": "^7.29.0",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "^1.3.1",
|
||||
"@uniswap/analytics-events": "^2.5.1",
|
||||
"@uniswap/analytics-events": "^2.6.0",
|
||||
"@uniswap/conedison": "^1.4.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
|
||||
@@ -16,10 +16,10 @@ try {
|
||||
}
|
||||
|
||||
// The last recorded size for these assets, as reported by `yarn build`.
|
||||
const MAX_SIZE_MAIN_KB = 361.36
|
||||
const LAST_SIZE_MAIN_KB = 374
|
||||
|
||||
// This is the async-loaded js, called <number>.<hash>.js, with a matching css file.
|
||||
const MAX_SIZE_ENTRY_MB = 1.38
|
||||
const LAST_SIZE_ENTRY_KB = 1417
|
||||
|
||||
const SIZE_TOLERANCE_KB = 5
|
||||
|
||||
@@ -30,20 +30,22 @@ let fail = false
|
||||
console.log('File sizes after gzip:\n')
|
||||
jsEntrypoints.forEach((entrypoint) => {
|
||||
const name = entrypoint.match(/\/([\w\d-]*)\./)[1]
|
||||
const size = gzipSize(fs.readFileSync(path.join(buildDir, entrypoint)))
|
||||
const size = gzipSize(fs.readFileSync(path.join(buildDir, entrypoint))) / 1024
|
||||
|
||||
let maxSize = MAX_SIZE_ENTRY_MB * 1024 * 1024
|
||||
let maxSize = LAST_SIZE_ENTRY_KB + SIZE_TOLERANCE_KB
|
||||
if (name === 'runtime-main') {
|
||||
return
|
||||
} else if (name === 'main') {
|
||||
maxSize = MAX_SIZE_MAIN_KB * 1024
|
||||
maxSize = LAST_SIZE_MAIN_KB + SIZE_TOLERANCE_KB
|
||||
}
|
||||
maxSize += SIZE_TOLERANCE_KB * 1024
|
||||
|
||||
const report = `\t${size.toFixed(2).padEnd(8)}kB\t${chalk.dim(
|
||||
`max: ${maxSize.toFixed().padEnd(4)} kB`
|
||||
)}\t${entrypoint}`
|
||||
if (maxSize > size) {
|
||||
console.log(chalk.green(`\t${toKb(maxSize)}\t${entrypoint}`))
|
||||
console.log(chalk.green(report))
|
||||
} else {
|
||||
console.log(chalk.red(`\t${toKb(maxSize)}\t${entrypoint}`), '\tdid you import an unnecessary dependency?')
|
||||
console.log(chalk.red(report), '\tdid you import an unnecessary dependency?')
|
||||
fail = true
|
||||
}
|
||||
})
|
||||
@@ -52,7 +54,3 @@ if (fail) {
|
||||
console.log(chalk.yellow('Reduce the file size or update the size limit (in scripts/test-size.js)'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function toKb(bytes) {
|
||||
return ((bytes / 1024).toFixed(2) + ' kB').padEnd(8)
|
||||
}
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
<svg id="Celo_Rings" data-name="Celo Rings" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 950 950"><defs><style>.cls-1{fill:#fbcc5c;}.cls-2{fill:#35d07f;}.cls-3{fill:#5ea33b;}</style></defs><title>Artboard 1</title><path id="Bottom_Ring" data-name="Bottom Ring" class="cls-1" d="M375,850c151.88,0,275-123.12,275-275S526.88,300,375,300,100,423.12,100,575,223.12,850,375,850Zm0,100C167.9,950,0,782.1,0,575S167.9,200,375,200,750,367.9,750,575,582.1,950,375,950Z"/><path id="Top_Ring" data-name="Top Ring" class="cls-2" d="M575,650c151.88,0,275-123.12,275-275S726.88,100,575,100,300,223.12,300,375,423.12,650,575,650Zm0,100c-207.1,0-375-167.9-375-375S367.9,0,575,0,950,167.9,950,375,782.1,750,575,750Z"/><path id="Rings_Overlap" data-name="Rings Overlap" class="cls-3" d="M587.39,750a274.38,274.38,0,0,0,54.55-108.06A274.36,274.36,0,0,0,750,587.4a373.63,373.63,0,0,1-29.16,133.45A373.62,373.62,0,0,1,587.39,750ZM308.06,308.06A274.36,274.36,0,0,0,200,362.6a373.63,373.63,0,0,1,29.16-133.45A373.62,373.62,0,0,1,362.61,200,274.38,274.38,0,0,0,308.06,308.06Z"/></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 250 250" style="enable-background:new 0 0 250 250;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FCFF52;}
|
||||
</style>
|
||||
<circle class="st0" cx="125" cy="125" r="125"/>
|
||||
<path d="M188.9,60.7H60.7v128.2h128.2v-44.8h-21.3c-7.3,16.3-23.8,27.7-42.7,27.7c-26,0-47.1-21.3-47.1-47.1c0-25.9,21.1-47,47.1-47
|
||||
c19.3,0,35.8,11.7,43.1,28.4h20.9V60.7z"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 630 B |
@@ -205,26 +205,6 @@ export const ButtonOutlined = styled(BaseButton)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonYellow = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
color: ${({ theme }) => theme.accentWarning};
|
||||
&:focus {
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
}
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.stateOverlayHover};
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
&:active {
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
opacity: 60%;
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonEmpty = styled(BaseButton)`
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
@@ -422,7 +402,7 @@ function pickThemeButtonBackgroundColor({ theme, emphasis }: { theme: DefaultThe
|
||||
case ButtonEmphasis.high:
|
||||
return theme.accentAction
|
||||
case ButtonEmphasis.promotional:
|
||||
return theme.accentTextLightPrimary
|
||||
return theme.accentActionSoft
|
||||
case ButtonEmphasis.highSoft:
|
||||
return theme.accentActionSoft
|
||||
case ButtonEmphasis.low:
|
||||
@@ -476,7 +456,7 @@ function pickThemeButtonTextColor({ theme, emphasis }: { theme: DefaultTheme; em
|
||||
switch (emphasis) {
|
||||
case ButtonEmphasis.high:
|
||||
case ButtonEmphasis.promotional:
|
||||
return theme.accentTextLightPrimary
|
||||
return theme.accentAction
|
||||
case ButtonEmphasis.highSoft:
|
||||
return theme.accentAction
|
||||
case ButtonEmphasis.low:
|
||||
|
||||
@@ -3,6 +3,7 @@ import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRout
|
||||
import { NftGraphqlVariant, useNftGraphqlFlag } from 'featureFlags/flags/nftlGraphql'
|
||||
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
|
||||
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
|
||||
import { TaxServiceVariant, useTaxServiceBannerFlag } from 'featureFlags/flags/taxServiceBanner'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
@@ -229,6 +230,12 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.nftGraphql}
|
||||
label="Migrate NFT read endpoints to GQL"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={TaxServiceVariant}
|
||||
value={useTaxServiceBannerFlag()}
|
||||
featureFlag={FeatureFlag.taxService}
|
||||
label="Tax Service Banner"
|
||||
/>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { InterfaceEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import fiatMaskUrl from 'assets/svg/fiat_mask.svg'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useToggleWalletDropdown } from 'state/application/hooks'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { useFiatOnrampAck } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
const Arrow = styled.div`
|
||||
top: -4px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
width: 16px;
|
||||
|
||||
::before {
|
||||
background: hsl(315.75, 93%, 83%);
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
transform: rotate(45deg);
|
||||
width: 16px;
|
||||
}
|
||||
`
|
||||
const ArrowWrapper = styled.div`
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 90%;
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
min-height: 92px;
|
||||
|
||||
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
|
||||
right: 36px;
|
||||
}
|
||||
`
|
||||
|
||||
const CloseIcon = styled(X)`
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 1;
|
||||
`
|
||||
const Wrapper = styled.button`
|
||||
background: radial-gradient(105% 250% at 100% 5%, hsla(318, 95%, 85%) 1%, hsla(331, 80%, 75%, 0.1) 84%),
|
||||
linear-gradient(180deg, hsla(296, 92%, 67%, 0.5) 0%, hsla(313, 96%, 60%, 0.5) 130%);
|
||||
background-color: hsla(297, 93%, 68%, 1);
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-align: start;
|
||||
max-width: 320px;
|
||||
min-height: 92px;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${fiatMaskUrl});
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: -154px; // roughly width of fiat mask image
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
|
||||
const Header = styled(ThemedText.SubHeader)`
|
||||
color: white;
|
||||
margin: 0;
|
||||
padding: 12px 12px 4px;
|
||||
position: relative;
|
||||
`
|
||||
const Body = styled(ThemedText.BodySmall)`
|
||||
color: white;
|
||||
margin: 0 12px 12px 12px !important;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const ANNOUNCEMENT_RENDERED = 'FiatOnrampAnnouncement-rendered'
|
||||
const ANNOUNCEMENT_DISMISSED = 'FiatOnrampAnnouncement-dismissed'
|
||||
|
||||
const MAX_RENDER_COUNT = 3
|
||||
export function FiatOnrampAnnouncement() {
|
||||
const { account } = useWeb3React()
|
||||
const [acks, acknowledge] = useFiatOnrampAck()
|
||||
const [localClose, setLocalClose] = useState(false)
|
||||
useEffect(() => {
|
||||
if (!sessionStorage.getItem(ANNOUNCEMENT_RENDERED)) {
|
||||
acknowledge({ renderCount: acks?.renderCount + 1 })
|
||||
sessionStorage.setItem(ANNOUNCEMENT_RENDERED, 'true')
|
||||
}
|
||||
}, [acknowledge, acks])
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setLocalClose(true)
|
||||
localStorage.setItem(ANNOUNCEMENT_DISMISSED, 'true')
|
||||
}, [])
|
||||
|
||||
const toggleWalletDropdown = useToggleWalletDropdown()
|
||||
const handleClick = useCallback(() => {
|
||||
sendAnalyticsEvent(InterfaceEventName.FIAT_ONRAMP_BANNER_CLICKED)
|
||||
toggleWalletDropdown()
|
||||
acknowledge({ user: true })
|
||||
}, [acknowledge, toggleWalletDropdown])
|
||||
|
||||
const openModal = useAppSelector((state) => state.application.openModal)
|
||||
|
||||
if (
|
||||
!account ||
|
||||
acks?.user ||
|
||||
localStorage.getItem(ANNOUNCEMENT_DISMISSED) ||
|
||||
acks?.renderCount >= MAX_RENDER_COUNT ||
|
||||
isMobile ||
|
||||
openModal !== null ||
|
||||
localClose
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<ArrowWrapper>
|
||||
<Arrow />
|
||||
<CloseIcon onClick={handleClose} data-testid="FiatOnrampAnnouncement-close" />
|
||||
<Wrapper onClick={handleClick}>
|
||||
<Header>
|
||||
<Trans>Buy crypto</Trans>
|
||||
</Header>
|
||||
<Body>
|
||||
<Trans>Get tokens at the best prices in web3 on Uniswap, powered by Moonpay.</Trans>
|
||||
</Body>
|
||||
</Wrapper>
|
||||
</ArrowWrapper>
|
||||
)
|
||||
}
|
||||
BIN
src/components/TaxServiceModal/CointrackerFullLogo.png
Normal file
BIN
src/components/TaxServiceModal/CointrackerFullLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
src/components/TaxServiceModal/CointrackerLogo.png
Normal file
BIN
src/components/TaxServiceModal/CointrackerLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
160
src/components/TaxServiceModal/TaxServiceBanner.tsx
Normal file
160
src/components/TaxServiceModal/TaxServiceBanner.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { bodySmall, subhead } from 'nft/css/common.css'
|
||||
import { useState } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { opacify } from 'theme/utils'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import TaxServiceModal from '.'
|
||||
import CointrackerLogo from './CointrackerLogo.png'
|
||||
import TokenTaxLogo from './TokenTaxLogo.png'
|
||||
|
||||
const PopupContainer = styled.div<{ show: boolean; isDarkMode: boolean }>`
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 13px;
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
display: ${({ show }) => (show ? 'flex' : 'none')};
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
right: clamp(0px, 1vw, 16px);
|
||||
z-index: ${Z_INDEX.sticky};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.slow} opacity ${timing.in}`};
|
||||
width: 320px;
|
||||
height: 156px;
|
||||
bottom: 50px;
|
||||
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
|
||||
background-image: url(${CointrackerLogo}), url(${TokenTaxLogo});
|
||||
background-size: 15%, 20%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right 75px, bottom 5px right 7px;
|
||||
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
background-size: 48px, 64px;
|
||||
background-position: top right 75px, bottom 20px right 7px;
|
||||
}
|
||||
|
||||
opacity: ${({ isDarkMode }) => (isDarkMode ? '0.9' : '0.25')};
|
||||
}
|
||||
`
|
||||
|
||||
const InnerContainer = styled.div<{ isDarkMode: boolean }>`
|
||||
border-radius: 12px;
|
||||
cursor: auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
background-color: ${({ isDarkMode, theme }) =>
|
||||
isDarkMode ? opacify(10, theme.accentAction) : opacify(4, theme.accentAction)};
|
||||
`
|
||||
|
||||
const Button = styled(ThemeButton)`
|
||||
margin-top: auto;
|
||||
margin-right: auto;
|
||||
padding: 8px 24px;
|
||||
gap: 8px;
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 70%;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
export const StyledXButton = styled(X)`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
&:active {
|
||||
opacity: ${({ theme }) => theme.opacity.click};
|
||||
}
|
||||
`
|
||||
|
||||
const TAX_SERVICE_DISMISSED = 'TaxServiceToast-dismissed'
|
||||
|
||||
export default function TaxServiceBanner() {
|
||||
const isDarkMode = useIsDarkMode()
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const sessionStorageTaxServiceDismissed = sessionStorage.getItem(TAX_SERVICE_DISMISSED)
|
||||
|
||||
if (!sessionStorageTaxServiceDismissed) {
|
||||
sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'false')
|
||||
}
|
||||
const [bannerOpen, setBannerOpen] = useState(sessionStorageTaxServiceDismissed !== 'true')
|
||||
const onDismiss = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
}, [])
|
||||
|
||||
const openTaxModal = useCallback(() => {
|
||||
setModalOpen(true)
|
||||
}, [])
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'true')
|
||||
setBannerOpen(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<PopupContainer show={bannerOpen} isDarkMode={isDarkMode}>
|
||||
<InnerContainer isDarkMode={isDarkMode}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<TextContainer data-testid="tax-service-description">
|
||||
<div className={subhead} style={{ paddingBottom: '12px' }}>
|
||||
<Trans>Save on your crypto taxes</Trans>
|
||||
</div>
|
||||
<div className={bodySmall} style={{ paddingBottom: '12px' }}>
|
||||
<Trans>Get up to a 20% discount on CoinTracker or TokenTax.</Trans>{' '}
|
||||
</div>
|
||||
</TextContainer>
|
||||
<StyledXButton size={20} onClick={handleClose} />
|
||||
</div>
|
||||
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.TAX_SERVICE_BANNER_CTA_BUTTON}
|
||||
>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
emphasis={ButtonEmphasis.promotional}
|
||||
onClick={openTaxModal}
|
||||
data-testid="learn-more-button"
|
||||
>
|
||||
<Trans>Learn more</Trans>
|
||||
</Button>
|
||||
</TraceEvent>
|
||||
</InnerContainer>
|
||||
<TaxServiceModal isOpen={modalOpen} onDismiss={onDismiss} />
|
||||
</PopupContainer>
|
||||
)
|
||||
}
|
||||
BIN
src/components/TaxServiceModal/TokenTaxFullLogo.png
Normal file
BIN
src/components/TaxServiceModal/TokenTaxFullLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
src/components/TaxServiceModal/TokenTaxLogo.png
Normal file
BIN
src/components/TaxServiceModal/TokenTaxLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
17
src/components/TaxServiceModal/index.test.tsx
Normal file
17
src/components/TaxServiceModal/index.test.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { render, screen } from '../../test-utils'
|
||||
import TaxServiceModal from './'
|
||||
import TaxServiceBanner from './TaxServiceBanner'
|
||||
|
||||
it('renders Tax Service Modal content', async () => {
|
||||
render(<TaxServiceModal isOpen={true} onDismiss={() => null} />)
|
||||
expect(screen.getByText('Save 10% on all plans')).toBeInTheDocument()
|
||||
expect(screen.getByText('New and existing users save up to 20%')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('tax-service-option-button')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('renders Tax Service Banner', async () => {
|
||||
render(<TaxServiceBanner />)
|
||||
expect(screen.getByText('Save on your crypto taxes')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('learn-more-button')).toHaveLength(1)
|
||||
expect(screen.getByText('Get up to a 20% discount on CoinTracker or TokenTax.')).toBeInTheDocument()
|
||||
})
|
||||
127
src/components/TaxServiceModal/index.tsx
Normal file
127
src/components/TaxServiceModal/index.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { ButtonEmphasis } from 'components/Button'
|
||||
import { ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { bodySmall, subhead } from 'nft/css/common.css'
|
||||
import { memo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Modal from '../Modal'
|
||||
import CointrackerFullLogo from './CointrackerFullLogo.png'
|
||||
import { StyledXButton } from './TaxServiceBanner'
|
||||
import TokenTaxFullLogo from './TokenTaxFullLogo.png'
|
||||
|
||||
interface TaxServiceModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
}
|
||||
|
||||
interface TaxServiceOptionProps {
|
||||
logo: any
|
||||
description: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const InnerContainer = styled.div`
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
width: 420px;
|
||||
height: 268px;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
gap: 20px;
|
||||
padding: 16px;
|
||||
`
|
||||
|
||||
const TaxOptionContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const TaxOptionDescription = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
const TaxOption = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
border-radius: 12px;
|
||||
cursor: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const StyledImageContainer = styled(Box)`
|
||||
width: 75%;
|
||||
height: 80%;
|
||||
cursor: auto;
|
||||
object-fit: contain;
|
||||
`
|
||||
|
||||
const Button = styled(ThemeButton)`
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
`
|
||||
|
||||
const TOKEN_TAX_URL = 'https://tokentax.co/uniswap?via=uniswap'
|
||||
const COINTRACKER_URL = 'https://www.cointracker.io/partner/uniswap?utm_source=uniswap'
|
||||
|
||||
const TOKEN_TAX_DESCRIPTION = 'Save 10% on all plans'
|
||||
const COINTRACKER_DESCRIPTION = 'New and existing users save up to 20%'
|
||||
|
||||
function TaxServiceOption({ description, logo, url }: TaxServiceOptionProps) {
|
||||
return (
|
||||
<TaxOption tabIndex={0}>
|
||||
<StyledImageContainer as="img" src={logo} draggable={false} />
|
||||
<TaxOptionDescription className={bodySmall}>{description}</TaxOptionDescription>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={
|
||||
url.includes('tokentax')
|
||||
? InterfaceElementName.TAX_SERVICE_TOKENTAX_BUTTON
|
||||
: InterfaceElementName.TAX_SERVICE_COINTRACKER_BUTTON
|
||||
}
|
||||
>
|
||||
<a href={url} target="_blank" rel="noreferrer" style={{ textDecoration: 'none' }}>
|
||||
<Button size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} data-testid="tax-service-option-button">
|
||||
Get started
|
||||
</Button>
|
||||
</a>
|
||||
</TraceEvent>
|
||||
</TaxOption>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(function TaxServiceModal({ isOpen, onDismiss }: TaxServiceModalProps) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90} minHeight={false}>
|
||||
<InnerContainer>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', userSelect: 'none' }}>
|
||||
<div className={subhead}>
|
||||
<Trans>Save on your crypto taxes</Trans>
|
||||
</div>
|
||||
<StyledXButton size={20} onClick={onDismiss} />
|
||||
</div>
|
||||
<TaxOptionContainer>
|
||||
<TaxServiceOption description={COINTRACKER_DESCRIPTION} logo={CointrackerFullLogo} url={COINTRACKER_URL} />
|
||||
<TaxServiceOption description={TOKEN_TAX_DESCRIPTION} logo={TokenTaxFullLogo} url={TOKEN_TAX_URL} />
|
||||
</TaxOptionContainer>
|
||||
</InnerContainer>
|
||||
</Modal>
|
||||
)
|
||||
})
|
||||
@@ -4,4 +4,3 @@ export const LARGE_MEDIA_BREAKPOINT = '840px'
|
||||
export const MEDIUM_MEDIA_BREAKPOINT = '720px'
|
||||
export const SMALL_MEDIA_BREAKPOINT = '540px'
|
||||
export const MOBILE_MEDIA_BREAKPOINT = '420px'
|
||||
// export const SMALL_MOBILE_MEDIA_BREAKPOINT = '390px'
|
||||
|
||||
@@ -2,6 +2,8 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import AddressClaimModal from 'components/claim/AddressClaimModal'
|
||||
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
|
||||
import FiatOnrampModal from 'components/FiatOnrampModal'
|
||||
import TaxServiceBanner from 'components/TaxServiceModal/TaxServiceBanner'
|
||||
import { useTaxServiceBannerEnabled } from 'featureFlags/flags/taxServiceBanner'
|
||||
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import { lazy } from 'react'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
@@ -18,6 +20,7 @@ export default function TopLevelModals() {
|
||||
const { account } = useWeb3React()
|
||||
useAccountRiskCheck(account)
|
||||
const accountBlocked = Boolean(blockedAccountModalOpen && account)
|
||||
const taxServiceEnabled = useTaxServiceBannerEnabled()
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -27,6 +30,7 @@ export default function TopLevelModals() {
|
||||
<TransactionCompleteModal />
|
||||
<AirdropModal />
|
||||
<FiatOnrampModal />
|
||||
{taxServiceEnabled && <TaxServiceBanner />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,16 +12,14 @@ import { SupportedChainId } from 'constants/chains'
|
||||
import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import useStablecoinPrice from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import ms from 'ms.macro'
|
||||
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { Copy, CreditCard, ExternalLink as ExternalLinkIcon, Info, Power } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useCurrencyBalanceString } from 'state/connection/hooks'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { useFiatOnrampAck } from 'state/user/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import styled, { css, keyframes } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
@@ -48,7 +46,7 @@ const BuyCryptoButtonBorderKeyframes = keyframes`
|
||||
}
|
||||
`
|
||||
|
||||
const BuyCryptoButton = styled(ThemeButton)<{ $animateBorder: boolean }>`
|
||||
const BuyCryptoButton = styled(ThemeButton)`
|
||||
border-color: transparent;
|
||||
border-radius: 12px;
|
||||
border-style: solid;
|
||||
@@ -60,7 +58,6 @@ const BuyCryptoButton = styled(ThemeButton)<{ $animateBorder: boolean }>`
|
||||
animation-fill-mode: none;
|
||||
animation-iteration-count: 2;
|
||||
animation-name: ${BuyCryptoButtonBorderKeyframes};
|
||||
animation-play-state: ${({ $animateBorder }) => ($animateBorder ? 'running' : 'paused')};
|
||||
animation-timing-function: ${({ theme }) => theme.transition.timing.inOut};
|
||||
`
|
||||
const WalletButton = styled(ThemeButton)`
|
||||
@@ -223,26 +220,6 @@ const AuthenticatedHeader = () => {
|
||||
closeModal()
|
||||
}, [clearCollectionFilters, closeModal, navigate, resetSellAssets, setSellPageState])
|
||||
|
||||
// animate the border of the buy crypto button when a user navigates here from the feature announcement
|
||||
// can be removed when components/FiatOnrampAnnouncment.tsx is no longer used
|
||||
const [acknowledgements, acknowledge] = useFiatOnrampAck()
|
||||
const animateBuyCryptoButtonBorder = acknowledgements?.user && !acknowledgements.system
|
||||
useEffect(() => {
|
||||
let stale = false
|
||||
let timeoutId = 0
|
||||
if (animateBuyCryptoButtonBorder) {
|
||||
timeoutId = setTimeout(() => {
|
||||
if (stale) return
|
||||
acknowledge({ system: true })
|
||||
}, ms`2 seconds`) as unknown as number
|
||||
// as unknown as number is necessary so it's not incorrectly typed as a NodeJS.Timeout
|
||||
}
|
||||
return () => {
|
||||
stale = true
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}, [acknowledge, animateBuyCryptoButtonBorder])
|
||||
|
||||
const openFiatOnrampModal = useOpenModal(ApplicationModal.FIAT_ONRAMP)
|
||||
const openFoRModalWithAnalytics = useCallback(() => {
|
||||
sendAnalyticsEvent(InterfaceEventName.FIAT_ONRAMP_WIDGET_OPENED)
|
||||
@@ -314,7 +291,6 @@ const AuthenticatedHeader = () => {
|
||||
<Trans>View and sell NFTs</Trans>
|
||||
</ProfileButton>
|
||||
<BuyCryptoButton
|
||||
$animateBorder={animateBuyCryptoButtonBorder}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
onClick={handleBuyCryptoClick}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { t, Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent, TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { FiatOnrampAnnouncement } from 'components/FiatOnrampAnnouncement'
|
||||
import { IconWrapper } from 'components/Identicon/StatusIcon'
|
||||
import WalletDropdown from 'components/WalletDropdown'
|
||||
import { getConnection } from 'connection/utils'
|
||||
@@ -318,7 +317,6 @@ export default function Web3Status() {
|
||||
return (
|
||||
<span ref={ref}>
|
||||
<Web3StatusInner />
|
||||
<FiatOnrampAnnouncement />
|
||||
<WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
|
||||
<Portal>
|
||||
<span ref={walletRef}>
|
||||
|
||||
@@ -107,6 +107,8 @@ export function useSyncWidgetTransactions() {
|
||||
const eventProperties = {
|
||||
...formatSwapSignedAnalyticsEventProperties({
|
||||
trade,
|
||||
// TODO: add once Widgets adds fiat values to callback
|
||||
fiatValues: { amountIn: null, amountOut: null },
|
||||
txHash: transaction.receipt?.transactionHash ?? '',
|
||||
}),
|
||||
...trace,
|
||||
|
||||
@@ -21,6 +21,14 @@ const CELO_QUOTER_ADDRESSES = '0x82825d0554fA07f7FC52Ab63c961F330fdEFa8E8'
|
||||
const CELO_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES = '0x3d79EdAaBC0EaB6F08ED885C05Fc0B014290D95A'
|
||||
const CELO_TICK_LENS_ADDRESSES = '0x5f115D9113F88e0a0Db1b5033D90D4a9690AcD3D'
|
||||
|
||||
// optimism goerli addresses
|
||||
const OPTIMISM_GOERLI_V3_CORE_FACTORY_ADDRESSES = '0xB656dA17129e7EB733A557f4EBc57B76CFbB5d10'
|
||||
const OPTIMISM_GOERLI_V3_MIGRATOR_ADDRESSES = '0xf6c55fBe84B1C8c3283533c53F51bC32F5C7Aba8'
|
||||
const OPTIMISM_GOERLI_MULTICALL_ADDRESS = '0x07F2D8a2a02251B62af965f22fC4744A5f96BCCd'
|
||||
const OPTIMISM_GOERLI_QUOTER_ADDRESSES = '0x9569CbA925c8ca2248772A9A4976A516743A246F'
|
||||
const OPTIMISM_GOERLI_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES = '0x39Ca85Af2F383190cBf7d7c41ED9202D27426EF6'
|
||||
const OPTIMISM_GOERLI_TICK_LENS_ADDRESSES = '0xe6140Bd164b63E8BfCfc40D5dF952f83e171758e'
|
||||
|
||||
// arbitrum goerli v3 addresses
|
||||
const ARBITRUM_GOERLI_V3_CORE_FACTORY_ADDRESSES = '0x4893376342d5D7b3e31d4184c08b265e5aB2A3f6'
|
||||
const ARBITRUM_GOERLI_V3_MIGRATOR_ADDRESSES = '0xA815919D2584Ac3F76ea9CB62E6Fd40a43BCe0C3'
|
||||
@@ -33,13 +41,13 @@ const ARBITRUM_GOERLI_TICK_LENS_ADDRESSES = '0xb52429333da969a0C79a60930a4Bf0020
|
||||
export const V3_CORE_FACTORY_ADDRESSES: AddressMap = {
|
||||
...constructSameAddressMap(V3_FACTORY_ADDRESS, [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISM_GOERLI,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
SupportedChainId.POLYGON,
|
||||
]),
|
||||
[SupportedChainId.CELO]: CELO_V3_CORE_FACTORY_ADDRESSES,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_V3_CORE_FACTORY_ADDRESSES,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_V3_CORE_FACTORY_ADDRESSES,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_V3_CORE_FACTORY_ADDRESSES,
|
||||
}
|
||||
|
||||
@@ -51,12 +59,12 @@ export const V3_MIGRATOR_ADDRESSES: AddressMap = {
|
||||
]),
|
||||
[SupportedChainId.CELO]: CELO_V3_MIGRATOR_ADDRESSES,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_V3_MIGRATOR_ADDRESSES,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_V3_MIGRATOR_ADDRESSES,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_V3_MIGRATOR_ADDRESSES,
|
||||
}
|
||||
|
||||
export const MULTICALL_ADDRESS: AddressMap = {
|
||||
...constructSameAddressMap('0x1F98415757620B543A52E61c46B32eB19261F984', [
|
||||
SupportedChainId.OPTIMISM_GOERLI,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
SupportedChainId.POLYGON,
|
||||
@@ -64,6 +72,7 @@ export const MULTICALL_ADDRESS: AddressMap = {
|
||||
[SupportedChainId.ARBITRUM_ONE]: '0xadF885960B47eA2CD9B55E6DAc6B42b7Cb2806dB',
|
||||
[SupportedChainId.CELO]: CELO_MULTICALL_ADDRESS,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_MULTICALL_ADDRESS,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_MULTICALL_ADDRESS,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_MULTICALL_ADDRESS,
|
||||
}
|
||||
|
||||
@@ -99,26 +108,26 @@ export const ARGENT_WALLET_DETECTOR_ADDRESS: AddressMap = {
|
||||
export const QUOTER_ADDRESSES: AddressMap = {
|
||||
...constructSameAddressMap('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6', [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISM_GOERLI,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
SupportedChainId.POLYGON,
|
||||
]),
|
||||
[SupportedChainId.CELO]: CELO_QUOTER_ADDRESSES,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_QUOTER_ADDRESSES,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_QUOTER_ADDRESSES,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_QUOTER_ADDRESSES,
|
||||
}
|
||||
|
||||
export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: AddressMap = {
|
||||
...constructSameAddressMap('0xC36442b4a4522E871399CD717aBDD847Ab11FE88', [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISM_GOERLI,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
SupportedChainId.POLYGON,
|
||||
]),
|
||||
[SupportedChainId.CELO]: CELO_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
|
||||
}
|
||||
|
||||
@@ -136,4 +145,5 @@ export const TICK_LENS_ADDRESSES: AddressMap = {
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_TICK_LENS_ADDRESSES,
|
||||
[SupportedChainId.CELO]: CELO_TICK_LENS_ADDRESSES,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_TICK_LENS_ADDRESSES,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_TICK_LENS_ADDRESSES,
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@ export enum FeatureFlag {
|
||||
gqlRouting = 'gqlRouting',
|
||||
statsigDummy = 'web_dummy_gate_amplitude_id',
|
||||
nftGraphql = 'nft_graphql_migration',
|
||||
taxService = 'tax_service_banner',
|
||||
}
|
||||
|
||||
11
src/featureFlags/flags/taxServiceBanner.ts
Normal file
11
src/featureFlags/flags/taxServiceBanner.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useTaxServiceBannerFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.taxService, BaseVariant.Control)
|
||||
}
|
||||
|
||||
export function useTaxServiceBannerEnabled(): boolean {
|
||||
return useTaxServiceBannerFlag() === BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as TaxServiceVariant }
|
||||
2274
src/graphql/data/__generated__/types-and-hooks.ts
generated
2274
src/graphql/data/__generated__/types-and-hooks.ts
generated
File diff suppressed because it is too large
Load Diff
5472
src/graphql/thegraph/__generated__/types-and-hooks.ts
generated
5472
src/graphql/thegraph/__generated__/types-and-hooks.ts
generated
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,13 @@ import useIsWindowVisible from './useIsWindowVisible'
|
||||
const USDCAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '10000')
|
||||
const DAIAmount = CurrencyAmount.fromRawAmount(DAI, '10000')
|
||||
|
||||
jest.mock('@web3-react/core', () => {
|
||||
return {
|
||||
useWeb3React: () => ({
|
||||
chainId: 1,
|
||||
}),
|
||||
}
|
||||
})
|
||||
jest.mock('./useAutoRouterSupported')
|
||||
jest.mock('./useClientSideV3Trade')
|
||||
jest.mock('./useDebounce')
|
||||
@@ -69,8 +76,7 @@ describe('#useBestV3Trade ExactIn', () => {
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, 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 })
|
||||
expect(result.current).toEqual({ state: TradeState.NO_ROUTE_FOUND, trade: undefined })
|
||||
})
|
||||
|
||||
describe('when routing api is in non-error state', () => {
|
||||
@@ -155,8 +161,7 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
USDC_MAINNET,
|
||||
RouterPreference.CLIENT
|
||||
)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
expect(result.current).toEqual({ state: TradeState.NO_ROUTE_FOUND, trade: undefined })
|
||||
})
|
||||
describe('when routing api is in non-error state', () => {
|
||||
it('does not compute client side v3 trade if routing api is LOADING', () => {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
import { useMemo } from 'react'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
import { InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
@@ -24,6 +26,7 @@ export function useBestTrade(
|
||||
state: TradeState
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
||||
} {
|
||||
const { chainId } = useWeb3React()
|
||||
const autoRouterSupported = useAutoRouterSupported()
|
||||
const isWindowVisible = useIsWindowVisible()
|
||||
|
||||
@@ -32,16 +35,27 @@ export function useBestTrade(
|
||||
200
|
||||
)
|
||||
|
||||
const isAWrapTransaction = useMemo(() => {
|
||||
if (!chainId || !amountSpecified || !debouncedOtherCurrency) return false
|
||||
const weth = WRAPPED_NATIVE_CURRENCY[chainId]
|
||||
return (
|
||||
(amountSpecified.currency.isNative && weth?.equals(debouncedOtherCurrency)) ||
|
||||
(debouncedOtherCurrency.isNative && weth?.equals(amountSpecified.currency))
|
||||
)
|
||||
}, [amountSpecified, chainId, debouncedOtherCurrency])
|
||||
|
||||
const shouldGetTrade = !isAWrapTransaction && isWindowVisible
|
||||
|
||||
const [clientSideRouter] = useClientSideRouter()
|
||||
const routingAPITrade = useRoutingAPITrade(
|
||||
tradeType,
|
||||
autoRouterSupported && isWindowVisible ? debouncedAmount : undefined,
|
||||
autoRouterSupported && shouldGetTrade ? debouncedAmount : undefined,
|
||||
debouncedOtherCurrency,
|
||||
clientSideRouter ? RouterPreference.CLIENT : RouterPreference.API
|
||||
)
|
||||
|
||||
const isLoading = routingAPITrade.state === TradeState.LOADING
|
||||
const useFallback = !autoRouterSupported || routingAPITrade.state === TradeState.NO_ROUTE_FOUND
|
||||
const useFallback = (!autoRouterSupported || routingAPITrade.state === TradeState.NO_ROUTE_FOUND) && shouldGetTrade
|
||||
|
||||
// only use client side router if routing api trade failed or is not supported
|
||||
const bestV3Trade = useClientSideV3Trade(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { PermitSignature } from 'hooks/usePermitAllowance'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useUniversalRouterSwapCallback } from './useUniversalRouter'
|
||||
// and the user has approved the slippage adjusted input amount for the trade
|
||||
export function useSwapCallback(
|
||||
trade: Trade<Currency, Currency, TradeType> | undefined, // trade to execute, required
|
||||
fiatValues: { amountIn: CurrencyAmount<Currency> | null; amountOut: CurrencyAmount<Currency> | null }, // usd values for amount in and out, logged for analytics
|
||||
allowedSlippage: Percent, // in bips
|
||||
permitSignature: PermitSignature | undefined
|
||||
): { callback: null | (() => Promise<string>) } {
|
||||
@@ -20,7 +21,7 @@ export function useSwapCallback(
|
||||
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
const universalRouterSwapCallback = useUniversalRouterSwapCallback(trade, {
|
||||
const universalRouterSwapCallback = useUniversalRouterSwapCallback(trade, fiatValues, {
|
||||
slippageTolerance: allowedSlippage,
|
||||
deadline,
|
||||
permit: permitSignature,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { t } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
|
||||
import { FeeOptions, toHex } from '@uniswap/v3-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
@@ -27,6 +27,7 @@ interface SwapOptions {
|
||||
|
||||
export function useUniversalRouterSwapCallback(
|
||||
trade: Trade<Currency, Currency, TradeType> | undefined,
|
||||
fiatValues: { amountIn: CurrencyAmount<Currency> | null; amountOut: CurrencyAmount<Currency> | null },
|
||||
options: SwapOptions
|
||||
) {
|
||||
const { account, chainId, provider } = useWeb3React()
|
||||
@@ -66,7 +67,7 @@ export function useUniversalRouterSwapCallback(
|
||||
.then((response) => {
|
||||
sendAnalyticsEvent(
|
||||
SwapEventName.SWAP_SIGNED,
|
||||
formatSwapSignedAnalyticsEventProperties({ trade, txHash: response.hash })
|
||||
formatSwapSignedAnalyticsEventProperties({ trade, fiatValues, txHash: response.hash })
|
||||
)
|
||||
if (tx.data !== response.data) {
|
||||
sendAnalyticsEvent(SwapEventName.SWAP_MODIFIED_IN_WALLET, { txHash: response.hash })
|
||||
@@ -84,6 +85,7 @@ export function useUniversalRouterSwapCallback(
|
||||
}, [
|
||||
account,
|
||||
chainId,
|
||||
fiatValues,
|
||||
options.deadline,
|
||||
options.feeOptions,
|
||||
options.permit,
|
||||
|
||||
@@ -36,9 +36,11 @@ export const getPriceUpdateBasisPoints = (
|
||||
|
||||
export const formatSwapSignedAnalyticsEventProperties = ({
|
||||
trade,
|
||||
fiatValues,
|
||||
txHash,
|
||||
}: {
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType> | Trade<Currency, Currency, TradeType>
|
||||
fiatValues: { amountIn: CurrencyAmount<Currency> | null; amountOut: CurrencyAmount<Currency> | null }
|
||||
txHash: string
|
||||
}) => ({
|
||||
transaction_hash: txHash,
|
||||
@@ -48,6 +50,8 @@ export const formatSwapSignedAnalyticsEventProperties = ({
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
|
||||
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
|
||||
token_in_amount_usd: fiatValues.amountIn ? parseFloat(fiatValues.amountIn.toFixed(2)) : undefined,
|
||||
token_out_amount_usd: fiatValues.amountOut ? parseFloat(fiatValues.amountOut.toFixed(2)) : undefined,
|
||||
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
|
||||
chain_id:
|
||||
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from 'state/mint/v3/hooks'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { ButtonError, ButtonLight, ButtonPrimary, ButtonText, ButtonYellow } from '../../components/Button'
|
||||
import { ButtonError, ButtonLight, ButtonPrimary, ButtonText } from '../../components/Button'
|
||||
import { BlueCard, OutlineCard, YellowCard } from '../../components/Card'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
@@ -53,7 +53,7 @@ import { Bound, Field } from '../../state/mint/v3/actions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
import { ThemedText } from '../../theme'
|
||||
import approveAmountCalldata from '../../utils/approveAmountCalldata'
|
||||
import { calculateGasMargin } from '../../utils/calculateGasMargin'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
@@ -153,11 +153,6 @@ export default function AddLiquidity() {
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false)
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
|
||||
|
||||
// capital efficiency warning
|
||||
const [showCapitalEfficiencyWarning, setShowCapitalEfficiencyWarning] = useState(false)
|
||||
|
||||
useEffect(() => setShowCapitalEfficiencyWarning(false), [baseCurrency, quoteCurrency, feeAmount])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
parsedQs.minPrice &&
|
||||
@@ -805,7 +800,7 @@ export default function AddLiquidity() {
|
||||
disabled={!feeAmount || invalidPool || (noLiquidity && !startPriceTypedValue)}
|
||||
>
|
||||
<StackedContainer>
|
||||
<StackedItem style={{ opacity: showCapitalEfficiencyWarning ? '0.05' : 1 }}>
|
||||
<StackedItem>
|
||||
<AutoColumn gap="md">
|
||||
{noLiquidity && (
|
||||
<RowBetween>
|
||||
@@ -831,65 +826,12 @@ export default function AddLiquidity() {
|
||||
{!noLiquidity && (
|
||||
<PresetsButtons
|
||||
setFullRange={() => {
|
||||
setShowCapitalEfficiencyWarning(true)
|
||||
getSetFullRange()
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</StackedItem>
|
||||
|
||||
{showCapitalEfficiencyWarning && (
|
||||
<StackedItem zIndex={1}>
|
||||
<YellowCard
|
||||
padding="15px"
|
||||
$borderRadius="12px"
|
||||
height="100%"
|
||||
style={{
|
||||
borderColor: theme.deprecated_yellow3,
|
||||
border: '1px solid',
|
||||
}}
|
||||
>
|
||||
<AutoColumn gap="sm" style={{ height: '100%' }}>
|
||||
<RowFixed>
|
||||
<AlertTriangle stroke={theme.deprecated_yellow3} size="16px" />
|
||||
<ThemedText.DeprecatedYellow ml="12px" fontSize="15px">
|
||||
<Trans>Efficiency Comparison</Trans>
|
||||
</ThemedText.DeprecatedYellow>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<ThemedText.DeprecatedYellow ml="12px" fontSize="13px" margin={0} fontWeight={400}>
|
||||
<Trans>
|
||||
Full range positions may earn less fees than concentrated positions. Learn more{' '}
|
||||
<ExternalLink
|
||||
style={{ color: theme.deprecated_yellow3, textDecoration: 'underline' }}
|
||||
href="https://help.uniswap.org/en/articles/5434296-can-i-provide-liquidity-over-the-full-range-in-v3"
|
||||
>
|
||||
here
|
||||
</ExternalLink>
|
||||
.
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedYellow>
|
||||
</RowFixed>
|
||||
<Row>
|
||||
<ButtonYellow
|
||||
padding="8px"
|
||||
marginRight="8px"
|
||||
$borderRadius="8px"
|
||||
width="auto"
|
||||
onClick={() => {
|
||||
setShowCapitalEfficiencyWarning(false)
|
||||
getSetFullRange()
|
||||
}}
|
||||
>
|
||||
<ThemedText.DeprecatedBlack fontSize={13} color="black">
|
||||
<Trans>I understand</Trans>
|
||||
</ThemedText.DeprecatedBlack>
|
||||
</ButtonYellow>
|
||||
</Row>
|
||||
</AutoColumn>
|
||||
</YellowCard>
|
||||
</StackedItem>
|
||||
)}
|
||||
</StackedContainer>
|
||||
|
||||
{outOfRange ? (
|
||||
|
||||
@@ -333,10 +333,14 @@ export default function Swap({ className }: { className?: string }) {
|
||||
[currencyBalances]
|
||||
)
|
||||
const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount))
|
||||
const swapFiatValues = useMemo(() => {
|
||||
return { amountIn: fiatValueTradeInput, amountOut: fiatValueTradeOutput }
|
||||
}, [fiatValueTradeInput, fiatValueTradeOutput])
|
||||
|
||||
// the callback to execute the swap
|
||||
const { callback: swapCallback } = useSwapCallback(
|
||||
trade,
|
||||
swapFiatValues,
|
||||
allowedSlippage,
|
||||
allowance.state === AllowanceState.ALLOWED ? allowance.permitSignature : undefined
|
||||
)
|
||||
|
||||
@@ -17,7 +17,6 @@ import { AppState } from '../types'
|
||||
import {
|
||||
addSerializedPair,
|
||||
addSerializedToken,
|
||||
updateFiatOnrampAcknowledgments,
|
||||
updateHideClosedPositions,
|
||||
updateUserClientSideRouter,
|
||||
updateUserDarkMode,
|
||||
@@ -104,26 +103,6 @@ export function useExpertModeManager(): [boolean, () => void] {
|
||||
return [expertMode, toggleSetExpertMode]
|
||||
}
|
||||
|
||||
interface FiatOnrampAcknowledgements {
|
||||
renderCount: number
|
||||
system: boolean
|
||||
user: boolean
|
||||
}
|
||||
export function useFiatOnrampAck(): [
|
||||
FiatOnrampAcknowledgements,
|
||||
(acknowledgements: Partial<FiatOnrampAcknowledgements>) => void
|
||||
] {
|
||||
const dispatch = useAppDispatch()
|
||||
const fiatOnrampAcknowledgments = useAppSelector((state) => state.user.fiatOnrampAcknowledgments)
|
||||
const setAcknowledgements = useCallback(
|
||||
(acks: Partial<FiatOnrampAcknowledgements>) => {
|
||||
dispatch(updateFiatOnrampAcknowledgments(acks))
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
return [fiatOnrampAcknowledgments, setAcknowledgements]
|
||||
}
|
||||
|
||||
export function useClientSideRouter(): [boolean, (userClientSideRouter: boolean) => void] {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
|
||||
@@ -82,12 +82,6 @@ const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState,
|
||||
reducers: {
|
||||
updateFiatOnrampAcknowledgments(
|
||||
state,
|
||||
{ payload }: { payload: Partial<{ renderCount: number; user: boolean; system: boolean }> }
|
||||
) {
|
||||
state.fiatOnrampAcknowledgments = { ...state.fiatOnrampAcknowledgments, ...payload }
|
||||
},
|
||||
updateSelectedWallet(state, { payload: { wallet } }) {
|
||||
state.selectedWallet = wallet
|
||||
},
|
||||
@@ -181,7 +175,6 @@ const userSlice = createSlice({
|
||||
export const {
|
||||
addSerializedPair,
|
||||
addSerializedToken,
|
||||
updateFiatOnrampAcknowledgments,
|
||||
updateSelectedWallet,
|
||||
updateHideClosedPositions,
|
||||
updateMatchesDarkMode,
|
||||
|
||||
@@ -198,7 +198,7 @@ export const lightTheme: Theme = {
|
||||
accentFailure: colors.red400,
|
||||
accentCritical: colors.red400,
|
||||
|
||||
accentActionSoft: opacify(24, colors.pink400),
|
||||
accentActionSoft: opacify(12, colors.pink400),
|
||||
accentActiveSoft: opacify(24, colors.blue400),
|
||||
accentSuccessSoft: opacify(24, colors.green300),
|
||||
accentWarningSoft: opacify(24, colors.gold200),
|
||||
|
||||
@@ -4989,10 +4989,10 @@
|
||||
"@typescript-eslint/types" "5.47.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@uniswap/analytics-events@^2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.5.1.tgz#26095391518954dd3c1248703b3996f07f034579"
|
||||
integrity sha512-CkatfcWlKZFIQat+/j53AuxhpVntwrgizBYiUgnxcjVQ0Qh87XrCFqgwfTq44QD5bYwgbYjjw263MXujdEqUYg==
|
||||
"@uniswap/analytics-events@^2.6.0":
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.6.0.tgz#c6d21830f46b450f7f633d2c8e832d3531c00f24"
|
||||
integrity sha512-8+El5RKKKs9CXRwRaIZ5t4onlnaTOZThBeO+vSTdr1KtYLp/updtwTR59wE7Yjb5Hj5I6OVKJwiPqAHxja/H4Q==
|
||||
|
||||
"@uniswap/analytics@^1.3.1":
|
||||
version "1.3.1"
|
||||
|
||||
Reference in New Issue
Block a user