ci(release): publish latest release

parent 40c26d35
IPFS hash of the deployment: IPFS hash of the deployment:
- CIDv0: `QmZ5GePYVnqnekAczBnSNAm8msBT3xzqgkJFwukVQZ1hBf` - CIDv0: `QmR4bcDmfwJ5AzVy1ypYxhRHq82RAjo2k44zT1ZupBCo8v`
- CIDv1: `bafybeie7p7swozshq4rt5lc2uzdtauviqpr2xhr5rghmyyuqoiggsgpv5i` - CIDv1: `bafybeibiozbm7r6yfdyfvjzkyxuopbbjhpfakucm2axx265hltuzjl357m`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
...@@ -10,15 +10,66 @@ You can also access the Uniswap Interface from an IPFS gateway. ...@@ -10,15 +10,66 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs. Your Uniswap settings are never remembered across different URLs.
IPFS gateways: IPFS gateways:
- https://bafybeie7p7swozshq4rt5lc2uzdtauviqpr2xhr5rghmyyuqoiggsgpv5i.ipfs.dweb.link/ - https://bafybeibiozbm7r6yfdyfvjzkyxuopbbjhpfakucm2axx265hltuzjl357m.ipfs.dweb.link/
- https://bafybeie7p7swozshq4rt5lc2uzdtauviqpr2xhr5rghmyyuqoiggsgpv5i.ipfs.cf-ipfs.com/ - https://bafybeibiozbm7r6yfdyfvjzkyxuopbbjhpfakucm2axx265hltuzjl357m.ipfs.cf-ipfs.com/
- [ipfs://QmZ5GePYVnqnekAczBnSNAm8msBT3xzqgkJFwukVQZ1hBf/](ipfs://QmZ5GePYVnqnekAczBnSNAm8msBT3xzqgkJFwukVQZ1hBf/) - [ipfs://QmR4bcDmfwJ5AzVy1ypYxhRHq82RAjo2k44zT1ZupBCo8v/](ipfs://QmR4bcDmfwJ5AzVy1ypYxhRHq82RAjo2k44zT1ZupBCo8v/)
### 5.52.1 (2024-10-15) ## 5.53.0 (2024-10-16)
### Features
* **web:** add a popup to claim (#12801) 727178b
* **web:** add bridge toast to web swaps (#12808) 91b3100
* **web:** add chainName to PosDP URL params (#12749) c5caa75
* **web:** bridge status polling (#12930) 1817b76
* **web:** bridging saga (#12753) dd6c181
* **web:** bridging tx status notification (#12932) 6e468ef
* **web:** bridging types (#12748) a473031
* **web:** Give bridge bottom card hover style (#12810) 7be278a
* **web:** improve fingerprinting for swap errors [staging] (#13046) 7cc032c
* **web:** LP create - wrapping native currency step for v2/v3 (#12769) 9c2b4bc
* **web:** pull in completed bridging activity (#12874) 3ac9ca5
* **web:** remove red gas UI on web shared swap (#12947) 8639947
* **web:** remove uniswap extension launch announcement modal (#12791) e1397ce
* **web:** v4 LP improvements (#12737) e111131
* **web:** worldchain bridge banner + minikit provider (#12918) 2439960
* **web:** Zora Explore pages (#12908) 6c0beaf
### Bug Fixes ### Bug Fixes
* **web:** allow pool creation on testnets (#13011) 74160b1 * **web:** 10 15 fix web allow pool creation on testnets staging (#13010) 78815dd
* **web:** Align Continue button text - staging (#13025) 8576cc4
* **web:** align web/mobile quicknode rpcs (#12976) 82adfe5
* **web:** bug with color extraction (#12757) 8fbfa57
* **web:** bump sdk-core for worldchain v2 lp (#12989) 44dd8f3
* **web:** cherrypick bridging analytics [staging] (#13035) 6593430
* **web:** conditionally add dependency array to useAnimatedScrollHandler (#12852) e523bfd
* **web:** correct currency logos in LP Create modal (#12751) a350498
* **web:** create v4 ui (#12638) 1c52b4e
* **web:** default to eth mainnet on landing page (#12958) e81c5ad
* **web:** display bridging options in unconnected state [staging] (#13047) 5a17a2f
* **web:** enable token swap on non mainnet tdp for legacy swap (#12899) a214ab2
* **web:** fix broken worldchain images (#13033) e31db30
* **web:** fix range formatting on my positions page (#12754) 8b13723
* **web:** hide astrochain usdc (#12961) 40ee988
* **web:** limits issues (allow cancelling insufficient funds) (#12912) f630284
* **web:** log missing swap analytics (#12967) 4c22de5
* **web:** log SWAP_SIGNED from uniswapx saga [main] (#12907) 39ccb30
* **web:** memoize connected chain ids (#12922) 3ed664d
* **web:** pass account to getSigner instead of using default (#12885) f508f20
* **web:** remove FOR feature flag and moonpay flow (#12547) b79e301
* **web:** Switch chains before cancelling limits (#12752) 8d2c4f1
* **web:** tapi key (#12866) bcc0529
* **web:** track wallet connect external provider (#12360) ff2c6e7
* **web:** update the toast for increase + decrease liquidity (#12786) c7c898e
* **web:** worldchain rpc (#12946) b1b7528
### Continuous Integration
* **web:** break down web testing jobs to speed up CI (#12766) cb3c56f
* **web:** update sitemaps 336fb63
web/5.52.1 web/5.53.0
\ No newline at end of file \ No newline at end of file
...@@ -34,6 +34,7 @@ import { initExtensionAnalytics } from 'src/app/utils/analytics' ...@@ -34,6 +34,7 @@ import { initExtensionAnalytics } from 'src/app/utils/analytics'
import { getLocalUserId } from 'src/app/utils/storage' import { getLocalUserId } from 'src/app/utils/storage'
import { import {
DappBackgroundPortChannel, DappBackgroundPortChannel,
backgroundToSidePanelMessageChannel,
createBackgroundToSidePanelMessagePort, createBackgroundToSidePanelMessagePort,
} from 'src/background/messagePassing/messageChannels' } from 'src/background/messagePassing/messageChannels'
import { BackgroundToSidePanelRequestType } from 'src/background/messagePassing/types/requests' import { BackgroundToSidePanelRequestType } from 'src/background/messagePassing/types/requests'
...@@ -44,7 +45,7 @@ import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice' ...@@ -44,7 +45,7 @@ import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context' import { UnitagUpdaterContextProvider, useUnitagUpdater } from 'uniswap/src/features/unitags/context'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n/i18n'
import { isDevEnv } from 'utilities/src/environment/env' import { isDevEnv } from 'utilities/src/environment/env'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -205,10 +206,21 @@ function SidebarWrapper(): JSX.Element { ...@@ -205,10 +206,21 @@ function SidebarWrapper(): JSX.Element {
useDappRequestPortListener() useDappRequestPortListener()
useTestnetModeForLoggingAndAnalytics() useTestnetModeForLoggingAndAnalytics()
const { triggerRefetchUnitags } = useUnitagUpdater()
useEffect(() => { useEffect(() => {
dispatch(syncAppWithDeviceLanguage()) dispatch(syncAppWithDeviceLanguage())
}, [dispatch]) }, [dispatch])
useEffect(() => {
return backgroundToSidePanelMessageChannel.addMessageListener(
BackgroundToSidePanelRequestType.RefreshUnitags,
() => {
triggerRefetchUnitags()
},
)
}, [triggerRefetchUnitags])
return ( return (
<> <>
<WebNavigation /> <WebNavigation />
......
import { render } from '@testing-library/react'
import UnitagClaimApp from 'src/app/UnitagClaimApp'
import { initializeReduxStore } from 'src/store/store'
describe('UnitagClaimApp', () => {
it('renders without error', async () => {
await initializeReduxStore()
render(<UnitagClaimApp />)
})
})
...@@ -10,10 +10,13 @@ import { GraphqlProvider } from 'src/app/apollo' ...@@ -10,10 +10,13 @@ import { GraphqlProvider } from 'src/app/apollo'
import { ErrorElement } from 'src/app/components/ErrorElement' import { ErrorElement } from 'src/app/components/ErrorElement'
import { TraceUserProperties } from 'src/app/components/Trace/TraceUserProperties' import { TraceUserProperties } from 'src/app/components/Trace/TraceUserProperties'
import { ClaimUnitagSteps, OnboardingStepsProvider } from 'src/app/features/onboarding/OnboardingSteps' import { ClaimUnitagSteps, OnboardingStepsProvider } from 'src/app/features/onboarding/OnboardingSteps'
import { EditUnitagProfileScreen } from 'src/app/features/unitags/EditUnitagProfileScreen'
import { UnitagChooseProfilePicScreen } from 'src/app/features/unitags/UnitagChooseProfilePicScreen' import { UnitagChooseProfilePicScreen } from 'src/app/features/unitags/UnitagChooseProfilePicScreen'
import { UnitagClaimContextProvider } from 'src/app/features/unitags/UnitagClaimContext' import { UnitagClaimContextProvider } from 'src/app/features/unitags/UnitagClaimContext'
import { UnitagConfirmationScreen } from 'src/app/features/unitags/UnitagConfirmationScreen'
import { UnitagCreateUsernameScreen } from 'src/app/features/unitags/UnitagCreateUsernameScreen' import { UnitagCreateUsernameScreen } from 'src/app/features/unitags/UnitagCreateUsernameScreen'
import { UnitagIntroScreen } from 'src/app/features/unitags/UnitagIntroScreen' import { UnitagIntroScreen } from 'src/app/features/unitags/UnitagIntroScreen'
import { OnboardingRoutes } from 'src/app/navigation/constants'
import { setRouter, setRouterState } from 'src/app/navigation/state' import { setRouter, setRouterState } from 'src/app/navigation/state'
import { SentryAppNameTag, initializeSentry, sentryCreateHashRouter } from 'src/app/sentry' import { SentryAppNameTag, initializeSentry, sentryCreateHashRouter } from 'src/app/sentry'
import { initExtensionAnalytics } from 'src/app/utils/analytics' import { initExtensionAnalytics } from 'src/app/utils/analytics'
...@@ -45,6 +48,11 @@ const router = sentryCreateHashRouter([ ...@@ -45,6 +48,11 @@ const router = sentryCreateHashRouter([
element: <UnitagClaimAppInner />, element: <UnitagClaimAppInner />,
errorElement: <ErrorElement />, errorElement: <ErrorElement />,
}, },
{
path: OnboardingRoutes.EditProfile,
element: <EditProfileAppInner />,
errorElement: <ErrorElement />,
},
]) ])
/** /**
...@@ -61,13 +69,30 @@ setRouter(router) ...@@ -61,13 +69,30 @@ setRouter(router)
function UnitagClaimAppInner(): JSX.Element { function UnitagClaimAppInner(): JSX.Element {
useTestnetModeForLoggingAndAnalytics() useTestnetModeForLoggingAndAnalytics()
return ( return (
<Flex alignItems="center" justifyContent="center" minHeight="100vh" width="100%"> <Flex centered height="100vh" width="100%">
<OnboardingStepsProvider <OnboardingStepsProvider
disableRedirect disableRedirect
steps={{ steps={{
[ClaimUnitagSteps.Intro]: <UnitagIntroScreen />, [ClaimUnitagSteps.Intro]: <UnitagIntroScreen />,
[ClaimUnitagSteps.CreateUsername]: <UnitagCreateUsernameScreen />, [ClaimUnitagSteps.CreateUsername]: <UnitagCreateUsernameScreen />,
[ClaimUnitagSteps.ChooseProfilePic]: <UnitagChooseProfilePicScreen />, [ClaimUnitagSteps.ChooseProfilePic]: <UnitagChooseProfilePicScreen />,
[ClaimUnitagSteps.Confirmation]: <UnitagConfirmationScreen />,
[ClaimUnitagSteps.EditProfile]: <EditUnitagProfileScreen enableBack />,
}}
ContainerComponent={UnitagClaimContextProvider}
/>
<Outlet />
</Flex>
)
}
function EditProfileAppInner(): JSX.Element {
return (
<Flex centered>
<OnboardingStepsProvider
disableRedirect
steps={{
[ClaimUnitagSteps.Intro]: <EditUnitagProfileScreen />,
}} }}
ContainerComponent={UnitagClaimContextProvider} ContainerComponent={UnitagClaimContextProvider}
/> />
......
...@@ -10,13 +10,13 @@ import { iconSizes } from 'ui/src/theme' ...@@ -10,13 +10,13 @@ import { iconSizes } from 'ui/src/theme'
import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal' import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal'
import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types'
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'uniswap/src/features/notifications/types'
import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { setClipboard } from 'uniswap/src/utils/clipboard' import { setClipboard } from 'uniswap/src/utils/clipboard'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'wallet/src/features/notifications/types'
import { EditAccountAction, editAccountActions } from 'wallet/src/features/wallet/accounts/editAccountSaga' import { EditAccountAction, editAccountActions } from 'wallet/src/features/wallet/accounts/editAccountSaga'
import { useActiveAccountWithThrow, useDisplayName, useSignerAccounts } from 'wallet/src/features/wallet/hooks' import { useActiveAccountWithThrow, useDisplayName, useSignerAccounts } from 'wallet/src/features/wallet/hooks'
import { DisplayNameType } from 'wallet/src/features/wallet/types' import { DisplayNameType } from 'wallet/src/features/wallet/types'
......
...@@ -11,14 +11,17 @@ import { updateDappConnectedAddressFromExtension } from 'src/app/features/dapp/a ...@@ -11,14 +11,17 @@ import { updateDappConnectedAddressFromExtension } from 'src/app/features/dapp/a
import { useDappConnectedAccounts } from 'src/app/features/dapp/hooks' import { useDappConnectedAccounts } from 'src/app/features/dapp/hooks'
import { isConnectedAccount } from 'src/app/features/dapp/utils' import { isConnectedAccount } from 'src/app/features/dapp/utils'
import { PopupName, openPopup } from 'src/app/features/popups/slice' import { PopupName, openPopup } from 'src/app/features/popups/slice'
import { AppRoutes, RemoveRecoveryPhraseRoutes, SettingsRoutes } from 'src/app/navigation/constants' import { AppRoutes, OnboardingRoutes, RemoveRecoveryPhraseRoutes, SettingsRoutes } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state' import { navigate } from 'src/app/navigation/state'
import { focusOrCreateUnitagTab } from 'src/app/navigation/utils'
import { Button, Flex, MenuContent, MenuContentItem, Popover, ScrollView, Text, useSporeColors } from 'ui/src' import { Button, Flex, MenuContent, MenuContentItem, Popover, ScrollView, Text, useSporeColors } from 'ui/src'
import { WalletFilled, X } from 'ui/src/components/icons' import { WalletFilled, X } from 'ui/src/components/icons'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal' import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal'
import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types'
import { AccountType } from 'uniswap/src/features/accounts/types' import { AccountType } from 'uniswap/src/features/accounts/types'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
...@@ -281,6 +284,20 @@ export function AccountSwitcherScreen(): JSX.Element { ...@@ -281,6 +284,20 @@ export function AccountSwitcherScreen(): JSX.Element {
const UnitagActionButton = (): JSX.Element => { const UnitagActionButton = (): JSX.Element => {
const { t } = useTranslation() const { t } = useTranslation()
const isClaimUnitagEnabled = useFeatureFlag(FeatureFlags.ExtensionClaimUnitag)
const onPressEditProfile = useCallback(async () => {
await focusOrCreateUnitagTab(OnboardingRoutes.EditProfile)
}, [])
if (isClaimUnitagEnabled) {
return (
<Button color="$neutral1" size="small" testID={TestID.AccountCard} theme="tertiary" onPress={onPressEditProfile}>
{t('account.wallet.header.button.disabled.title')}
</Button>
)
}
return ( return (
<ComingSoon placement="top"> <ComingSoon placement="top">
<Button color="$neutral2" disabled={true} size="small" testID={TestID.AccountCard} theme="secondary"> <Button color="$neutral2" disabled={true} size="small" testID={TestID.AccountCard} theme="secondary">
......
...@@ -99,7 +99,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` ...@@ -99,7 +99,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
<circle <circle
cx="28" cx="28"
cy="28" cy="28"
fill="#4300B01F" fill="#FFBF171F"
r="28" r="28"
/> />
<g <g
...@@ -107,8 +107,8 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` ...@@ -107,8 +107,8 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
> >
<path <path
clip-rule="evenodd" clip-rule="evenodd"
d="M13.2 6C9.22355 6 6 9.22355 6 13.2V34.8C6 38.7765 9.22355 42 13.2 42H34.8C38.7765 42 42 38.7765 42 34.8V13.2C42 9.22355 38.7765 6 34.8 6H13.2ZM26.1213 14.1213C24.9497 12.9497 23.0503 12.9497 21.8787 14.1213L14.1213 21.8787C12.9497 23.0503 12.9497 24.9497 14.1213 26.1213L21.8787 33.8787C23.0503 35.0503 24.9497 35.0503 26.1213 33.8787L33.8787 26.1213C35.0503 24.9497 35.0503 23.0503 33.8787 21.8787L26.1213 14.1213Z" d="M28.541 24C28.541 23.6075 28.7998 23.264 29.1711 23.1367C35.8814 20.8368 40.9702 14.1754 41.9802 5.99363C42.1832 4.34926 40.8202 3 39.1634 3H8.8369C7.18005 3 5.81706 4.34926 6.02011 5.99363C7.03026 14.1742 12.1192 20.8349 18.8286 23.1357C19.2002 23.2632 19.4593 23.6071 19.4593 24C19.4593 24.3929 19.2002 24.7368 18.8286 24.8643C12.1192 27.1651 7.03026 33.8258 6.02011 42.0064C5.81706 43.6507 7.18005 45 8.8369 45H39.1634C40.8202 45 42.1832 43.6507 41.9802 42.0064C40.9702 33.8246 35.8814 27.1632 29.1711 24.8633C28.7998 24.736 28.541 24.3925 28.541 24Z"
fill="#4300B0" fill="#FFBF17"
fill-rule="evenodd" fill-rule="evenodd"
/> />
</g> </g>
...@@ -127,9 +127,9 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` ...@@ -127,9 +127,9 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
<span <span
class="font_heading _display-inline _boxSizing-border-box _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-843134974 _wordWrap-break-word _fontFamily-299667014 _fontSize-18px _lineHeight-24px _fontWeight-233016202 _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _textAlign-center _flexShrink-1" class="font_heading _display-inline _boxSizing-border-box _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-843134974 _wordWrap-break-word _fontFamily-299667014 _fontSize-18px _lineHeight-24px _fontWeight-233016202 _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _textAlign-center _flexShrink-1"
data-disable-theme="true" data-disable-theme="true"
data-testid="address-display/name/Jacob Haley" data-testid="address-display/name/Tamara Brekke"
> >
Jacob Haley Tamara Brekke
</span> </span>
</div> </div>
</div> </div>
...@@ -144,7 +144,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` ...@@ -144,7 +144,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
class="font_body _display-inline _boxSizing-border-box _whiteSpace-pre-wrap _mt-0px _mr-0px _mb-0px _ml-0px _color-843135005 _fontFamily-299667014 _wordWrap-break-word _fontSize-229441158 _lineHeight-222976511 _fontWeight-233016202" class="font_body _display-inline _boxSizing-border-box _whiteSpace-pre-wrap _mt-0px _mr-0px _mb-0px _ml-0px _color-843135005 _fontFamily-299667014 _wordWrap-break-word _fontSize-229441158 _lineHeight-222976511 _fontWeight-233016202"
data-disable-theme="true" data-disable-theme="true"
> >
0x​0fc6...be59 0x​e0c6...ea11
</span> </span>
<svg <svg
fill="none" fill="none"
...@@ -325,7 +325,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` ...@@ -325,7 +325,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
<circle <circle
cx="28" cx="28"
cy="28" cy="28"
fill="#4300B01F" fill="#FFBF171F"
r="28" r="28"
/> />
<g <g
...@@ -333,8 +333,8 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` ...@@ -333,8 +333,8 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
> >
<path <path
clip-rule="evenodd" clip-rule="evenodd"
d="M13.2 6C9.22355 6 6 9.22355 6 13.2V34.8C6 38.7765 9.22355 42 13.2 42H34.8C38.7765 42 42 38.7765 42 34.8V13.2C42 9.22355 38.7765 6 34.8 6H13.2ZM26.1213 14.1213C24.9497 12.9497 23.0503 12.9497 21.8787 14.1213L14.1213 21.8787C12.9497 23.0503 12.9497 24.9497 14.1213 26.1213L21.8787 33.8787C23.0503 35.0503 24.9497 35.0503 26.1213 33.8787L33.8787 26.1213C35.0503 24.9497 35.0503 23.0503 33.8787 21.8787L26.1213 14.1213Z" d="M28.541 24C28.541 23.6075 28.7998 23.264 29.1711 23.1367C35.8814 20.8368 40.9702 14.1754 41.9802 5.99363C42.1832 4.34926 40.8202 3 39.1634 3H8.8369C7.18005 3 5.81706 4.34926 6.02011 5.99363C7.03026 14.1742 12.1192 20.8349 18.8286 23.1357C19.2002 23.2632 19.4593 23.6071 19.4593 24C19.4593 24.3929 19.2002 24.7368 18.8286 24.8643C12.1192 27.1651 7.03026 33.8258 6.02011 42.0064C5.81706 43.6507 7.18005 45 8.8369 45H39.1634C40.8202 45 42.1832 43.6507 41.9802 42.0064C40.9702 33.8246 35.8814 27.1632 29.1711 24.8633C28.7998 24.736 28.541 24.3925 28.541 24Z"
fill="#4300B0" fill="#FFBF17"
fill-rule="evenodd" fill-rule="evenodd"
/> />
</g> </g>
...@@ -353,9 +353,9 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` ...@@ -353,9 +353,9 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
<span <span
class="font_heading _display-inline _boxSizing-border-box _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-843134974 _wordWrap-break-word _fontFamily-299667014 _fontSize-18px _lineHeight-24px _fontWeight-233016202 _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _textAlign-center _flexShrink-1" class="font_heading _display-inline _boxSizing-border-box _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-843134974 _wordWrap-break-word _fontFamily-299667014 _fontSize-18px _lineHeight-24px _fontWeight-233016202 _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _textAlign-center _flexShrink-1"
data-disable-theme="true" data-disable-theme="true"
data-testid="address-display/name/Jacob Haley" data-testid="address-display/name/Tamara Brekke"
> >
Jacob Haley Tamara Brekke
</span> </span>
</div> </div>
</div> </div>
...@@ -370,7 +370,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` ...@@ -370,7 +370,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
class="font_body _display-inline _boxSizing-border-box _whiteSpace-pre-wrap _mt-0px _mr-0px _mb-0px _ml-0px _color-843135005 _fontFamily-299667014 _wordWrap-break-word _fontSize-229441158 _lineHeight-222976511 _fontWeight-233016202" class="font_body _display-inline _boxSizing-border-box _whiteSpace-pre-wrap _mt-0px _mr-0px _mb-0px _ml-0px _color-843135005 _fontFamily-299667014 _wordWrap-break-word _fontSize-229441158 _lineHeight-222976511 _fontWeight-233016202"
data-disable-theme="true" data-disable-theme="true"
> >
0x​0fc6...be59 0x​e0c6...ea11
</span> </span>
<svg <svg
fill="none" fill="none"
......
...@@ -5,7 +5,6 @@ import { useDappRequestQueueContext } from 'src/app/features/dappRequests/DappRe ...@@ -5,7 +5,6 @@ import { useDappRequestQueueContext } from 'src/app/features/dappRequests/DappRe
import { DappRequestStoreItem } from 'src/app/features/dappRequests/slice' import { DappRequestStoreItem } from 'src/app/features/dappRequests/slice'
import { Anchor, AnimatePresence, Button, Flex, Text, UniversalImage, UniversalImageResizeMode, styled } from 'ui/src' import { Anchor, AnimatePresence, Button, Flex, Text, UniversalImage, UniversalImageResizeMode, styled } from 'ui/src'
import { borderRadii, iconSizes } from 'ui/src/theme' import { borderRadii, iconSizes } from 'ui/src/theme'
import { useUSDValueOfGasFee } from 'uniswap/src/features/gas/hooks'
import { GasFeeResult } from 'uniswap/src/features/gas/types' import { GasFeeResult } from 'uniswap/src/features/gas/types'
import { hasSufficientFundsIncludingGas } from 'uniswap/src/features/gas/utils' import { hasSufficientFundsIncludingGas } from 'uniswap/src/features/gas/utils'
import { useOnChainNativeCurrencyBalance } from 'uniswap/src/features/portfolio/api' import { useOnChainNativeCurrencyBalance } from 'uniswap/src/features/portfolio/api'
...@@ -175,7 +174,6 @@ export function DappRequestFooter({ ...@@ -175,7 +174,6 @@ export function DappRequestFooter({
} }
const currentChainId = chainId || activeChain || defaultChainId const currentChainId = chainId || activeChain || defaultChainId
const { value: gasFeeUSD } = useUSDValueOfGasFee(currentChainId, transactionGasFeeResult?.value)
const { balance: nativeBalance } = useOnChainNativeCurrencyBalance(currentChainId, currentAccount.address) const { balance: nativeBalance } = useOnChainNativeCurrencyBalance(currentChainId, currentAccount.address)
const hasSufficientGas = hasSufficientFundsIncludingGas({ const hasSufficientGas = hasSufficientFundsIncludingGas({
...@@ -184,7 +182,9 @@ export function DappRequestFooter({ ...@@ -184,7 +182,9 @@ export function DappRequestFooter({
}) })
const shouldCloseSidebar = request.isSidebarClosed && totalRequestCount <= 1 const shouldCloseSidebar = request.isSidebarClosed && totalRequestCount <= 1
const isConfirmDisabled = transactionGasFeeResult ? !gasFeeUSD || !hasSufficientGas : false
// Disable submission if no gas fee value
const isConfirmEnabled = transactionGasFeeResult?.value && hasSufficientGas
const handleOnConfirm = useCallback(async () => { const handleOnConfirm = useCallback(async () => {
if (onConfirm) { if (onConfirm) {
...@@ -240,7 +240,7 @@ export function DappRequestFooter({ ...@@ -240,7 +240,7 @@ export function DappRequestFooter({
{t('common.button.cancel')} {t('common.button.cancel')}
</Button> </Button>
<Button <Button
disabled={isConfirmDisabled} disabled={!isConfirmEnabled}
flex={1} flex={1}
flexBasis={1} flexBasis={1}
size="medium" size="medium"
......
...@@ -17,12 +17,12 @@ import { extractBaseUrl } from 'src/app/features/dappRequests/utils' ...@@ -17,12 +17,12 @@ import { extractBaseUrl } from 'src/app/features/dappRequests/utils'
import { dappResponseMessageChannel } from 'src/background/messagePassing/messageChannels' import { dappResponseMessageChannel } from 'src/background/messagePassing/messageChannels'
import { call, put, select } from 'typed-redux-saga' import { call, put, select } from 'typed-redux-saga'
import { chainIdToHexadecimalString } from 'uniswap/src/features/chains/utils' import { chainIdToHexadecimalString } from 'uniswap/src/features/chains/utils'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { getEnabledChainIdsSaga } from 'uniswap/src/features/settings/saga' import { getEnabledChainIdsSaga } from 'uniswap/src/features/settings/saga'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { UniverseChainId } from 'uniswap/src/types/chains' import { UniverseChainId } from 'uniswap/src/types/chains'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { getProvider } from 'wallet/src/features/wallet/context' import { getProvider } from 'wallet/src/features/wallet/context'
import { selectActiveAccount } from 'wallet/src/features/wallet/selectors' import { selectActiveAccount } from 'wallet/src/features/wallet/selectors'
......
...@@ -21,8 +21,8 @@ import { Permission } from 'src/contentScript/WindowEthereumRequestTypes' ...@@ -21,8 +21,8 @@ import { Permission } from 'src/contentScript/WindowEthereumRequestTypes'
import { ExtensionEthMethods } from 'src/contentScript/methodHandlers/requestMethods' import { ExtensionEthMethods } from 'src/contentScript/methodHandlers/requestMethods'
import { call, put } from 'typed-redux-saga' import { call, put } from 'typed-redux-saga'
import { chainIdToHexadecimalString } from 'uniswap/src/features/chains/utils' import { chainIdToHexadecimalString } from 'uniswap/src/features/chains/utils'
import { pushNotification } from 'wallet/src/features/notifications/slice' import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types' import { AppNotificationType } from 'uniswap/src/features/notifications/types'
export function getPermissions(dappUrl: string | undefined, connectedAddresses: Address[] | undefined): Permission[] { export function getPermissions(dappUrl: string | undefined, connectedAddresses: Address[] | undefined): Permission[] {
const permissions: Permission[] = [] const permissions: Permission[] = []
......
...@@ -9,9 +9,9 @@ import { useCopyToClipboard } from 'src/app/hooks/useOnCopyToClipboard' ...@@ -9,9 +9,9 @@ import { useCopyToClipboard } from 'src/app/hooks/useOnCopyToClipboard'
import { Anchor, Flex, Text, TouchableArea } from 'ui/src' import { Anchor, Flex, Text, TouchableArea } from 'ui/src'
import { AnimatedCopySheets, ExternalLink } from 'ui/src/components/icons' import { AnimatedCopySheets, ExternalLink } from 'ui/src/components/icons'
import { GasFeeResult } from 'uniswap/src/features/gas/types' import { GasFeeResult } from 'uniswap/src/features/gas/types'
import { CopyNotificationType } from 'uniswap/src/features/notifications/types'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
import { ellipseMiddle, shortenAddress } from 'utilities/src/addresses' import { ellipseMiddle, shortenAddress } from 'utilities/src/addresses'
import { CopyNotificationType } from 'wallet/src/features/notifications/types'
import { ContentRow } from 'wallet/src/features/transactions/TransactionRequest/ContentRow' import { ContentRow } from 'wallet/src/features/transactions/TransactionRequest/ContentRow'
import { import {
SpendingDetails, SpendingDetails,
......
...@@ -6,6 +6,7 @@ import { formatUnits as formatUnitsEthers } from 'ethers/lib/utils' ...@@ -6,6 +6,7 @@ import { formatUnits as formatUnitsEthers } from 'ethers/lib/utils'
import { useDappLastChainId } from 'src/app/features/dapp/hooks' import { useDappLastChainId } from 'src/app/features/dapp/hooks'
import { import {
CONTRACT_BALANCE, CONTRACT_BALANCE,
ETH_ADDRESS,
MAX_UINT160, MAX_UINT160,
MAX_UINT256, MAX_UINT256,
} from 'src/app/features/dappRequests/requestContent/EthSend/Swap/constants' } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/constants'
...@@ -24,10 +25,14 @@ import { ...@@ -24,10 +25,14 @@ import {
V4SwapExactOutSingleParamSchema, V4SwapExactOutSingleParamSchema,
isAmountInMaxParam, isAmountInMaxParam,
isAmountInParam, isAmountInParam,
isAmountMinParam,
isAmountOutMinParam, isAmountOutMinParam,
isAmountOutParam, isAmountOutParam,
isURCommandASwap, isURCommandASwap,
isUrCommandSweep,
isUrCommandUnwrapWeth,
} from 'src/app/features/dappRequests/types/UniversalRouterTypes' } from 'src/app/features/dappRequests/types/UniversalRouterTypes'
import { DEFAULT_NATIVE_ADDRESS } from 'uniswap/src/constants/chains'
import { buildCurrencyId } from 'uniswap/src/utils/currencyId' import { buildCurrencyId } from 'uniswap/src/utils/currencyId'
import { assert } from 'utilities/src/errors' import { assert } from 'utilities/src/errors'
...@@ -114,8 +119,8 @@ export function useSwapDetails( ...@@ -114,8 +119,8 @@ export function useSwapDetails(
if (v4Command) { if (v4Command) {
// Extract details using the V4 helper // Extract details using the V4 helper
const v4Details = getTokenDetailsFromV4SwapCommands(v4Command) const v4Details = getTokenDetailsFromV4SwapCommands(v4Command)
inputAddress = v4Details.inputAddress inputAddress = v4Details.inputAddress === ETH_ADDRESS ? DEFAULT_NATIVE_ADDRESS : v4Details.inputAddress
outputAddress = v4Details.outputAddress outputAddress = v4Details.outputAddress === ETH_ADDRESS ? DEFAULT_NATIVE_ADDRESS : v4Details.outputAddress
inputValue = v4Details.inputValue || '0' inputValue = v4Details.inputValue || '0'
outputValue = v4Details.outputValue || '0' outputValue = v4Details.outputValue || '0'
} else { } else {
...@@ -157,12 +162,28 @@ function extractTokenAddresses(commands: UniversalRouterCommand[]): { ...@@ -157,12 +162,28 @@ function extractTokenAddresses(commands: UniversalRouterCommand[]): {
return { inputAddress, outputAddress } return { inputAddress, outputAddress }
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isZeroBigNumber(bigNumberObj: any): boolean {
// The true type of bigNumberObj is { type: string; hex: string } but param.value is any type
try {
if (!bigNumberObj) {
return true
}
const bigNumber = BigNumber.from(bigNumberObj.hex)
return bigNumber.isZero()
} catch (error) {
return true // Treat as zero if there's any error
}
}
function getTokenAmounts(commands: UniversalRouterCommand[]): { function getTokenAmounts(commands: UniversalRouterCommand[]): {
inputValue: string inputValue: string
outputValue: string outputValue: string
} { } {
const firstSwapCommand = commands.find(isURCommandASwap) const firstSwapCommand = commands.find(isURCommandASwap)
const lastSwapCommand = commands.findLast(isURCommandASwap) const lastSwapCommand = commands.findLast(isURCommandASwap)
const sweepCommand = commands.find(isUrCommandSweep)
const unwrapWethCommand = commands.find(isUrCommandUnwrapWeth)
assert( assert(
firstSwapCommand && lastSwapCommand, firstSwapCommand && lastSwapCommand,
...@@ -171,15 +192,24 @@ function getTokenAmounts(commands: UniversalRouterCommand[]): { ...@@ -171,15 +192,24 @@ function getTokenAmounts(commands: UniversalRouterCommand[]): {
const firstAmountInParam = firstSwapCommand?.params.find(isAmountInOrMaxParam) const firstAmountInParam = firstSwapCommand?.params.find(isAmountInOrMaxParam)
const lastAmountOutParam = lastSwapCommand?.params.find(isAmountOutMinOrOutParam) const lastAmountOutParam = lastSwapCommand?.params.find(isAmountOutMinOrOutParam)
const sweepAmountOutParam = sweepCommand?.params.find(isAmountMinParam)
const unwrapWethAmountOutParam = unwrapWethCommand?.params.find(isAmountMinParam)
assert( assert(
firstAmountInParam && lastAmountOutParam, firstAmountInParam && lastAmountOutParam,
'SwapRequestContent: All swaps must have a defined input and output amount parameter.', 'SwapRequestContent: All swaps must have a defined input and output amount parameter.',
) )
// There's a special case where V3_SWAP command's amountOutMin param is zero (0x00... some gas optimization slippage thing)
// In this case fallback to the amountMin from the SWEEP or UNWRAP_WETH command as the outputValue
const inputValue = firstAmountInParam?.value
const fallbackOutputValue = sweepAmountOutParam?.value || unwrapWethAmountOutParam?.value
const outputValue =
fallbackOutputValue && isZeroBigNumber(lastAmountOutParam?.value) ? fallbackOutputValue : lastAmountOutParam?.value
return { return {
inputValue: firstAmountInParam?.value || '0', // Safe due to assert inputValue: inputValue || '0', // Safe due to assert
outputValue: lastAmountOutParam?.value || '0', // Safe due to assert outputValue: outputValue || '0', // Safe due to assert
} }
} }
......
...@@ -28,14 +28,14 @@ import { navigate } from 'src/app/navigation/state' ...@@ -28,14 +28,14 @@ import { navigate } from 'src/app/navigation/state'
import { dappResponseMessageChannel } from 'src/background/messagePassing/messageChannels' import { dappResponseMessageChannel } from 'src/background/messagePassing/messageChannels'
import { call, put, select, take } from 'typed-redux-saga' import { call, put, select, take } from 'typed-redux-saga'
import { hexadecimalStringToInt, toSupportedChainId } from 'uniswap/src/features/chains/utils' import { hexadecimalStringToInt, toSupportedChainId } from 'uniswap/src/features/chains/utils'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { import {
TransactionOriginType, TransactionOriginType,
TransactionType, TransactionType,
TransactionTypeInfo, TransactionTypeInfo,
} from 'uniswap/src/features/transactions/types/transactionDetails' } from 'uniswap/src/features/transactions/types/transactionDetails'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { SendTransactionParams, sendTransaction } from 'wallet/src/features/transactions/sendTransactionSaga' import { SendTransactionParams, sendTransaction } from 'wallet/src/features/transactions/sendTransactionSaga'
import { getProvider, getSignerManager } from 'wallet/src/features/wallet/context' import { getProvider, getSignerManager } from 'wallet/src/features/wallet/context'
import { selectActiveAccount } from 'wallet/src/features/wallet/selectors' import { selectActiveAccount } from 'wallet/src/features/wallet/selectors'
......
...@@ -41,6 +41,13 @@ const AmountOutParamSchema = z.object({ ...@@ -41,6 +41,13 @@ const AmountOutParamSchema = z.object({
}) })
export type AmountOutParam = z.infer<typeof AmountOutParamSchema> export type AmountOutParam = z.infer<typeof AmountOutParamSchema>
const AmountMinParamSchema = z.object({
name: z.literal('amountMin'),
value: BigNumberSchema,
})
export type AmountMinParam = z.infer<typeof AmountMinParamSchema>
const FeeAmountSchema = z.nativeEnum(FeeAmountV3) const FeeAmountSchema = z.nativeEnum(FeeAmountV3)
export type FeeAmount = z.infer<typeof FeeAmountSchema> export type FeeAmount = z.infer<typeof FeeAmountSchema>
...@@ -188,6 +195,7 @@ export const ParamSchema = z.union([ ...@@ -188,6 +195,7 @@ export const ParamSchema = z.union([
AmountInMaxParamSchema, AmountInMaxParamSchema,
AmountOutParamSchema, AmountOutParamSchema,
AmountOutMinParamSchema, AmountOutMinParamSchema,
AmountMinParamSchema,
V3PathParamSchema, V3PathParamSchema,
PayerIsUserParamSchema, PayerIsUserParamSchema,
FallbackParamSchema, FallbackParamSchema,
...@@ -229,6 +237,21 @@ const V3SwapExactOutCommandSchema = z.object({ ...@@ -229,6 +237,21 @@ const V3SwapExactOutCommandSchema = z.object({
}) })
export type V3SwapExactOutCommand = z.infer<typeof V3SwapExactOutCommandSchema> export type V3SwapExactOutCommand = z.infer<typeof V3SwapExactOutCommandSchema>
const SweepCommandSchema = z.object({
commandName: z.literal('SWEEP'),
commandType: z.literal(CommandType.SWEEP),
params: z.array(ParamSchema),
})
export type SweepCommand = z.infer<typeof SweepCommandSchema>
const UnwrapWethCommandSchema = z.object({
commandName: z.literal('UNWRAP_WETH'),
commandType: z.literal(CommandType.UNWRAP_WETH),
params: z.array(ParamSchema),
})
export type UnwrapWethCommand = z.infer<typeof UnwrapWethCommandSchema>
const V4SwapCommandSchema = z.object({ const V4SwapCommandSchema = z.object({
commandName: z.literal('V4_SWAP'), commandName: z.literal('V4_SWAP'),
commandType: z.literal(CommandType.V4_SWAP), commandType: z.literal(CommandType.V4_SWAP),
...@@ -246,7 +269,7 @@ export const UniversalRouterSwapCommandSchema = z.union([ ...@@ -246,7 +269,7 @@ export const UniversalRouterSwapCommandSchema = z.union([
V2SwapExactOutCommandSchema, V2SwapExactOutCommandSchema,
V3SwapExactInCommandSchema, V3SwapExactInCommandSchema,
V3SwapExactOutCommandSchema, V3SwapExactOutCommandSchema,
V4SwapCommandSchema V4SwapCommandSchema,
]) ])
export type UniversalRouterSwapCommand = z.infer<typeof UniversalRouterSwapCommandSchema> export type UniversalRouterSwapCommand = z.infer<typeof UniversalRouterSwapCommandSchema>
...@@ -256,7 +279,9 @@ const UniversalRouterCommandSchema = z.union([ ...@@ -256,7 +279,9 @@ const UniversalRouterCommandSchema = z.union([
V2SwapExactOutCommandSchema, V2SwapExactOutCommandSchema,
V3SwapExactInCommandSchema, V3SwapExactInCommandSchema,
V3SwapExactOutCommandSchema, V3SwapExactOutCommandSchema,
V4SwapCommandSchema V4SwapCommandSchema,
SweepCommandSchema,
UnwrapWethCommandSchema
]) ])
export type UniversalRouterCommand = z.infer<typeof UniversalRouterCommandSchema> export type UniversalRouterCommand = z.infer<typeof UniversalRouterCommandSchema>
...@@ -272,6 +297,14 @@ export function isURCommandASwap( ...@@ -272,6 +297,14 @@ export function isURCommandASwap(
return UniversalRouterSwapCommandSchema.safeParse(command).success return UniversalRouterSwapCommandSchema.safeParse(command).success
} }
export function isUrCommandSweep(command: UniversalRouterCommand): command is SweepCommand {
return SweepCommandSchema.safeParse(command).success
}
export function isUrCommandUnwrapWeth(command: UniversalRouterCommand): command is UnwrapWethCommand {
return UnwrapWethCommandSchema.safeParse(command).success
}
export function isAmountInParam(param: Param): param is AmountInParam { export function isAmountInParam(param: Param): param is AmountInParam {
return AmountInParamSchema.safeParse(param).success return AmountInParamSchema.safeParse(param).success
} }
...@@ -287,3 +320,7 @@ export function isAmountOutMinParam(param: Param): param is AmountOutMinParam { ...@@ -287,3 +320,7 @@ export function isAmountOutMinParam(param: Param): param is AmountOutMinParam {
export function isAmountOutParam(param: Param): param is AmountOutParam { export function isAmountOutParam(param: Param): param is AmountOutParam {
return AmountOutParamSchema.safeParse(param).success return AmountOutParamSchema.safeParse(param).success
} }
export function isAmountMinParam(param: Param): param is AmountMinParam {
return AmountMinParamSchema.safeParse(param).success
}
...@@ -16,14 +16,14 @@ import { useOptimizedSearchParams } from 'src/app/hooks/useOptimizedSearchParams ...@@ -16,14 +16,14 @@ import { useOptimizedSearchParams } from 'src/app/hooks/useOptimizedSearchParams
import { HomeQueryParams, HomeTabs } from 'src/app/navigation/constants' import { HomeQueryParams, HomeTabs } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state' import { navigate } from 'src/app/navigation/state'
import { Flex, Loader, Text, TouchableArea, styled } from 'ui/src' import { Flex, Loader, Text, TouchableArea, styled } from 'ui/src'
import { useSelectAddressHasNotifications } from 'uniswap/src/features/notifications/hooks'
import { setNotificationStatus } from 'uniswap/src/features/notifications/slice'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ONE_MINUTE_MS, ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_MINUTE_MS, ONE_SECOND_MS } from 'utilities/src/time/time'
import { useTimeout } from 'utilities/src/time/timing' import { useTimeout } from 'utilities/src/time/timing'
import { NFTS_TAB_DATA_DEPENDENCIES } from 'wallet/src/components/nfts/NftsList' import { NFTS_TAB_DATA_DEPENDENCIES } from 'wallet/src/components/nfts/NftsList'
import { PendingNotificationBadge } from 'wallet/src/features/notifications/components/PendingNotificationBadge' import { PendingNotificationBadge } from 'wallet/src/features/notifications/components/PendingNotificationBadge'
import { useSelectAddressHasNotifications } from 'wallet/src/features/notifications/hooks'
import { setNotificationStatus } from 'wallet/src/features/notifications/slice'
import { PortfolioBalance } from 'wallet/src/features/portfolio/PortfolioBalance' import { PortfolioBalance } from 'wallet/src/features/portfolio/PortfolioBalance'
import { useHeartbeatReporter, useLastBalancesReporter } from 'wallet/src/features/telemetry/hooks' import { useHeartbeatReporter, useLastBalancesReporter } from 'wallet/src/features/telemetry/hooks'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
...@@ -230,7 +230,7 @@ const AnimatedTab = styled(Flex, { ...@@ -230,7 +230,7 @@ const AnimatedTab = styled(Flex, {
true: {}, true: {},
false: { false: {
pointerEvents: 'none', pointerEvents: 'none',
maxHeight: 300, display: 'none',
}, },
}, },
......
...@@ -15,6 +15,8 @@ import { animationPresets } from 'ui/src/animations' ...@@ -15,6 +15,8 @@ import { animationPresets } from 'ui/src/animations'
import { CopyAlt, Globe, RotatableChevron, Settings } from 'ui/src/components/icons' import { CopyAlt, Globe, RotatableChevron, Settings } from 'ui/src/components/icons'
import { borderRadii, iconSizes } from 'ui/src/theme' import { borderRadii, iconSizes } from 'ui/src/theme'
import { useAvatar } from 'uniswap/src/features/address/avatar' import { useAvatar } from 'uniswap/src/features/address/avatar'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'uniswap/src/features/notifications/types'
import { ElementName } from 'uniswap/src/features/telemetry/constants' import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
...@@ -27,8 +29,6 @@ import { DappIconPlaceholder } from 'wallet/src/components/WalletConnect/DappIco ...@@ -27,8 +29,6 @@ import { DappIconPlaceholder } from 'wallet/src/components/WalletConnect/DappIco
import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon' import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon'
import { AnimatedUnitagDisplayName } from 'wallet/src/components/accounts/AnimatedUnitagDisplayName' import { AnimatedUnitagDisplayName } from 'wallet/src/components/accounts/AnimatedUnitagDisplayName'
import useIsFocused from 'wallet/src/features/focus/useIsFocused' import useIsFocused from 'wallet/src/features/focus/useIsFocused'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'wallet/src/features/notifications/types'
import { useDisplayName } from 'wallet/src/features/wallet/hooks' import { useDisplayName } from 'wallet/src/features/wallet/hooks'
import { DisplayNameType } from 'wallet/src/features/wallet/types' import { DisplayNameType } from 'wallet/src/features/wallet/types'
......
...@@ -11,12 +11,12 @@ import { usePreventOverflowBelowFold } from 'ui/src/hooks/usePreventOverflowBelo ...@@ -11,12 +11,12 @@ import { usePreventOverflowBelowFold } from 'ui/src/hooks/usePreventOverflowBelo
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo'
import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useEnabledChains } from 'uniswap/src/features/settings/hooks'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { UniverseChainId } from 'uniswap/src/types/chains' import { UniverseChainId } from 'uniswap/src/types/chains'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks'
const BUTTON_OFFSET = 20 const BUTTON_OFFSET = 20
......
...@@ -11,6 +11,7 @@ import { InfoLinkModal } from 'uniswap/src/components/modals/InfoLinkModal' ...@@ -11,6 +11,7 @@ import { InfoLinkModal } from 'uniswap/src/components/modals/InfoLinkModal'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { PortfolioBalance } from 'uniswap/src/features/dataApi/types' import { PortfolioBalance } from 'uniswap/src/features/dataApi/types'
import { useEnabledChains } from 'uniswap/src/features/settings/hooks'
import { ElementName, ModalName, SectionName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ModalName, SectionName, WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { InformationBanner } from 'wallet/src/components/banners/InformationBanner' import { InformationBanner } from 'wallet/src/components/banners/InformationBanner'
...@@ -36,7 +37,13 @@ type TokenBalanceListProps = { ...@@ -36,7 +37,13 @@ type TokenBalanceListProps = {
export const TokenBalanceList = memo(function _TokenBalanceList({ owner }: TokenBalanceListProps): JSX.Element { export const TokenBalanceList = memo(function _TokenBalanceList({ owner }: TokenBalanceListProps): JSX.Element {
const { navigateToTokenDetails } = useWalletNavigation() const { navigateToTokenDetails } = useWalletNavigation()
const { isTestnetModeEnabled } = useEnabledChains()
const onPressToken = (currencyId: string): void => { const onPressToken = (currencyId: string): void => {
if (isTestnetModeEnabled) {
return
}
sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, { sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, {
element: ElementName.TokenItem, element: ElementName.TokenItem,
section: SectionName.HomeTokensTab, section: SectionName.HomeTokensTab,
......
import { useCallback } from 'react' import { useCallback } from 'react'
import { focusOrCreateUnitagClaimTab } from 'src/app/navigation/utils' import { focusOrCreateUnitagTab } from 'src/app/navigation/utils'
import { Flex } from 'ui/src' import { Flex } from 'ui/src'
import { PollingInterval } from 'uniswap/src/constants/misc' import { PollingInterval } from 'uniswap/src/constants/misc'
import { AccountType } from 'uniswap/src/features/accounts/types' import { AccountType } from 'uniswap/src/features/accounts/types'
...@@ -18,7 +18,7 @@ export function HomeIntroCardStack(): JSX.Element | null { ...@@ -18,7 +18,7 @@ export function HomeIntroCardStack(): JSX.Element | null {
}) })
const navigateToUnitagClaim = useCallback(async () => { const navigateToUnitagClaim = useCallback(async () => {
await focusOrCreateUnitagClaimTab() await focusOrCreateUnitagTab()
}, []) }, [])
const { cards } = useSharedIntroCards({ const { cards } = useSharedIntroCards({
......
import { useSelector } from 'react-redux' import { useSelectAddressNotifications } from 'uniswap/src/features/notifications/hooks'
import { AppNotification, AppNotificationType } from 'uniswap/src/features/notifications/types'
import { DappConnectedNotification } from 'wallet/src/features/notifications/components/DappConnectedNotification' import { DappConnectedNotification } from 'wallet/src/features/notifications/components/DappConnectedNotification'
import { DappDisconnectedNotification } from 'wallet/src/features/notifications/components/DappDisconnectedNotification' import { DappDisconnectedNotification } from 'wallet/src/features/notifications/components/DappDisconnectedNotification'
import { NotSupportedNetworkNotification } from 'wallet/src/features/notifications/components/NotSupportedNetworkNotification' import { NotSupportedNetworkNotification } from 'wallet/src/features/notifications/components/NotSupportedNetworkNotification'
import { PasswordChangedNotification } from 'wallet/src/features/notifications/components/PasswordChangedNotification' import { PasswordChangedNotification } from 'wallet/src/features/notifications/components/PasswordChangedNotification'
import { SharedNotificationToastRouter } from 'wallet/src/features/notifications/components/SharedNotificationToastRouter' import { SharedNotificationToastRouter } from 'wallet/src/features/notifications/components/SharedNotificationToastRouter'
import { selectActiveAccountNotifications } from 'wallet/src/features/notifications/selectors' import { useActiveAccountAddress } from 'wallet/src/features/wallet/hooks'
import { AppNotification, AppNotificationType } from 'wallet/src/features/notifications/types'
export function NotificationToastWrapper(): JSX.Element | null { export function NotificationToastWrapper(): JSX.Element | null {
const notifications = useSelector(selectActiveAccountNotifications) const activeAccountAddress = useActiveAccountAddress()
const notifications = useSelectAddressNotifications(activeAccountAddress)
const notification = notifications?.[0] const notification = notifications?.[0]
if (!notification) { if (!notification) {
......
...@@ -17,6 +17,8 @@ export function OnboardingScreenFrame({ ...@@ -17,6 +17,8 @@ export function OnboardingScreenFrame({
subtitle, subtitle,
title, title,
warningSubtitle, warningSubtitle,
endAdornment,
noTopPadding,
}: Partial<OnboardingScreenProps>): JSX.Element { }: Partial<OnboardingScreenProps>): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -26,7 +28,7 @@ export function OnboardingScreenFrame({ ...@@ -26,7 +28,7 @@ export function OnboardingScreenFrame({
return ( return (
<> <>
<Flex alignItems="center" gap="$spacing16" pt="$spacing24"> <Flex alignItems="center" gap="$spacing16" pt={noTopPadding || '$spacing24'}>
{onBack && ( {onBack && (
<TouchableArea <TouchableArea
hoverable hoverable
...@@ -56,6 +58,11 @@ export function OnboardingScreenFrame({ ...@@ -56,6 +58,11 @@ export function OnboardingScreenFrame({
</Text> </Text>
</TouchableArea> </TouchableArea>
)} )}
{endAdornment && (
<TouchableArea position="absolute" right="$none" top="$none">
{endAdornment}
</TouchableArea>
)}
{Icon} {Icon}
<Flex alignItems="center" gap="$spacing4" px="$spacing24"> <Flex alignItems="center" gap="$spacing4" px="$spacing24">
<Text textAlign="center" variant="subheading1"> <Text textAlign="center" variant="subheading1">
......
...@@ -11,8 +11,10 @@ export type OnboardingScreenProps = { ...@@ -11,8 +11,10 @@ export type OnboardingScreenProps = {
onSubmit?: () => void onSubmit?: () => void
onSkip?: () => void onSkip?: () => void
subtitle?: string subtitle?: string
title: string | JSX.Element title?: string | JSX.Element
warningSubtitle?: string warningSubtitle?: string
outsideContent?: JSX.Element outsideContent?: JSX.Element
belowFrameContent?: JSX.Element belowFrameContent?: JSX.Element
endAdornment?: JSX.Element
noTopPadding?: boolean
} }
...@@ -37,6 +37,8 @@ export enum ClaimUnitagSteps { ...@@ -37,6 +37,8 @@ export enum ClaimUnitagSteps {
Intro = 'intro', Intro = 'intro',
CreateUsername = 'createUsername', CreateUsername = 'createUsername',
ChooseProfilePic = 'chooseProfilePic', ChooseProfilePic = 'chooseProfilePic',
EditProfile = 'editProfile',
Confirmation = 'confirmation',
} }
export type Step = CreateOnboardingSteps | ImportOnboardingSteps | ResetSteps | ScanOnboardingSteps | ClaimUnitagSteps export type Step = CreateOnboardingSteps | ImportOnboardingSteps | ResetSteps | ScanOnboardingSteps | ClaimUnitagSteps
......
...@@ -11,14 +11,14 @@ import { NoDappConnections } from 'src/app/features/settings/SettingsManageConne ...@@ -11,14 +11,14 @@ import { NoDappConnections } from 'src/app/features/settings/SettingsManageConne
import { Flex, Text, TouchableArea, UniversalImage, useSporeColors } from 'ui/src' import { Flex, Text, TouchableArea, UniversalImage, useSporeColors } from 'ui/src'
import { MinusCircle } from 'ui/src/components/icons' import { MinusCircle } from 'ui/src/components/icons'
import { borderRadii, breakpoints, iconSizes } from 'ui/src/theme' import { borderRadii, breakpoints, iconSizes } from 'ui/src/theme'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { ExtensionScreens } from 'uniswap/src/types/screens/extension' import { ExtensionScreens } from 'uniswap/src/types/screens/extension'
import { extractNameFromUrl } from 'utilities/src/format/extractNameFromUrl' import { extractNameFromUrl } from 'utilities/src/format/extractNameFromUrl'
import { DappIconPlaceholder } from 'wallet/src/components/WalletConnect/DappIconPlaceholder' import { DappIconPlaceholder } from 'wallet/src/components/WalletConnect/DappIconPlaceholder'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks'
const MIN_SCREEN_WIDTH = breakpoints.xxs const MIN_SCREEN_WIDTH = breakpoints.xxs
......
...@@ -3,10 +3,10 @@ import { useDispatch } from 'react-redux' ...@@ -3,10 +3,10 @@ import { useDispatch } from 'react-redux'
import { removeAllDappConnectionsForAccount } from 'src/app/features/dapp/actions' import { removeAllDappConnectionsForAccount } from 'src/app/features/dapp/actions'
import { ContextMenu, Flex, TouchableArea } from 'ui/src' import { ContextMenu, Flex, TouchableArea } from 'ui/src'
import { Ellipsis, Power } from 'ui/src/components/icons' import { Ellipsis, Power } from 'ui/src/components/icons'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { useActiveAccountAddress, useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountAddress, useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks'
const PowerCircle = (): JSX.Element => ( const PowerCircle = (): JSX.Element => (
......
...@@ -239,7 +239,9 @@ function SeedPhraseWord({ ...@@ -239,7 +239,9 @@ function SeedPhraseWord({
<Text color="$neutral3" minWidth={indexMinWidth} variant="body2" onLayout={onIndexLayout}> <Text color="$neutral3" minWidth={indexMinWidth} variant="body2" onLayout={onIndexLayout}>
{index} {index}
</Text> </Text>
<Text variant="body2">{word}</Text> <Text variant="body2" className="notranslate">
{word}
</Text>
</Flex> </Flex>
) )
} }
...@@ -3,10 +3,10 @@ import { useTranslation } from 'react-i18next' ...@@ -3,10 +3,10 @@ import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { PADDING_STRENGTH_INDICATOR, PasswordInput } from 'src/app/components/PasswordInput' import { PADDING_STRENGTH_INDICATOR, PasswordInput } from 'src/app/components/PasswordInput'
import { Button, Flex, Text } from 'ui/src' import { Button, Flex, Text } from 'ui/src'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring' import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
import { usePasswordForm } from 'wallet/src/utils/password' import { usePasswordForm } from 'wallet/src/utils/password'
......
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext'
import { AnimatePresence, ContextMenu, Flex, MenuContentItem } from 'ui/src'
import { Edit, Ellipsis, Trash } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks'
import { UnitagScreens } from 'uniswap/src/types/screens/mobile'
import { ChangeUnitagModal } from 'wallet/src/features/unitags/ChangeUnitagModal'
import { DeleteUnitagModal } from 'wallet/src/features/unitags/DeleteUnitagModal'
import { EditUnitagProfileContent } from 'wallet/src/features/unitags/EditUnitagProfileContent'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
export function EditUnitagProfileScreen({ enableBack = false }: { enableBack?: boolean }): JSX.Element {
const { t } = useTranslation()
const address = useActiveAccountAddressWithThrow()
const { unitag: retrievedUnitag } = useUnitagByAddress(address)
const unitag = retrievedUnitag?.username
const { goToPreviousStep } = useOnboardingSteps()
const [showDeleteUnitagModal, setShowDeleteUnitagModal] = useState(false)
const [showChangeUnitagModal, setShowChangeUnitagModal] = useState(false)
const menuOptions = useMemo((): MenuContentItem[] => {
return [
{
label: t('unitags.profile.action.edit'),
onPress: (): void => setShowChangeUnitagModal(true),
Icon: Edit,
},
{
label: t('unitags.profile.action.delete'),
onPress: (): void => setShowDeleteUnitagModal(true),
Icon: Trash,
destructive: true,
},
]
}, [t, setShowChangeUnitagModal, setShowDeleteUnitagModal])
return (
<Trace logImpression screen={UnitagScreens.EditProfile}>
<OnboardingScreen
noTopPadding
title={t('settings.setting.wallet.action.editProfile')}
endAdornment={
<ContextMenu closeOnClick itemId={address} menuOptions={menuOptions} onLeftClick>
<Flex>
<Ellipsis color="$neutral2" size={iconSizes.icon24} />
</Flex>
</ContextMenu>
}
onBack={enableBack ? goToPreviousStep : undefined}
>
<Flex gap="$spacing12" width="100%" pt="$spacing8">
{unitag && (
<>
<EditUnitagProfileContent address={address} unitag={unitag} entryPoint={UnitagScreens.EditProfile} />
<AnimatePresence>
{showDeleteUnitagModal && (
<DeleteUnitagModal
address={address}
unitag={unitag}
onClose={(): void => setShowDeleteUnitagModal(false)}
/>
)}
{showChangeUnitagModal && (
<ChangeUnitagModal
address={address}
unitag={unitag}
onClose={(): void => setShowChangeUnitagModal(false)}
/>
)}
</AnimatePresence>
</>
)}
</Flex>
</OnboardingScreen>
</Trace>
)
}
...@@ -3,25 +3,30 @@ import { useCallback, useEffect } from 'react' ...@@ -3,25 +3,30 @@ import { useCallback, useEffect } from 'react'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen' import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext' import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext'
import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext' import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext'
import { backgroundToSidePanelMessageChannel } from 'src/background/messagePassing/messageChannels'
import { BackgroundToSidePanelRequestType } from 'src/background/messagePassing/types/requests'
import { Flex, Square } from 'ui/src' import { Flex, Square } from 'ui/src'
import { Person } from 'ui/src/components/icons' import { Person } from 'ui/src/components/icons'
import { fonts, iconSizes, spacing } from 'ui/src/theme' import { fonts, iconSizes } from 'ui/src/theme'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionUnitagClaimScreens } from 'uniswap/src/types/screens/extension' import { ExtensionUnitagClaimScreens } from 'uniswap/src/types/screens/extension'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { extensionNftModalProps } from 'wallet/src/features/unitags/ChooseNftModal'
import { UnitagChooseProfilePicContent } from 'wallet/src/features/unitags/UnitagChooseProfilePicContent' import { UnitagChooseProfilePicContent } from 'wallet/src/features/unitags/UnitagChooseProfilePicContent'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
const NFT_MODAL_MAX_WIDTH = 610
export function UnitagChooseProfilePicScreen(): JSX.Element { export function UnitagChooseProfilePicScreen(): JSX.Element {
const { goToNextStep, goToPreviousStep } = useOnboardingSteps() const { goToNextStep, goToPreviousStep } = useOnboardingSteps()
const { unitag, entryPoint, setProfilePicUri } = useUnitagClaimContext() const { unitag, entryPoint, setProfilePicUri } = useUnitagClaimContext()
const address = useActiveAccountAddressWithThrow() const address = useActiveAccountAddressWithThrow()
const onNavigateContinue = useCallback( const onNavigateContinue = useCallback(
(imageUri: string | undefined) => { async (imageUri: string | undefined) => {
setProfilePicUri(imageUri) setProfilePicUri(imageUri)
// TODO WALL-5067 move claim logic out of UnitagChooseProfilePicContent and integrate message sending
await backgroundToSidePanelMessageChannel.sendMessage({
type: BackgroundToSidePanelRequestType.RefreshUnitags,
})
goToNextStep() goToNextStep()
}, },
[setProfilePicUri, goToNextStep], [setProfilePicUri, goToNextStep],
...@@ -52,18 +57,12 @@ export function UnitagChooseProfilePicScreen(): JSX.Element { ...@@ -52,18 +57,12 @@ export function UnitagChooseProfilePicScreen(): JSX.Element {
> >
<Flex gap="$spacing24" pt="$spacing24" width="100%"> <Flex gap="$spacing24" pt="$spacing24" width="100%">
<UnitagChooseProfilePicContent <UnitagChooseProfilePicContent
shouldHandleClaim
entryPoint={entryPoint} entryPoint={entryPoint}
address={address} address={address}
unitag={unitag ?? ''} unitag={unitag ?? ''}
shouldHandleClaim={false}
unitagFontSize={fonts.heading3.fontSize} unitagFontSize={fonts.heading3.fontSize}
nftModalProps={{ nftModalProps={extensionNftModalProps}
includeContextMenu: false,
itemMargin: '$spacing6',
containerProps: { m: -spacing.spacing6 }, // Cancels out the margin on each NFT item
modalMaxWidth: NFT_MODAL_MAX_WIDTH,
numColumns: 4,
}}
onContinue={onNavigateContinue} onContinue={onNavigateContinue}
/> />
</Flex> </Flex>
......
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext'
import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext'
import { closeCurrentTab } from 'src/app/navigation/utils'
import { Button, Flex, Text } from 'ui/src'
import { logger } from 'utilities/src/logger/logger'
import { UnitagWithProfilePicture } from 'wallet/src/features/unitags/UnitagWithProfilePicture'
import { UNITAG_SUFFIX } from 'wallet/src/features/unitags/constants'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
export function UnitagConfirmationScreen(): JSX.Element {
const { t } = useTranslation()
const address = useActiveAccountAddressWithThrow()
const { unitag, profilePicUri } = useUnitagClaimContext()
const { goToNextStep } = useOnboardingSteps()
const onPressCustomize = (): void => {
// Assumes edit profile screen is next step. Uses onboarding steps for consistent nav animation
goToNextStep()
}
useEffect(() => {
if (!unitag) {
logger.warn('UnitagConfirmationScreen.tsx', 'render', 'unitag is empty when it should have a value')
}
}, [unitag])
if (!unitag) {
return <></>
}
return (
<OnboardingScreen>
<Flex grow gap="$spacing12" pt="$spacing24">
<Flex centered>
<UnitagWithProfilePicture address={address} profilePictureUri={profilePicUri} unitag={unitag} />
</Flex>
<Flex centered gap="$spacing12">
<Text color="$neutral1" textAlign="center" variant="heading3">
{t('unitags.claim.confirmation.success.long')}
</Text>
<Text color="$neutral2" textAlign="center" variant="subheading2">
{t('unitags.claim.confirmation.description', {
unitagAddress: `${unitag}${UNITAG_SUFFIX}`,
})}
</Text>
</Flex>
<Flex gap="$spacing12" pt="$spacing12">
<Button size="medium" theme="primary" onPress={closeCurrentTab}>
{t('common.button.done')}
</Button>
<Button size="medium" theme="secondary" onPress={onPressCustomize}>
{t('unitags.claim.confirmation.customize')}
</Button>
</Flex>
</Flex>
</OnboardingScreen>
)
}
import { useCallback } from 'react' import { useCallback } from 'react'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { pushNotification } from 'wallet/src/features/notifications/slice' import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'wallet/src/features/notifications/types' import { AppNotificationType, CopyNotificationType } from 'uniswap/src/features/notifications/types'
export function useCopyToClipboard(): ({ export function useCopyToClipboard(): ({
textToCopy, textToCopy,
......
...@@ -5,6 +5,7 @@ import { useCopyToClipboard } from 'src/app/hooks/useOnCopyToClipboard' ...@@ -5,6 +5,7 @@ import { useCopyToClipboard } from 'src/app/hooks/useOnCopyToClipboard'
import { AppRoutes, HomeQueryParams, HomeTabs } from 'src/app/navigation/constants' import { AppRoutes, HomeQueryParams, HomeTabs } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state' import { navigate } from 'src/app/navigation/state'
import { SidebarLocationState, focusOrCreateTokensExploreTab } from 'src/app/navigation/utils' import { SidebarLocationState, focusOrCreateTokensExploreTab } from 'src/app/navigation/utils'
import { CopyNotificationType } from 'uniswap/src/features/notifications/types'
import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useEnabledChains } from 'uniswap/src/features/settings/hooks'
import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
...@@ -22,7 +23,6 @@ import { ...@@ -22,7 +23,6 @@ import {
getNavigateToSendFlowArgsInitialState, getNavigateToSendFlowArgsInitialState,
getNavigateToSwapFlowArgsInitialState, getNavigateToSwapFlowArgsInitialState,
} from 'wallet/src/contexts/WalletNavigationContext' } from 'wallet/src/contexts/WalletNavigationContext'
import { CopyNotificationType } from 'wallet/src/features/notifications/types'
import { getNftUrl, getTokenUrl } from 'wallet/src/utils/linking' import { getNftUrl, getTokenUrl } from 'wallet/src/utils/linking'
export function SideBarNavigationProvider({ children }: PropsWithChildren): JSX.Element { export function SideBarNavigationProvider({ children }: PropsWithChildren): JSX.Element {
......
...@@ -13,6 +13,7 @@ export enum OnboardingRoutes { ...@@ -13,6 +13,7 @@ export enum OnboardingRoutes {
Reset = 'reset', Reset = 'reset',
ResetScan = 'reset-scan', ResetScan = 'reset-scan',
UnsupportedBrowser = 'unsupported-browser', UnsupportedBrowser = 'unsupported-browser',
EditProfile = 'edit-profile',
} }
export enum AppRoutes { export enum AppRoutes {
......
...@@ -68,13 +68,13 @@ export async function focusOrCreateOnboardingTab(page?: string): Promise<void> { ...@@ -68,13 +68,13 @@ export async function focusOrCreateOnboardingTab(page?: string): Promise<void> {
}) })
} }
export async function focusOrCreateUnitagClaimTab(): Promise<void> { export async function focusOrCreateUnitagTab(page?: string): Promise<void> {
const extension = await chrome.management.getSelf() const extension = await chrome.management.getSelf()
const tabs = await chrome.tabs.query({ url: `chrome-extension://${extension.id}/unitagClaim.html*` }) const tabs = await chrome.tabs.query({ url: `chrome-extension://${extension.id}/unitagClaim.html*` })
const tab = tabs[0] const tab = tabs[0]
const url = 'unitagClaim.html#/' const url = `unitagClaim.html#/${page ?? ''}`
if (!tab?.id) { if (!tab?.id) {
await chrome.tabs.create({ url }) await chrome.tabs.create({ url })
...@@ -205,3 +205,22 @@ export async function getCurrentTabAndWindowId(): Promise<{ tabId: number; windo ...@@ -205,3 +205,22 @@ export async function getCurrentTabAndWindowId(): Promise<{ tabId: number; windo
} }
return { tabId: tabs[0].id, windowId: tabs[0].windowId } return { tabId: tabs[0].id, windowId: tabs[0].windowId }
} }
export async function closeCurrentTab(): Promise<void> {
try {
const tab = await chrome.tabs.getCurrent()
if (tab?.id) {
await chrome.tabs.remove(tab.id)
} else {
throw new Error('chrome.tabs.getCurrent did not return a tab with an id')
}
} catch (e) {
logger.error(e, {
tags: {
file: 'utils.ts',
function: 'closeCurrentTab',
},
})
}
}
...@@ -76,6 +76,8 @@ import { ...@@ -76,6 +76,8 @@ import {
ExtensionToDappRequestType, ExtensionToDappRequestType,
FocusOnboardingMessage, FocusOnboardingMessage,
FocusOnboardingMessageSchema, FocusOnboardingMessageSchema,
RefreshUnitagsRequest,
RefreshUnitagsRequestSchema,
TabActivatedRequest, TabActivatedRequest,
TabActivatedRequestSchema, TabActivatedRequestSchema,
UpdateConnectionRequest, UpdateConnectionRequest,
...@@ -121,6 +123,7 @@ export function createOnboardingMessagePort( ...@@ -121,6 +123,7 @@ export function createOnboardingMessagePort(
type BackgroundToSidePanelMessageSchemas = { type BackgroundToSidePanelMessageSchemas = {
[BackgroundToSidePanelRequestType.DappRequestReceived]: DappRequestMessage [BackgroundToSidePanelRequestType.DappRequestReceived]: DappRequestMessage
[BackgroundToSidePanelRequestType.TabActivated]: TabActivatedRequest [BackgroundToSidePanelRequestType.TabActivated]: TabActivatedRequest
[BackgroundToSidePanelRequestType.RefreshUnitags]: RefreshUnitagsRequest
} }
const backgroundToSidePanelMessageParsers: MessageParsers< const backgroundToSidePanelMessageParsers: MessageParsers<
BackgroundToSidePanelRequestType, BackgroundToSidePanelRequestType,
...@@ -130,6 +133,8 @@ const backgroundToSidePanelMessageParsers: MessageParsers< ...@@ -130,6 +133,8 @@ const backgroundToSidePanelMessageParsers: MessageParsers<
DappRequestMessageSchema.parse(message), DappRequestMessageSchema.parse(message),
[BackgroundToSidePanelRequestType.TabActivated]: (message): TabActivatedRequest => [BackgroundToSidePanelRequestType.TabActivated]: (message): TabActivatedRequest =>
TabActivatedRequestSchema.parse(message), TabActivatedRequestSchema.parse(message),
[BackgroundToSidePanelRequestType.RefreshUnitags]: (message): RefreshUnitagsRequest =>
RefreshUnitagsRequestSchema.parse(message),
} }
function createBackgroundToSidePanelMessageChannel(): TypedRuntimeMessageChannel< function createBackgroundToSidePanelMessageChannel(): TypedRuntimeMessageChannel<
...@@ -139,6 +144,7 @@ function createBackgroundToSidePanelMessageChannel(): TypedRuntimeMessageChannel ...@@ -139,6 +144,7 @@ function createBackgroundToSidePanelMessageChannel(): TypedRuntimeMessageChannel
return new TypedRuntimeMessageChannel<BackgroundToSidePanelRequestType, BackgroundToSidePanelMessageSchemas>({ return new TypedRuntimeMessageChannel<BackgroundToSidePanelRequestType, BackgroundToSidePanelMessageSchemas>({
channelName: MessageChannelName.DappBackground, channelName: MessageChannelName.DappBackground,
messageParsers: backgroundToSidePanelMessageParsers, messageParsers: backgroundToSidePanelMessageParsers,
canReceiveFromWebPage: true,
}) })
} }
...@@ -194,7 +200,7 @@ function createContentScriptToBackgroundMessageChannel(): TypedRuntimeMessageCha ...@@ -194,7 +200,7 @@ function createContentScriptToBackgroundMessageChannel(): TypedRuntimeMessageCha
return new TypedRuntimeMessageChannel<DappRequestType, ContentScriptToBackgroundMessageSchemas>({ return new TypedRuntimeMessageChannel<DappRequestType, ContentScriptToBackgroundMessageSchemas>({
channelName: MessageChannelName.DappContentScript, channelName: MessageChannelName.DappContentScript,
messageParsers: contentScriptToBackgroundMessageParsers, messageParsers: contentScriptToBackgroundMessageParsers,
canReceiveFromContentScript: true, canReceiveFromWebPage: true,
}) })
} }
...@@ -315,7 +321,7 @@ export function createContentScriptUtilityMessageChannel(): TypedRuntimeMessageC ...@@ -315,7 +321,7 @@ export function createContentScriptUtilityMessageChannel(): TypedRuntimeMessageC
return new TypedRuntimeMessageChannel<ContentScriptUtilityMessageType, ContentScriptUtilityMessageSchemas>({ return new TypedRuntimeMessageChannel<ContentScriptUtilityMessageType, ContentScriptUtilityMessageSchemas>({
channelName: MessageChannelName.ContentScriptUtility, channelName: MessageChannelName.ContentScriptUtility,
messageParsers: contentScriptUtilityMessageParsers, messageParsers: contentScriptUtilityMessageParsers,
canReceiveFromContentScript: true, canReceiveFromWebPage: true,
}) })
} }
......
...@@ -13,10 +13,10 @@ class ChromeMessageChannel { ...@@ -13,10 +13,10 @@ class ChromeMessageChannel {
constructor({ constructor({
channelName, channelName,
port, port,
canReceiveFromContentScript = false, canReceiveFromWebPage = false,
}: { }: {
channelName: string channelName: string
canReceiveFromContentScript?: boolean canReceiveFromWebPage?: boolean
port?: chrome.runtime.Port port?: chrome.runtime.Port
}) { }) {
this.channelName = channelName this.channelName = channelName
...@@ -26,7 +26,7 @@ class ChromeMessageChannel { ...@@ -26,7 +26,7 @@ class ChromeMessageChannel {
const targetMessage = message[this.channelName] const targetMessage = message[this.channelName]
if (targetMessage !== undefined) { if (targetMessage !== undefined) {
if (sender?.tab !== undefined && !canReceiveFromContentScript) { if (sender?.tab !== undefined && !canReceiveFromWebPage) {
return return
} }
...@@ -113,18 +113,18 @@ abstract class TypedMessageChannel< ...@@ -113,18 +113,18 @@ abstract class TypedMessageChannel<
channelName, channelName,
port, port,
messageParsers, messageParsers,
canReceiveFromContentScript, canReceiveFromWebPage,
}: { }: {
channelName: string channelName: string
port?: chrome.runtime.Port port?: chrome.runtime.Port
messageParsers: MessageParsers<T, R> messageParsers: MessageParsers<T, R>
canReceiveFromContentScript?: boolean canReceiveFromWebPage?: boolean
}) { }) {
this.messageParsers = messageParsers this.messageParsers = messageParsers
this.chromeMessageChannel = new ChromeMessageChannel({ this.chromeMessageChannel = new ChromeMessageChannel({
channelName, channelName,
port, port,
canReceiveFromContentScript, canReceiveFromWebPage,
}) })
this.chromeMessageChannel.addMessageListener((message, sender) => { this.chromeMessageChannel.addMessageListener((message, sender) => {
...@@ -263,13 +263,13 @@ export class TypedRuntimeMessageChannel< ...@@ -263,13 +263,13 @@ export class TypedRuntimeMessageChannel<
constructor({ constructor({
channelName, channelName,
messageParsers, messageParsers,
canReceiveFromContentScript, canReceiveFromWebPage,
}: { }: {
channelName: string channelName: string
messageParsers: MessageParsers<T, R> messageParsers: MessageParsers<T, R>
canReceiveFromContentScript?: boolean canReceiveFromWebPage?: boolean
}) { }) {
super({ channelName, messageParsers, canReceiveFromContentScript }) super({ channelName, messageParsers, canReceiveFromWebPage })
} }
} }
...@@ -294,7 +294,7 @@ export class TypedPortMessageChannel< ...@@ -294,7 +294,7 @@ export class TypedPortMessageChannel<
port: chrome.runtime.Port port: chrome.runtime.Port
canReceiveFromContentScript?: boolean canReceiveFromContentScript?: boolean
}) { }) {
super({ channelName, messageParsers, port, canReceiveFromContentScript }) super({ channelName, messageParsers, port, canReceiveFromWebPage: canReceiveFromContentScript })
this.port = port this.port = port
} }
} }
...@@ -36,6 +36,7 @@ export type FocusOnboardingMessage = z.infer<typeof FocusOnboardingMessageSchema ...@@ -36,6 +36,7 @@ export type FocusOnboardingMessage = z.infer<typeof FocusOnboardingMessageSchema
export enum BackgroundToSidePanelRequestType { export enum BackgroundToSidePanelRequestType {
TabActivated = 'TabActivated', TabActivated = 'TabActivated',
DappRequestReceived = 'DappRequestReceived', DappRequestReceived = 'DappRequestReceived',
RefreshUnitags = 'RefreshUnitags',
} }
export const DappRequestMessageSchema = z.object({ export const DappRequestMessageSchema = z.object({
...@@ -55,6 +56,11 @@ export const TabActivatedRequestSchema = MessageSchema.extend({ ...@@ -55,6 +56,11 @@ export const TabActivatedRequestSchema = MessageSchema.extend({
}) })
export type TabActivatedRequest = z.infer<typeof TabActivatedRequestSchema> export type TabActivatedRequest = z.infer<typeof TabActivatedRequestSchema>
export const RefreshUnitagsRequestSchema = MessageSchema.extend({
type: z.literal(BackgroundToSidePanelRequestType.RefreshUnitags),
})
export type RefreshUnitagsRequest = z.infer<typeof RefreshUnitagsRequestSchema>
// Requests outgoing from the extension to the injected script // Requests outgoing from the extension to the injected script
export enum ExtensionToDappRequestType { export enum ExtensionToDappRequestType {
UpdateConnections = 'UpdateConnections', UpdateConnections = 'UpdateConnections',
......
...@@ -10,6 +10,7 @@ import { ...@@ -10,6 +10,7 @@ import {
v12Schema, v12Schema,
v13Schema, v13Schema,
v14Schema, v14Schema,
v15Schema,
v1Schema, v1Schema,
v2Schema, v2Schema,
v3Schema, v3Schema,
...@@ -23,6 +24,7 @@ import { ...@@ -23,6 +24,7 @@ import {
import { initialUniswapBehaviorHistoryState } from 'uniswap/src/features/behaviorHistory/slice' import { initialUniswapBehaviorHistoryState } from 'uniswap/src/features/behaviorHistory/slice'
import { initialFavoritesState } from 'uniswap/src/features/favorites/slice' import { initialFavoritesState } from 'uniswap/src/features/favorites/slice'
import { FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants' import { FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants'
import { initialNotificationsState } from 'uniswap/src/features/notifications/slice'
import { initialSearchHistoryState } from 'uniswap/src/features/search/searchHistorySlice' import { initialSearchHistoryState } from 'uniswap/src/features/search/searchHistorySlice'
import { initialUserSettingsState } from 'uniswap/src/features/settings/slice' import { initialUserSettingsState } from 'uniswap/src/features/settings/slice'
import { initialTokensState } from 'uniswap/src/features/tokens/slice/slice' import { initialTokensState } from 'uniswap/src/features/tokens/slice/slice'
...@@ -32,7 +34,6 @@ import { UniverseChainId } from 'uniswap/src/types/chains' ...@@ -32,7 +34,6 @@ import { UniverseChainId } from 'uniswap/src/types/chains'
import { getAllKeysOfNestedObject } from 'utilities/src/primitives/objects' import { getAllKeysOfNestedObject } from 'utilities/src/primitives/objects'
import { initialAppearanceSettingsState } from 'wallet/src/features/appearance/slice' import { initialAppearanceSettingsState } from 'wallet/src/features/appearance/slice'
import { initialBehaviorHistoryState } from 'wallet/src/features/behaviorHistory/slice' import { initialBehaviorHistoryState } from 'wallet/src/features/behaviorHistory/slice'
import { initialNotificationsState } from 'wallet/src/features/notifications/slice'
import { initialWalletState } from 'wallet/src/features/wallet/slice' import { initialWalletState } from 'wallet/src/features/wallet/slice'
import { createMigrate } from 'wallet/src/state/createMigrate' import { createMigrate } from 'wallet/src/state/createMigrate'
import { HAYDEN_ETH_ADDRESS } from 'wallet/src/state/walletMigrations' import { HAYDEN_ETH_ADDRESS } from 'wallet/src/state/walletMigrations'
...@@ -45,6 +46,7 @@ import { ...@@ -45,6 +46,7 @@ import {
testMovedTokenWarnings, testMovedTokenWarnings,
testMovedUserSettings, testMovedUserSettings,
testRemoveHoldToSwap, testRemoveHoldToSwap,
testUpdateExploreOrderByType,
} from 'wallet/src/state/walletMigrationsTests' } from 'wallet/src/state/walletMigrationsTests'
expect.extend({ toIncludeSameMembers }) expect.extend({ toIncludeSameMembers })
...@@ -262,4 +264,8 @@ describe('Redux state migrations', () => { ...@@ -262,4 +264,8 @@ describe('Redux state migrations', () => {
it('migrates from v14 to v15', async () => { it('migrates from v14 to v15', async () => {
testMovedCurrencySetting(migrations[15], v14Schema) testMovedCurrencySetting(migrations[15], v14Schema)
}) })
it('migrates from v15 to v16', async () => {
testUpdateExploreOrderByType(migrations[16], v15Schema)
})
}) })
...@@ -17,6 +17,7 @@ import { ...@@ -17,6 +17,7 @@ import {
moveUserSettings, moveUserSettings,
removeUniconV2BehaviorState, removeUniconV2BehaviorState,
removeWalletIsUnlockedState, removeWalletIsUnlockedState,
updateExploreOrderByType,
} from 'wallet/src/state/walletMigrations' } from 'wallet/src/state/walletMigrations'
export const migrations = { export const migrations = {
...@@ -38,6 +39,7 @@ export const migrations = { ...@@ -38,6 +39,7 @@ export const migrations = {
13: moveDismissedTokenWarnings, 13: moveDismissedTokenWarnings,
14: moveLanguageSetting, 14: moveLanguageSetting,
15: moveCurrencySetting, 15: moveCurrencySetting,
16: updateExploreOrderByType,
} }
export const EXTENSION_STATE_VERSION = 15 export const EXTENSION_STATE_VERSION = 16
import { RankingType } from 'wallet/src/features/wallet/types'
// only add fields that are persisted // only add fields that are persisted
export const initialSchema = { export const initialSchema = {
dapp: {}, dapp: {},
...@@ -187,4 +189,9 @@ const v15SchemaIntermediate = { ...@@ -187,4 +189,9 @@ const v15SchemaIntermediate = {
delete v15SchemaIntermediate.fiatCurrencySettings delete v15SchemaIntermediate.fiatCurrencySettings
export const v15Schema = v15SchemaIntermediate export const v15Schema = v15SchemaIntermediate
export const getSchema = (): typeof v15Schema => v15Schema export const v16Schema = {
...v15Schema,
wallet: { ...v15Schema.wallet, settings: { ...v15Schema.wallet.settings, tokensOrderBy: RankingType.Volume } },
}
export const getSchema = (): typeof v16Schema => v16Schema
...@@ -19,7 +19,9 @@ ...@@ -19,7 +19,9 @@
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:localeConfig="@xml/locales_config" android:localeConfig="@xml/locales_config"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
android:taskAffinity=""
android:excludeFromRecents="true">
<meta-data <meta-data
android:name="com.onesignal.messaging.default_notification_icon" android:name="com.onesignal.messaging.default_notification_icon"
......
...@@ -17,6 +17,7 @@ import com.shopify.reactnativeperformance.ReactNativePerformance ...@@ -17,6 +17,7 @@ import com.shopify.reactnativeperformance.ReactNativePerformance
import com.uniswap.onboarding.scantastic.ScantasticEncryptionModule import com.uniswap.onboarding.scantastic.ScantasticEncryptionModule
import expo.modules.ApplicationLifecycleDispatcher import expo.modules.ApplicationLifecycleDispatcher
import expo.modules.ReactNativeHostWrapper import expo.modules.ReactNativeHostWrapper
import com.uniswap.RedirectToSourceAppPackage
class MainApplication : MultiDexApplication(), ReactApplication { class MainApplication : MultiDexApplication(), ReactApplication {
override val reactNativeHost: ReactNativeHost = override val reactNativeHost: ReactNativeHost =
...@@ -28,6 +29,7 @@ class MainApplication : MultiDexApplication(), ReactApplication { ...@@ -28,6 +29,7 @@ class MainApplication : MultiDexApplication(), ReactApplication {
add(UniswapPackage()) add(UniswapPackage())
add(RNCloudStorageBackupsManagerModule()) add(RNCloudStorageBackupsManagerModule())
add(ScantasticEncryptionModule()) add(ScantasticEncryptionModule())
add(RedirectToSourceAppPackage())
} }
override fun getJSMainModuleName(): String { override fun getJSMainModuleName(): String {
return "index" return "index"
......
package com.uniswap
import android.content.Intent
import android.net.Uri
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
class RedirectToSourceAppModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String {
return "RedirectToSourceApp"
}
@ReactMethod
fun moveAppToBackground() {
currentActivity?.moveTaskToBack(true)
}
}
package com.uniswap
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class RedirectToSourceAppPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(RedirectToSourceAppModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
...@@ -1777,7 +1777,7 @@ PODS: ...@@ -1777,7 +1777,7 @@ PODS:
- react-native-appsflyer (6.13.1): - react-native-appsflyer (6.13.1):
- AppsFlyerFramework (= 6.13.1) - AppsFlyerFramework (= 6.13.1)
- React - React
- react-native-compat (2.11.2): - react-native-compat (2.17.1):
- glog - glog
- RCT-Folly (= 2022.05.16.00) - RCT-Folly (= 2022.05.16.00)
- React-Core - React-Core
...@@ -2523,7 +2523,7 @@ SPEC CHECKSUMS: ...@@ -2523,7 +2523,7 @@ SPEC CHECKSUMS:
React-logger: 3eb80a977f0d9669468ef641a5e1fabbc50a09ec React-logger: 3eb80a977f0d9669468ef641a5e1fabbc50a09ec
React-Mapbuffer: 84ea43c6c6232049135b1550b8c60b2faac19fab React-Mapbuffer: 84ea43c6c6232049135b1550b8c60b2faac19fab
react-native-appsflyer: 2cc1f96348065fc23e976fc7a27e371789fb349e react-native-appsflyer: 2cc1f96348065fc23e976fc7a27e371789fb349e
react-native-compat: 3af9add14d349701306d3d052638435f6795ac2c react-native-compat: eddb3937dcc5dcf3a27ebc42564dc15a89415829
react-native-context-menu-view: dcec18eb8882e20596dbb75802e7d19cb87dac02 react-native-context-menu-view: dcec18eb8882e20596dbb75802e7d19cb87dac02
react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a
react-native-image-picker: 1569cfade34b3a011191ce262423e6ce2f8db5a1 react-native-image-picker: 1569cfade34b3a011191ce262423e6ce2f8db5a1
......
...@@ -24,6 +24,8 @@ jest.mock('@sentry/react-native', () => ({ ...@@ -24,6 +24,8 @@ jest.mock('@sentry/react-native', () => ({
ReactNativeTracing: jest.fn(), ReactNativeTracing: jest.fn(),
})) }))
jest.mock('@uniswap/client-explore/dist/uniswap/explore/v1/service-ExploreStatsService_connectquery', () => {})
// Disables animated driver warning // Disables animated driver warning
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper') jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper')
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
"link:assets": "react-native-asset", "link:assets": "react-native-asset",
"graphql:generate:swift": "cd ios && ./Pods/Apollo/apollo-ios-cli generate", "graphql:generate:swift": "cd ios && ./Pods/Apollo/apollo-ios-cli generate",
"hardhat": "hardhat node", "hardhat": "hardhat node",
"check:circular": "../../scripts/check-circular-imports.sh ./src/app/App.tsx 6", "check:circular": "../../scripts/check-circular-imports.sh ./src/app/App.tsx 10",
"ios": "yarn ios:prebuild && SKIP_BUNDLING=1 react-native run-ios", "ios": "yarn ios:prebuild && SKIP_BUNDLING=1 react-native run-ios",
"ios:prebuild": "yarn graphql:generate:swift && cd ios/WidgetsCore/MobileSchema && rm -rf !(README.md) && cd ../../.. && yarn graphql:generate:swift && yarn env:local:copy:swift", "ios:prebuild": "yarn graphql:generate:swift && cd ios/WidgetsCore/MobileSchema && rm -rf !(README.md) && cd ../../.. && yarn graphql:generate:swift && yarn env:local:copy:swift",
"ios:smol": "SKIP_BUNDLING=1 react-native run-ios --simulator=\"iPhone SE (3rd generation)\"", "ios:smol": "SKIP_BUNDLING=1 react-native run-ios --simulator=\"iPhone SE (3rd generation)\"",
...@@ -78,6 +78,7 @@ ...@@ -78,6 +78,7 @@
"@react-navigation/native-stack": "6.7.0", "@react-navigation/native-stack": "6.7.0",
"@react-navigation/stack": "6.2.2", "@react-navigation/stack": "6.2.2",
"@reduxjs/toolkit": "1.9.3", "@reduxjs/toolkit": "1.9.3",
"@reown/walletkit": "1.1.1",
"@sentry/react": "7.80.0", "@sentry/react": "7.80.0",
"@sentry/react-native": "5.5.0", "@sentry/react-native": "5.5.0",
"@shopify/flash-list": "1.6.3", "@shopify/flash-list": "1.6.3",
...@@ -87,12 +88,12 @@ ...@@ -87,12 +88,12 @@
"@sparkfabrik/react-native-idfa-aaid": "1.2.0", "@sparkfabrik/react-native-idfa-aaid": "1.2.0",
"@uniswap/analytics": "1.7.0", "@uniswap/analytics": "1.7.0",
"@uniswap/analytics-events": "2.38.0", "@uniswap/analytics-events": "2.38.0",
"@uniswap/client-explore": "0.0.10",
"@uniswap/ethers-rs-mobile": "0.0.5", "@uniswap/ethers-rs-mobile": "0.0.5",
"@uniswap/sdk-core": "5.8.0", "@uniswap/sdk-core": "5.8.3",
"@walletconnect/core": "2.11.2", "@walletconnect/core": "2.17.1",
"@walletconnect/react-native-compat": "2.11.2", "@walletconnect/react-native-compat": "2.17.1",
"@walletconnect/utils": "2.11.2", "@walletconnect/utils": "2.17.1",
"@walletconnect/web3wallet": "1.10.2",
"apollo3-cache-persist": "0.14.1", "apollo3-cache-persist": "0.14.1",
"babel-plugin-transform-inline-environment-variables": "0.4.4", "babel-plugin-transform-inline-environment-variables": "0.4.4",
"babel-plugin-transform-remove-console": "6.9.4", "babel-plugin-transform-remove-console": "6.9.4",
...@@ -167,7 +168,7 @@ ...@@ -167,7 +168,7 @@
"@testing-library/react-native": "11.5.0", "@testing-library/react-native": "11.5.0",
"@types/redux-mock-store": "1.0.6", "@types/redux-mock-store": "1.0.6",
"@uniswap/eslint-config": "workspace:^", "@uniswap/eslint-config": "workspace:^",
"@walletconnect/types": "2.11.2", "@walletconnect/types": "2.17.1",
"@welldone-software/why-did-you-render": "8.0.1", "@welldone-software/why-did-you-render": "8.0.1",
"babel-loader": "8.2.3", "babel-loader": "8.2.3",
"babel-plugin-module-resolver": "5.0.0", "babel-plugin-module-resolver": "5.0.0",
......
...@@ -64,6 +64,7 @@ import { loadStatsigOverrides } from 'uniswap/src/features/gating/overrides/cust ...@@ -64,6 +64,7 @@ import { loadStatsigOverrides } from 'uniswap/src/features/gating/overrides/cust
import { Statsig, StatsigOptions, StatsigProvider, StatsigUser } from 'uniswap/src/features/gating/sdk/statsig' import { Statsig, StatsigOptions, StatsigProvider, StatsigUser } from 'uniswap/src/features/gating/sdk/statsig'
import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext' import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext'
import { useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks' import { useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks'
import { clearNotificationQueue } from 'uniswap/src/features/notifications/slice'
import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice' import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
...@@ -85,7 +86,6 @@ import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApo ...@@ -85,7 +86,6 @@ import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApo
import { initFirebaseAppCheck } from 'wallet/src/features/appCheck/appCheck' import { initFirebaseAppCheck } from 'wallet/src/features/appCheck/appCheck'
import { useCurrentAppearanceSetting } from 'wallet/src/features/appearance/hooks' import { useCurrentAppearanceSetting } from 'wallet/src/features/appearance/hooks'
import { selectHapticsEnabled } from 'wallet/src/features/appearance/slice' import { selectHapticsEnabled } from 'wallet/src/features/appearance/slice'
import { clearNotificationQueue } from 'wallet/src/features/notifications/slice'
import { TransactionHistoryUpdater } from 'wallet/src/features/transactions/TransactionHistoryUpdater' import { TransactionHistoryUpdater } from 'wallet/src/features/transactions/TransactionHistoryUpdater'
import { WalletUniswapProvider } from 'wallet/src/features/transactions/contexts/WalletUniswapContext' import { WalletUniswapProvider } from 'wallet/src/features/transactions/contexts/WalletUniswapContext'
import { Account } from 'wallet/src/features/wallet/accounts/types' import { Account } from 'wallet/src/features/wallet/accounts/types'
......
...@@ -80,6 +80,7 @@ import { ...@@ -80,6 +80,7 @@ import {
v76Schema, v76Schema,
v77Schema, v77Schema,
v78Schema, v78Schema,
v79Schema,
v7Schema, v7Schema,
v8Schema, v8Schema,
v9Schema, v9Schema,
...@@ -95,6 +96,7 @@ import { AccountType } from 'uniswap/src/features/accounts/types' ...@@ -95,6 +96,7 @@ import { AccountType } from 'uniswap/src/features/accounts/types'
import { initialUniswapBehaviorHistoryState } from 'uniswap/src/features/behaviorHistory/slice' import { initialUniswapBehaviorHistoryState } from 'uniswap/src/features/behaviorHistory/slice'
import { initialFavoritesState } from 'uniswap/src/features/favorites/slice' import { initialFavoritesState } from 'uniswap/src/features/favorites/slice'
import { FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants' import { FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants'
import { initialNotificationsState } from 'uniswap/src/features/notifications/slice'
import { initialSearchHistoryState } from 'uniswap/src/features/search/searchHistorySlice' import { initialSearchHistoryState } from 'uniswap/src/features/search/searchHistorySlice'
import { initialUserSettingsState } from 'uniswap/src/features/settings/slice' import { initialUserSettingsState } from 'uniswap/src/features/settings/slice'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
...@@ -107,7 +109,6 @@ import { getAllKeysOfNestedObject } from 'utilities/src/primitives/objects' ...@@ -107,7 +109,6 @@ import { getAllKeysOfNestedObject } from 'utilities/src/primitives/objects'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { initialAppearanceSettingsState } from 'wallet/src/features/appearance/slice' import { initialAppearanceSettingsState } from 'wallet/src/features/appearance/slice'
import { initialBehaviorHistoryState } from 'wallet/src/features/behaviorHistory/slice' import { initialBehaviorHistoryState } from 'wallet/src/features/behaviorHistory/slice'
import { initialNotificationsState } from 'wallet/src/features/notifications/slice'
import { initialTelemetryState } from 'wallet/src/features/telemetry/slice' import { initialTelemetryState } from 'wallet/src/features/telemetry/slice'
import { Account, SignerMnemonicAccount } from 'wallet/src/features/wallet/accounts/types' import { Account, SignerMnemonicAccount } from 'wallet/src/features/wallet/accounts/types'
import { initialWalletState, SwapProtectionSetting } from 'wallet/src/features/wallet/slice' import { initialWalletState, SwapProtectionSetting } from 'wallet/src/features/wallet/slice'
...@@ -122,6 +123,7 @@ import { ...@@ -122,6 +123,7 @@ import {
testMovedTokenWarnings, testMovedTokenWarnings,
testMovedUserSettings, testMovedUserSettings,
testRemoveHoldToSwap, testRemoveHoldToSwap,
testUpdateExploreOrderByType,
} from 'wallet/src/state/walletMigrationsTests' } from 'wallet/src/state/walletMigrationsTests'
import { signerMnemonicAccount } from 'wallet/src/test/fixtures' import { signerMnemonicAccount } from 'wallet/src/test/fixtures'
...@@ -1580,4 +1582,8 @@ describe('Redux state migrations', () => { ...@@ -1580,4 +1582,8 @@ describe('Redux state migrations', () => {
it('migrates from v78 to v79', async () => { it('migrates from v78 to v79', async () => {
testMovedCurrencySetting(migrations[79], v78Schema) testMovedCurrencySetting(migrations[79], v78Schema)
}) })
it('migrates from v79 to v80', async () => {
testUpdateExploreOrderByType(migrations[80], v79Schema)
})
}) })
...@@ -35,6 +35,7 @@ import { ...@@ -35,6 +35,7 @@ import {
moveUserSettings, moveUserSettings,
removeUniconV2BehaviorState, removeUniconV2BehaviorState,
removeWalletIsUnlockedState, removeWalletIsUnlockedState,
updateExploreOrderByType,
} from 'wallet/src/state/walletMigrations' } from 'wallet/src/state/walletMigrations'
export const OLD_DEMO_ACCOUNT_ADDRESS = '0xdd0E380579dF30E38524F9477808d9eE37E2dEa6' export const OLD_DEMO_ACCOUNT_ADDRESS = '0xdd0E380579dF30E38524F9477808d9eE37E2dEa6'
...@@ -948,6 +949,8 @@ export const migrations = { ...@@ -948,6 +949,8 @@ export const migrations = {
78: moveLanguageSetting, 78: moveLanguageSetting,
79: moveCurrencySetting, 79: moveCurrencySetting,
80: updateExploreOrderByType,
} }
export const MOBILE_STATE_VERSION = 79 export const MOBILE_STATE_VERSION = 80
...@@ -3,6 +3,7 @@ import { FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants' ...@@ -3,6 +3,7 @@ import { FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants'
import { Language } from 'uniswap/src/features/language/constants' import { Language } from 'uniswap/src/features/language/constants'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice' import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice'
import { RankingType } from 'wallet/src/features/wallet/types'
// only add fields that are persisted // only add fields that are persisted
export const initialSchema = { export const initialSchema = {
...@@ -615,6 +616,17 @@ const v79SchemaIntermediate = { ...@@ -615,6 +616,17 @@ const v79SchemaIntermediate = {
delete v79SchemaIntermediate.fiatCurrencySettings delete v79SchemaIntermediate.fiatCurrencySettings
export const v79Schema = v79SchemaIntermediate export const v79Schema = v79SchemaIntermediate
export const v80Schema = {
...v79Schema,
wallet: {
...v79Schema.wallet,
settings: {
...v79Schema.wallet.settings,
tokensOrderBy: RankingType.Volume,
},
},
}
// TODO: [MOB-201] use function with typed output when API reducers are removed from rootReducer // TODO: [MOB-201] use function with typed output when API reducers are removed from rootReducer
// export const getSchema = (): RootState => v0Schema // export const getSchema = (): RootState => v0Schema
export const getSchema = (): typeof v79Schema => v79Schema export const getSchema = (): typeof v80Schema => v80Schema
...@@ -2,7 +2,6 @@ import React, { useState } from 'react' ...@@ -2,7 +2,6 @@ import React, { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Alert } from 'react-native' import { Alert } from 'react-native'
import 'react-native-reanimated' import 'react-native-reanimated'
import { useSelector } from 'react-redux'
import { QRCodeScanner } from 'src/components/QRCodeScanner/QRCodeScanner' import { QRCodeScanner } from 'src/components/QRCodeScanner/QRCodeScanner'
import { getSupportedURI, URIType } from 'src/components/Requests/ScanSheet/util' import { getSupportedURI, URIType } from 'src/components/Requests/ScanSheet/util'
import { Flex, Text, TouchableArea, useHapticFeedback, useIsDarkMode, useSporeColors } from 'ui/src' import { Flex, Text, TouchableArea, useHapticFeedback, useIsDarkMode, useSporeColors } from 'ui/src'
...@@ -14,7 +13,7 @@ import { ModalName } from 'uniswap/src/features/telemetry/constants' ...@@ -14,7 +13,7 @@ import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { WalletQRCode } from 'wallet/src/components/QRCodeScanner/WalletQRCode' import { WalletQRCode } from 'wallet/src/components/QRCodeScanner/WalletQRCode'
import { selectActiveAccountAddress } from 'wallet/src/features/wallet/selectors' import { useActiveAccountAddress } from 'wallet/src/features/wallet/hooks'
type Props = { type Props = {
onClose: () => void onClose: () => void
...@@ -24,7 +23,7 @@ type Props = { ...@@ -24,7 +23,7 @@ type Props = {
export function RecipientScanModal({ onSelectRecipient, onClose }: Props): JSX.Element { export function RecipientScanModal({ onSelectRecipient, onClose }: Props): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const colors = useSporeColors() const colors = useSporeColors()
const activeAddress = useSelector(selectActiveAccountAddress) const activeAddress = useActiveAccountAddress()
const [currentScreenState, setCurrentScreenState] = useState<ScannerModalState>(ScannerModalState.ScanQr) const [currentScreenState, setCurrentScreenState] = useState<ScannerModalState>(ScannerModalState.ScanQr)
const [shouldFreezeCamera, setShouldFreezeCamera] = useState(false) const [shouldFreezeCamera, setShouldFreezeCamera] = useState(false)
const { hapticFeedback } = useHapticFeedback() const { hapticFeedback } = useHapticFeedback()
......
...@@ -11,12 +11,12 @@ import { iconSizes } from 'ui/src/theme' ...@@ -11,12 +11,12 @@ import { iconSizes } from 'ui/src/theme'
import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { WalletConnectEvent } from 'uniswap/src/types/walletConnect'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
interface DappConnectedNetworkModalProps { interface DappConnectedNetworkModalProps {
session: WalletConnectSession session: WalletConnectSession
......
...@@ -13,12 +13,12 @@ import { disableOnPress } from 'src/utils/disableOnPress' ...@@ -13,12 +13,12 @@ import { disableOnPress } from 'src/utils/disableOnPress'
import { AnimatedTouchableArea, Flex, ImpactFeedbackStyle, Text, TouchableArea } from 'ui/src' import { AnimatedTouchableArea, Flex, ImpactFeedbackStyle, Text, TouchableArea } from 'ui/src'
import { iconSizes, spacing } from 'ui/src/theme' import { iconSizes, spacing } from 'ui/src/theme'
import { NetworkLogos } from 'uniswap/src/components/network/NetworkLogos' import { NetworkLogos } from 'uniswap/src/components/network/NetworkLogos'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { WalletConnectEvent } from 'uniswap/src/types/walletConnect'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
export function DappConnectionItem({ export function DappConnectionItem({
......
...@@ -17,7 +17,11 @@ import { returnToPreviousApp } from 'src/features/walletConnect/WalletConnect' ...@@ -17,7 +17,11 @@ import { returnToPreviousApp } from 'src/features/walletConnect/WalletConnect'
import { wcWeb3Wallet } from 'src/features/walletConnect/saga' import { wcWeb3Wallet } from 'src/features/walletConnect/saga'
import { selectDidOpenFromDeepLink } from 'src/features/walletConnect/selectors' import { selectDidOpenFromDeepLink } from 'src/features/walletConnect/selectors'
import { signWcRequestActions } from 'src/features/walletConnect/signWcRequestSaga' import { signWcRequestActions } from 'src/features/walletConnect/signWcRequestSaga'
import { WalletConnectRequest, isTransactionRequest } from 'src/features/walletConnect/walletConnectSlice' import {
WalletConnectRequest,
isTransactionRequest,
setDidOpenFromDeepLink,
} from 'src/features/walletConnect/walletConnectSlice'
import { useTransactionGasFee } from 'uniswap/src/features/gas/hooks' import { useTransactionGasFee } from 'uniswap/src/features/gas/hooks'
import { MobileEventName, ModalName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
...@@ -131,7 +135,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -131,7 +135,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
onClose() onClose()
if (didOpenFromDeepLink) { if (didOpenFromDeepLink) {
returnToPreviousApp() await returnToPreviousApp()
setDidOpenFromDeepLink(false)
} }
} }
...@@ -190,7 +195,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -190,7 +195,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
onClose() onClose()
if (didOpenFromDeepLink) { if (didOpenFromDeepLink) {
returnToPreviousApp() await returnToPreviousApp()
setDidOpenFromDeepLink(false)
} }
} }
......
...@@ -16,18 +16,19 @@ import { ...@@ -16,18 +16,19 @@ import {
WalletConnectPendingSession, WalletConnectPendingSession,
addSession, addSession,
removePendingSession, removePendingSession,
setDidOpenFromDeepLink,
} from 'src/features/walletConnect/walletConnectSlice' } from 'src/features/walletConnect/walletConnectSlice'
import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import { Check, RotatableChevron, X } from 'ui/src/components/icons' import { Check, RotatableChevron, X } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { MobileEventName, ModalName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { WCEventType, WCRequestOutcome, WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { WCEventType, WCRequestOutcome, WalletConnectEvent } from 'uniswap/src/types/walletConnect'
import { formatDappURL } from 'utilities/src/format/urls' import { formatDappURL } from 'utilities/src/format/urls'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { AddressFooter } from 'wallet/src/features/transactions/TransactionRequest/AddressFooter' import { AddressFooter } from 'wallet/src/features/transactions/TransactionRequest/AddressFooter'
import { import {
useActiveAccountAddressWithThrow, useActiveAccountAddressWithThrow,
...@@ -198,7 +199,8 @@ export const PendingConnectionModal = ({ pendingSession, onClose }: Props): JSX. ...@@ -198,7 +199,8 @@ export const PendingConnectionModal = ({ pendingSession, onClose }: Props): JSX.
onClose() onClose()
if (didOpenFromDeepLink) { if (didOpenFromDeepLink) {
returnToPreviousApp() await returnToPreviousApp()
setDidOpenFromDeepLink(false)
} }
}, },
[activeAddress, dispatch, onClose, pendingSession, didOpenFromDeepLink], [activeAddress, dispatch, onClose, pendingSession, didOpenFromDeepLink],
......
...@@ -5,6 +5,8 @@ import { useDispatch } from 'react-redux' ...@@ -5,6 +5,8 @@ import { useDispatch } from 'react-redux'
import { Flex, IconProps, Text, TouchableArea, useSporeColors } from 'ui/src' import { Flex, IconProps, Text, TouchableArea, useSporeColors } from 'ui/src'
import CopyIcon from 'ui/src/assets/icons/copy-sheets.svg' import CopyIcon from 'ui/src/assets/icons/copy-sheets.svg'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'uniswap/src/features/notifications/types'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName, ElementNameType } from 'uniswap/src/features/telemetry/constants' import { ElementName, ElementNameType } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
...@@ -12,8 +14,6 @@ import { TestIDType } from 'uniswap/src/test/fixtures/testIDs' ...@@ -12,8 +14,6 @@ import { TestIDType } from 'uniswap/src/test/fixtures/testIDs'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { setClipboard } from 'uniswap/src/utils/clipboard' import { setClipboard } from 'uniswap/src/utils/clipboard'
import { openUri } from 'uniswap/src/utils/linking' import { openUri } from 'uniswap/src/utils/linking'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'wallet/src/features/notifications/types'
export enum LinkButtonType { export enum LinkButtonType {
Copy = 'copy', Copy = 'copy',
......
...@@ -10,6 +10,8 @@ import { disableOnPress } from 'src/utils/disableOnPress' ...@@ -10,6 +10,8 @@ import { disableOnPress } from 'src/utils/disableOnPress'
import { Flex, Text, TouchableArea, useHapticFeedback } from 'ui/src' import { Flex, Text, TouchableArea, useHapticFeedback } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'uniswap/src/features/notifications/types'
import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
...@@ -17,8 +19,6 @@ import { setClipboard } from 'uniswap/src/utils/clipboard' ...@@ -17,8 +19,6 @@ import { setClipboard } from 'uniswap/src/utils/clipboard'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { useAccountList } from 'wallet/src/features/accounts/hooks' import { useAccountList } from 'wallet/src/features/accounts/hooks'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'wallet/src/features/notifications/types'
type AccountCardItemProps = { type AccountCardItemProps = {
address: Address address: Address
......
...@@ -2,13 +2,15 @@ import { SharedEventName } from '@uniswap/analytics-events' ...@@ -2,13 +2,15 @@ import { SharedEventName } from '@uniswap/analytics-events'
import React, { useCallback, useEffect } from 'react' import React, { useCallback, useEffect } from 'react'
import { Gesture, GestureDetector, State } from 'react-native-gesture-handler' import { Gesture, GestureDetector, State } from 'react-native-gesture-handler'
import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withDelay, withTiming } from 'react-native-reanimated' import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withDelay, withTiming } from 'react-native-reanimated'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
import { openModal } from 'src/features/modals/modalSlice' import { openModal } from 'src/features/modals/modalSlice'
import { Flex, ImpactFeedbackStyle, Text, TouchableArea, useHapticFeedback } from 'ui/src' import { Flex, ImpactFeedbackStyle, Text, TouchableArea, useHapticFeedback } from 'ui/src'
import { CopyAlt, Settings } from 'ui/src/components/icons' import { CopyAlt, Settings } from 'ui/src/components/icons'
import { AccountType } from 'uniswap/src/features/accounts/types' import { AccountType } from 'uniswap/src/features/accounts/types'
import { useAvatar } from 'uniswap/src/features/address/avatar' import { useAvatar } from 'uniswap/src/features/address/avatar'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'uniswap/src/features/notifications/types'
import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { MobileUserPropertyName, setUserProperty } from 'uniswap/src/features/telemetry/user' import { MobileUserPropertyName, setUserProperty } from 'uniswap/src/features/telemetry/user'
...@@ -20,10 +22,7 @@ import { isDevEnv } from 'utilities/src/environment/env' ...@@ -20,10 +22,7 @@ import { isDevEnv } from 'utilities/src/environment/env'
import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon' import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon'
import { AnimatedUnitagDisplayName } from 'wallet/src/components/accounts/AnimatedUnitagDisplayName' import { AnimatedUnitagDisplayName } from 'wallet/src/components/accounts/AnimatedUnitagDisplayName'
import useIsFocused from 'wallet/src/features/focus/useIsFocused' import useIsFocused from 'wallet/src/features/focus/useIsFocused'
import { pushNotification } from 'wallet/src/features/notifications/slice' import { useActiveAccount, useActiveAccountAddress, useDisplayName } from 'wallet/src/features/wallet/hooks'
import { AppNotificationType, CopyNotificationType } from 'wallet/src/features/notifications/types'
import { useDisplayName } from 'wallet/src/features/wallet/hooks'
import { selectActiveAccount, selectActiveAccountAddress } from 'wallet/src/features/wallet/selectors'
import { DisplayNameType } from 'wallet/src/features/wallet/types' import { DisplayNameType } from 'wallet/src/features/wallet/types'
const RotatingSettingsIcon = ({ onPressSettings }: { onPressSettings(): void }): JSX.Element => { const RotatingSettingsIcon = ({ onPressSettings }: { onPressSettings(): void }): JSX.Element => {
...@@ -68,8 +67,8 @@ const RotatingSettingsIcon = ({ onPressSettings }: { onPressSettings(): void }): ...@@ -68,8 +67,8 @@ const RotatingSettingsIcon = ({ onPressSettings }: { onPressSettings(): void }):
} }
export function AccountHeader(): JSX.Element { export function AccountHeader(): JSX.Element {
const activeAddress = useSelector(selectActiveAccountAddress) const activeAddress = useActiveAccountAddress()
const account = useSelector(selectActiveAccount) const account = useActiveAccount()
const dispatch = useDispatch() const dispatch = useDispatch()
const { hapticFeedback } = useHapticFeedback() const { hapticFeedback } = useHapticFeedback()
......
...@@ -28,11 +28,7 @@ export function Carousel({ slides, ...flatListProps }: CarouselProps): JSX.Eleme ...@@ -28,11 +28,7 @@ export function Carousel({ slides, ...flatListProps }: CarouselProps): JSX.Eleme
const { fullWidth } = useDeviceDimensions() const { fullWidth } = useDeviceDimensions()
const myRef = useRef<Animated.FlatList<unknown>>(null) const myRef = useRef<Animated.FlatList<unknown>>(null)
const scrollHandler = useAnimatedScrollHandler({ const scrollHandler = useAnimatedScrollHandler((event) => (scroll.value = event.contentOffset.x), [scroll])
onScroll: (event) => {
scroll.value = event.contentOffset.x
},
})
const goToNext = useCallback(() => { const goToNext = useCallback(() => {
// @ts-expect-error https://github.com/software-mansion/react-native-reanimated/issues/2976 // @ts-expect-error https://github.com/software-mansion/react-native-reanimated/issues/2976
......
...@@ -10,10 +10,12 @@ export function FavoriteHeaderRow({ ...@@ -10,10 +10,12 @@ export function FavoriteHeaderRow({
editingTitle, editingTitle,
isEditing, isEditing,
onPress, onPress,
disabled,
}: { }: {
title: string title: string
editingTitle: string editingTitle: string
isEditing: boolean isEditing: boolean
disabled?: boolean
onPress: () => void onPress: () => void
}): JSX.Element { }): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -23,7 +25,7 @@ export function FavoriteHeaderRow({ ...@@ -23,7 +25,7 @@ export function FavoriteHeaderRow({
{isEditing ? editingTitle : title} {isEditing ? editingTitle : title}
</Text> </Text>
{!isEditing ? ( {!isEditing ? (
<TouchableArea hapticFeedback hitSlop={16} testID={TestID.Edit} onPress={onPress}> <TouchableArea hapticFeedback hitSlop={16} testID={TestID.Edit} disabled={disabled} onPress={onPress}>
<Ellipsis color="$neutral2" size={iconSizes.icon20} strokeLinecap="round" strokeWidth={1} /> <Ellipsis color="$neutral2" size={iconSizes.icon20} strokeLinecap="round" strokeWidth={1} />
</TouchableArea> </TouchableArea>
) : ( ) : (
......
...@@ -9,10 +9,17 @@ import { useAnimatedCardDragStyle, useExploreTokenContextMenu } from 'src/compon ...@@ -9,10 +9,17 @@ import { useAnimatedCardDragStyle, useExploreTokenContextMenu } from 'src/compon
import { Loader } from 'src/components/loading/loaders' import { Loader } from 'src/components/loading/loaders'
import { disableOnPress } from 'src/utils/disableOnPress' import { disableOnPress } from 'src/utils/disableOnPress'
import { usePollOnFocusOnly } from 'src/utils/hooks' import { usePollOnFocusOnly } from 'src/utils/hooks'
import { AnimatedTouchableArea, Flex, ImpactFeedbackStyle, Text } from 'ui/src' import {
AnimatedTouchableArea,
Flex,
ImpactFeedbackStyle,
Text,
useIsDarkMode,
useShadowPropsShort,
useSporeColors,
} from 'ui/src'
import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { borderRadii, imageSizes } from 'ui/src/theme' import { borderRadii, imageSizes, opacify } from 'ui/src/theme'
import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard'
import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo' import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo'
import { PollingInterval } from 'uniswap/src/constants/misc' import { PollingInterval } from 'uniswap/src/constants/misc'
import { useFavoriteTokenCardQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { useFavoriteTokenCardQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
...@@ -50,6 +57,9 @@ function FavoriteTokenCard({ ...@@ -50,6 +57,9 @@ function FavoriteTokenCard({
const tokenDetailsNavigation = useTokenDetailsNavigation() const tokenDetailsNavigation = useTokenDetailsNavigation()
const { convertFiatAmountFormatted } = useLocalizationContext() const { convertFiatAmountFormatted } = useLocalizationContext()
const colors = useSporeColors()
const isDarkMode = useIsDarkMode()
const { data, networkStatus, startPolling, stopPolling } = useFavoriteTokenCardQuery({ const { data, networkStatus, startPolling, stopPolling } = useFavoriteTokenCardQuery({
variables: currencyIdToContractInput(currencyId), variables: currencyIdToContractInput(currencyId),
// Rely on cache for fast favoriting UX, and poll for updates. // Rely on cache for fast favoriting UX, and poll for updates.
...@@ -94,12 +104,14 @@ function FavoriteTokenCard({ ...@@ -94,12 +104,14 @@ function FavoriteTokenCard({
const animatedDragStyle = useAnimatedCardDragStyle(pressProgress, dragActivationProgress) const animatedDragStyle = useAnimatedCardDragStyle(pressProgress, dragActivationProgress)
const shadowProps = useShadowPropsShort()
if (isNonPollingRequestInFlight(networkStatus)) { if (isNonPollingRequestInFlight(networkStatus)) {
return <Loader.Favorite height={FAVORITE_TOKEN_CARD_LOADER_HEIGHT} /> return <Loader.Favorite height={FAVORITE_TOKEN_CARD_LOADER_HEIGHT} />
} }
return ( return (
<AnimatedFlex style={animatedDragStyle}> <AnimatedFlex borderRadius="$rounded16" style={animatedDragStyle}>
<ContextMenu <ContextMenu
actions={menuActions} actions={menuActions}
disabled={isEditing} disabled={isEditing}
...@@ -109,8 +121,10 @@ function FavoriteTokenCard({ ...@@ -109,8 +121,10 @@ function FavoriteTokenCard({
> >
<AnimatedTouchableArea <AnimatedTouchableArea
activeOpacity={isEditing ? 1 : undefined} activeOpacity={isEditing ? 1 : undefined}
backgroundColor="$surface2" backgroundColor={isDarkMode ? '$surface2' : '$surface1'}
borderColor={opacify(0.05, colors.surface3.val)}
borderRadius="$rounded16" borderRadius="$rounded16"
borderWidth={isDarkMode ? '$none' : '$spacing1'}
entering={FadeIn} entering={FadeIn}
hapticFeedback={!isEditing} hapticFeedback={!isEditing}
hapticStyle={ImpactFeedbackStyle.Light} hapticStyle={ImpactFeedbackStyle.Light}
...@@ -118,35 +132,34 @@ function FavoriteTokenCard({ ...@@ -118,35 +132,34 @@ function FavoriteTokenCard({
testID={`token-box-${token?.symbol}`} testID={`token-box-${token?.symbol}`}
onLongPress={disableOnPress} onLongPress={disableOnPress}
onPress={onPress} onPress={onPress}
{...shadowProps}
> >
<BaseCard.Shadow> <Flex alignItems="flex-start" gap="$spacing8" p="$spacing12">
<Flex alignItems="flex-start" gap="$spacing8"> <Flex row gap="$spacing4" justifyContent="space-between">
<Flex row gap="$spacing4" justifyContent="space-between"> <Flex grow row alignItems="center" gap="$spacing8">
<Flex grow row alignItems="center" gap="$spacing8"> <TokenLogo
<TokenLogo chainId={chainId ?? undefined}
chainId={chainId ?? undefined} name={token?.name ?? undefined}
name={token?.name ?? undefined} size={imageSizes.image20}
size={imageSizes.image20} symbol={token?.symbol ?? undefined}
symbol={token?.symbol ?? undefined} url={token?.project?.logoUrl ?? undefined}
url={token?.project?.logoUrl ?? undefined}
/>
<Text variant="body1">{getSymbolDisplayText(token?.symbol)}</Text>
</Flex>
<RemoveButton visible={isEditing} onPress={onRemove} />
</Flex>
<Flex gap="$spacing2">
<Text adjustsFontSizeToFit numberOfLines={1} variant="heading3">
{price}
</Text>
<RelativeChange
arrowSize="$icon.16"
change={pricePercentChange ?? undefined}
semanticColor={true}
variant="subheading2"
/> />
<Text variant="body1">{getSymbolDisplayText(token?.symbol)}</Text>
</Flex> </Flex>
<RemoveButton visible={isEditing} onPress={onRemove} />
</Flex>
<Flex gap="$spacing2">
<Text adjustsFontSizeToFit numberOfLines={1} variant="heading3">
{price}
</Text>
<RelativeChange
arrowSize="$icon.16"
change={pricePercentChange ?? undefined}
semanticColor={true}
variant="subheading2"
/>
</Flex> </Flex>
</BaseCard.Shadow> </Flex>
</AnimatedTouchableArea> </AnimatedTouchableArea>
</ContextMenu> </ContextMenu>
</AnimatedFlex> </AnimatedFlex>
......
...@@ -63,6 +63,7 @@ export function FavoriteTokensGrid({ showLoading, ...rest }: FavoriteTokensGridP ...@@ -63,6 +63,7 @@ export function FavoriteTokensGrid({ showLoading, ...rest }: FavoriteTokensGridP
return ( return (
<AnimatedFlex entering={FadeIn} style={animatedStyle}> <AnimatedFlex entering={FadeIn} style={animatedStyle}>
<FavoriteHeaderRow <FavoriteHeaderRow
disabled={showLoading}
editingTitle={t('explore.tokens.favorite.title.edit')} editingTitle={t('explore.tokens.favorite.title.edit')}
isEditing={isEditing} isEditing={isEditing}
title={t('explore.tokens.favorite.title.default')} title={t('explore.tokens.favorite.title.default')}
......
...@@ -8,10 +8,9 @@ import { useEagerExternalProfileNavigation } from 'src/app/navigation/hooks' ...@@ -8,10 +8,9 @@ import { useEagerExternalProfileNavigation } from 'src/app/navigation/hooks'
import RemoveButton from 'src/components/explore/RemoveButton' import RemoveButton from 'src/components/explore/RemoveButton'
import { useAnimatedCardDragStyle } from 'src/components/explore/hooks' import { useAnimatedCardDragStyle } from 'src/components/explore/hooks'
import { disableOnPress } from 'src/utils/disableOnPress' import { disableOnPress } from 'src/utils/disableOnPress'
import { Flex, ImpactFeedbackStyle, TouchableArea } from 'ui/src' import { Flex, ImpactFeedbackStyle, TouchableArea, useIsDarkMode, useShadowPropsShort, useSporeColors } from 'ui/src'
import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { borderRadii, iconSizes } from 'ui/src/theme' import { borderRadii, iconSizes, opacify } from 'ui/src/theme'
import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard'
import { useAvatar } from 'uniswap/src/features/address/avatar' import { useAvatar } from 'uniswap/src/features/address/avatar'
import { removeWatchedAddress } from 'uniswap/src/features/favorites/slice' import { removeWatchedAddress } from 'uniswap/src/features/favorites/slice'
import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon' import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon'
...@@ -37,6 +36,9 @@ function FavoriteWalletCard({ ...@@ -37,6 +36,9 @@ function FavoriteWalletCard({
}: FavoriteWalletCardProps): JSX.Element { }: FavoriteWalletCardProps): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const dispatch = useDispatch() const dispatch = useDispatch()
const colors = useSporeColors()
const isDarkMode = useIsDarkMode()
const { preload, navigate } = useEagerExternalProfileNavigation() const { preload, navigate } = useEagerExternalProfileNavigation()
const displayName = useDisplayName(address) const displayName = useDisplayName(address)
...@@ -60,6 +62,8 @@ function FavoriteWalletCard({ ...@@ -60,6 +62,8 @@ function FavoriteWalletCard({
const animatedDragStyle = useAnimatedCardDragStyle(pressProgress, dragActivationProgress) const animatedDragStyle = useAnimatedCardDragStyle(pressProgress, dragActivationProgress)
const shadowProps = useShadowPropsShort()
return ( return (
<AnimatedFlex style={animatedDragStyle}> <AnimatedFlex style={animatedDragStyle}>
<ContextMenu <ContextMenu
...@@ -82,8 +86,10 @@ function FavoriteWalletCard({ ...@@ -82,8 +86,10 @@ function FavoriteWalletCard({
<TouchableArea <TouchableArea
hapticFeedback hapticFeedback
activeOpacity={isEditing ? 1 : undefined} activeOpacity={isEditing ? 1 : undefined}
backgroundColor="$surface2" backgroundColor={isDarkMode ? '$surface2' : '$surface1'}
borderColor={opacify(0.05, colors.surface3.val)}
borderRadius="$rounded16" borderRadius="$rounded16"
borderWidth={isDarkMode ? '$none' : '$spacing1'}
disabled={isEditing} disabled={isEditing}
hapticStyle={ImpactFeedbackStyle.Light} hapticStyle={ImpactFeedbackStyle.Light}
m="$spacing4" m="$spacing4"
...@@ -95,22 +101,21 @@ function FavoriteWalletCard({ ...@@ -95,22 +101,21 @@ function FavoriteWalletCard({
onPressIn={async (): Promise<void> => { onPressIn={async (): Promise<void> => {
await preload(address) await preload(address)
}} }}
{...shadowProps}
> >
<BaseCard.Shadow> <Flex row gap="$spacing4" justifyContent="space-between" p="$spacing12">
<Flex row gap="$spacing4" justifyContent="space-between"> <Flex row shrink alignItems="center" gap="$spacing8">
<Flex row shrink alignItems="center" gap="$spacing8"> {icon}
{icon} <DisplayNameText
<DisplayNameText displayName={displayName}
displayName={displayName} textProps={{
textProps={{ adjustsFontSizeToFit: displayName?.type === DisplayNameType.Address,
adjustsFontSizeToFit: displayName?.type === DisplayNameType.Address, variant: 'body1',
variant: 'body1', }}
}} />
/>
</Flex>
<RemoveButton visible={isEditing} onPress={onRemove} />
</Flex> </Flex>
</BaseCard.Shadow> <RemoveButton visible={isEditing} onPress={onRemove} />
</Flex>
</TouchableArea> </TouchableArea>
</ContextMenu> </ContextMenu>
</AnimatedFlex> </AnimatedFlex>
......
...@@ -67,6 +67,7 @@ export function FavoriteWalletsGrid({ showLoading, ...rest }: FavoriteWalletsGri ...@@ -67,6 +67,7 @@ export function FavoriteWalletsGrid({ showLoading, ...rest }: FavoriteWalletsGri
editingTitle={t('explore.wallets.favorite.title.edit')} editingTitle={t('explore.wallets.favorite.title.edit')}
isEditing={isEditing} isEditing={isEditing}
title={t('explore.wallets.favorite.title.default')} title={t('explore.wallets.favorite.title.default')}
disabled={showLoading}
onPress={(): void => setIsEditing(!isEditing)} onPress={(): void => setIsEditing(!isEditing)}
/> />
{showLoading ? ( {showLoading ? (
......
import { SortButton } from 'src/components/explore/SortButton' import { SortButton } from 'src/components/explore/SortButton'
import { render } from 'src/test/test-utils' import { render } from 'src/test/test-utils'
import { TokenSortableField } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { CustomRankingType, ExploreOrderBy, RankingType } from 'wallet/src/features/wallet/types'
import { ClientTokensOrderBy } from 'wallet/src/features/wallet/types'
jest.mock('react-native-context-menu-view', () => { jest.mock('react-native-context-menu-view', () => {
// Use the actual implementation of `react-native-context-menu-view` as the mock implementation // Use the actual implementation of `react-native-context-menu-view` as the mock implementation
...@@ -11,23 +10,23 @@ jest.mock('react-native-context-menu-view', () => { ...@@ -11,23 +10,23 @@ jest.mock('react-native-context-menu-view', () => {
describe('SortButton', () => { describe('SortButton', () => {
it('renders without error', () => { it('renders without error', () => {
const tree = render(<SortButton orderBy={TokenSortableField.Volume} />) const tree = render(<SortButton orderBy={RankingType.Volume} />)
expect(tree).toMatchSnapshot() expect(tree).toMatchSnapshot()
}) })
const cases = [ const cases: Array<{ test: string; orderBy: ExploreOrderBy; label: string }> = [
{ test: 'volume', orderBy: TokenSortableField.Volume, label: 'Volume' }, { test: 'volume', orderBy: RankingType.Volume, label: 'Volume' },
{ test: 'total value locked', orderBy: TokenSortableField.TotalValueLocked, label: 'TVL' }, { test: 'total value locked', orderBy: RankingType.TotalValueLocked, label: 'TVL' },
{ test: 'market cap', orderBy: TokenSortableField.MarketCap, label: 'Market cap' }, { test: 'market cap', orderBy: RankingType.MarketCap, label: 'Market cap' },
{ {
test: 'price increase', test: 'price increase',
orderBy: ClientTokensOrderBy.PriceChangePercentage24hDesc, orderBy: CustomRankingType.PricePercentChange1DayDesc,
label: 'Price increase', label: 'Price increase',
}, },
{ {
test: 'price decrease', test: 'price decrease',
orderBy: ClientTokensOrderBy.PriceChangePercentage24hAsc, orderBy: CustomRankingType.PricePercentChange1DayAsc,
label: 'Price decrease', label: 'Price decrease',
}, },
] ]
......
...@@ -2,61 +2,85 @@ import React, { memo, useCallback, useMemo } from 'react' ...@@ -2,61 +2,85 @@ import React, { memo, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { getTokensOrderByMenuLabel, getTokensOrderBySelectedLabel } from 'src/features/explore/utils' import { getTokensOrderByMenuLabel, getTokensOrderBySelectedLabel } from 'src/features/explore/utils'
import { Flex, Text, useIsDarkMode } from 'ui/src' import { Flex, Text, useSporeColors } from 'ui/src'
import { RotatableChevron } from 'ui/src/components/icons' import {
Chart,
ChartPie,
ChartPyramid,
CheckCircleFilled,
RotatableChevron,
TrendDown,
TrendUp,
} from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { ActionSheetDropdown } from 'uniswap/src/components/dropdowns/ActionSheetDropdown' import { ActionSheetDropdown } from 'uniswap/src/components/dropdowns/ActionSheetDropdown'
import { MenuItemProp } from 'uniswap/src/components/modals/ActionSheetModal' import { MenuItemProp } from 'uniswap/src/components/modals/ActionSheetModal'
import { TokenSortableField } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { setTokensOrderBy } from 'wallet/src/features/wallet/slice' import { setTokensOrderBy } from 'wallet/src/features/wallet/slice'
import { ClientTokensOrderBy, TokensOrderBy } from 'wallet/src/features/wallet/types' import { CustomRankingType, ExploreOrderBy, RankingType } from 'wallet/src/features/wallet/types'
const MIN_MENU_ITEM_WIDTH = 220
interface FilterGroupProps { interface FilterGroupProps {
orderBy: TokensOrderBy orderBy: ExploreOrderBy
} }
function _SortButton({ orderBy }: FilterGroupProps): JSX.Element { function _SortButton({ orderBy }: FilterGroupProps): JSX.Element {
const isDarkMode = useIsDarkMode()
const dispatch = useDispatch() const dispatch = useDispatch()
const { t } = useTranslation() const { t } = useTranslation()
const colors = useSporeColors()
const menuActions = useMemo(() => { const menuActions = useMemo(() => {
return [ return [
{ {
title: getTokensOrderByMenuLabel(TokenSortableField.Volume, t), title: getTokensOrderByMenuLabel(RankingType.Volume, t),
systemIcon: orderBy === TokenSortableField.Volume ? 'checkmark' : '', orderBy: RankingType.Volume,
orderBy: TokenSortableField.Volume, icon: <Chart color={colors.neutral2.val} size={iconSizes.icon16} />,
active: orderBy === RankingType.Volume,
}, },
{ {
title: getTokensOrderByMenuLabel(TokenSortableField.TotalValueLocked, t), title: getTokensOrderByMenuLabel(RankingType.TotalValueLocked, t),
systemIcon: orderBy === TokenSortableField.TotalValueLocked ? 'checkmark' : '', orderBy: RankingType.TotalValueLocked,
orderBy: TokenSortableField.TotalValueLocked, icon: <ChartPyramid color={colors.neutral2.val} size={iconSizes.icon16} />,
active: orderBy === RankingType.TotalValueLocked,
}, },
{ {
title: getTokensOrderByMenuLabel(TokenSortableField.MarketCap, t), title: getTokensOrderByMenuLabel(RankingType.MarketCap, t),
systemIcon: orderBy === TokenSortableField.MarketCap ? 'checkmark' : '', orderBy: RankingType.MarketCap,
orderBy: TokenSortableField.MarketCap, icon: <ChartPie color={colors.neutral2.val} size={iconSizes.icon16} />,
active: orderBy === RankingType.MarketCap,
}, },
{ {
title: getTokensOrderByMenuLabel(ClientTokensOrderBy.PriceChangePercentage24hDesc, t), title: getTokensOrderByMenuLabel(CustomRankingType.PricePercentChange1DayDesc, t),
systemIcon: orderBy === ClientTokensOrderBy.PriceChangePercentage24hDesc ? 'checkmark' : '', orderBy: CustomRankingType.PricePercentChange1DayDesc,
orderBy: ClientTokensOrderBy.PriceChangePercentage24hDesc, icon: <TrendUp color={colors.neutral2.val} size={iconSizes.icon16} />,
active: orderBy === CustomRankingType.PricePercentChange1DayDesc,
}, },
{ {
title: getTokensOrderByMenuLabel(ClientTokensOrderBy.PriceChangePercentage24hAsc, t), title: getTokensOrderByMenuLabel(CustomRankingType.PricePercentChange1DayAsc, t),
systemIcon: orderBy === ClientTokensOrderBy.PriceChangePercentage24hAsc ? 'checkmark' : '', orderBy: CustomRankingType.PricePercentChange1DayAsc,
orderBy: ClientTokensOrderBy.PriceChangePercentage24hAsc, icon: <TrendDown color={colors.neutral2.val} size={iconSizes.icon16} />,
active: orderBy === CustomRankingType.PricePercentChange1DayAsc,
}, },
] ]
}, [t, orderBy]) }, [t, colors.neutral2.val, orderBy])
const MenuItem = useCallback(({ label }: { label: string }) => { const MenuItem = useCallback(({ label, icon, active }: { label: string; icon: JSX.Element; active: boolean }) => {
return ( return (
<Flex grow style={{ padding: 5 }}> <Flex
grow
row
alignItems="center"
gap="$spacing8"
minWidth={MIN_MENU_ITEM_WIDTH}
py="$spacing8"
style={{ padding: 5 }}
>
{icon && icon}
<Text>{label}</Text> <Text>{label}</Text>
{active && <CheckCircleFilled color="$neutral1" size="$icon.16" />}
</Flex> </Flex>
) )
}, []) }, [])
...@@ -78,7 +102,7 @@ function _SortButton({ orderBy }: FilterGroupProps): JSX.Element { ...@@ -78,7 +102,7 @@ function _SortButton({ orderBy }: FilterGroupProps): JSX.Element {
filter_type: selectedMenuAction.orderBy, filter_type: selectedMenuAction.orderBy,
}) })
}, },
render: () => <MenuItem label={option.title} />, render: () => <MenuItem active={option.active} icon={option.icon} label={option.title} />,
} }
}) })
}, [MenuItem, dispatch, menuActions]) }, [MenuItem, dispatch, menuActions])
...@@ -91,17 +115,17 @@ function _SortButton({ orderBy }: FilterGroupProps): JSX.Element { ...@@ -91,17 +115,17 @@ function _SortButton({ orderBy }: FilterGroupProps): JSX.Element {
alignment: 'right', alignment: 'right',
}} }}
testID="chain-selector" testID="chain-selector"
onDismiss={() => {}}
> >
<Flex <Flex
row row
backgroundColor={isDarkMode ? '$DEP_backgroundOverlay' : '$surface1'} backgroundColor="$surface3"
borderRadius="$rounded20" borderRadius="$rounded20"
gap="$spacing4" gap="$spacing4"
px="$spacing16" pl="$spacing12"
pr="$spacing8"
py="$spacing8" py="$spacing8"
> >
<Text ellipse color="$neutral2" flexShrink={1} numberOfLines={1} variant="buttonLabel2"> <Text ellipse color="$neutral1" flexShrink={1} numberOfLines={1} variant="buttonLabel2">
{getTokensOrderBySelectedLabel(orderBy, t)} {getTokensOrderBySelectedLabel(orderBy, t)}
</Text> </Text>
<RotatableChevron color="$neutral2" direction="down" height={iconSizes.icon20} width={iconSizes.icon20} /> <RotatableChevron color="$neutral2" direction="down" height={iconSizes.icon20} width={iconSizes.icon20} />
......
...@@ -10,6 +10,7 @@ import { TokenMetadata } from 'src/components/tokens/TokenMetadata' ...@@ -10,6 +10,7 @@ import { TokenMetadata } from 'src/components/tokens/TokenMetadata'
import { disableOnPress } from 'src/utils/disableOnPress' import { disableOnPress } from 'src/utils/disableOnPress'
import { Flex, ImpactFeedbackStyle, Text, TouchableArea, ViewProps } from 'ui/src' import { Flex, ImpactFeedbackStyle, Text, TouchableArea, ViewProps } from 'ui/src'
import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { spacing } from 'ui/src/theme'
import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo' import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo'
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import { MobileEventName, SectionName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName, SectionName } from 'uniswap/src/features/telemetry/constants'
...@@ -115,15 +116,15 @@ export const TokenItem = memo(function _TokenItem({ ...@@ -115,15 +116,15 @@ export const TokenItem = memo(function _TokenItem({
> >
{overlay} {overlay}
<AnimatedFlex grow row alignItems="center" gap="$spacing12" px="$spacing24" py="$spacing8" {...containerProps}> <AnimatedFlex grow row alignItems="center" gap="$spacing12" px="$spacing24" py="$spacing8" {...containerProps}>
<Flex centered row gap="$spacing4" overflow="hidden"> <Flex centered row gap="$spacing4">
{!hideNumberedList && ( {!hideNumberedList && (
<Flex minWidth={16}> <Flex minWidth={spacing.spacing16} mr="$spacing8">
<Text color="$neutral2" variant="buttonLabel2"> <Text color="$neutral2" variant="buttonLabel2">
{index + 1} {index + 1}
</Text> </Text>
</Flex> </Flex>
)} )}
<TokenLogo name={name} symbol={symbol} url={logoUrl} /> <TokenLogo chainId={chainId} name={name} symbol={symbol} url={logoUrl} />
</Flex> </Flex>
<Flex fill shrink gap="$spacing2"> <Flex fill shrink gap="$spacing2">
<Text numberOfLines={1} variant="body1"> <Text numberOfLines={1} variant="body1">
......
...@@ -44,7 +44,7 @@ exports[`SortButton renders without error 1`] = ` ...@@ -44,7 +44,7 @@ exports[`SortButton renders without error 1`] = `
<View <View
style={ style={
{ {
"backgroundColor": "#FFFFFF", "backgroundColor": "rgba(34,34,34,0.05)",
"borderBottomLeftRadius": 20, "borderBottomLeftRadius": 20,
"borderBottomRightRadius": 20, "borderBottomRightRadius": 20,
"borderTopLeftRadius": 20, "borderTopLeftRadius": 20,
...@@ -52,8 +52,8 @@ exports[`SortButton renders without error 1`] = ` ...@@ -52,8 +52,8 @@ exports[`SortButton renders without error 1`] = `
"flexDirection": "row", "flexDirection": "row",
"gap": 4, "gap": 4,
"paddingBottom": 8, "paddingBottom": 8,
"paddingLeft": 16, "paddingLeft": 12,
"paddingRight": 16, "paddingRight": 8,
"paddingTop": 8, "paddingTop": 8,
} }
} }
...@@ -65,7 +65,7 @@ exports[`SortButton renders without error 1`] = ` ...@@ -65,7 +65,7 @@ exports[`SortButton renders without error 1`] = `
numberOfLines={1} numberOfLines={1}
style={ style={
{ {
"color": "#7D7D7D", "color": "#222222",
"flexShrink": 1, "flexShrink": 1,
"fontFamily": "Basel Grotesk", "fontFamily": "Basel Grotesk",
"fontSize": 17, "fontSize": 17,
......
...@@ -59,7 +59,6 @@ exports[`TokenItem renders without error 1`] = ` ...@@ -59,7 +59,6 @@ exports[`TokenItem renders without error 1`] = `
"flexDirection": "row", "flexDirection": "row",
"gap": 4, "gap": 4,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden",
} }
} }
> >
...@@ -67,6 +66,7 @@ exports[`TokenItem renders without error 1`] = ` ...@@ -67,6 +66,7 @@ exports[`TokenItem renders without error 1`] = `
style={ style={
{ {
"flexDirection": "column", "flexDirection": "column",
"marginRight": 8,
"minWidth": 16, "minWidth": 16,
} }
} }
......
...@@ -14,28 +14,15 @@ import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' ...@@ -14,28 +14,15 @@ import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal' import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal'
import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types'
import { SearchResultType, WalletSearchResult } from 'uniswap/src/features/search/SearchResult'
import { clearSearchHistory } from 'uniswap/src/features/search/searchHistorySlice' import { clearSearchHistory } from 'uniswap/src/features/search/searchHistorySlice'
import { selectSearchHistory } from 'uniswap/src/features/search/selectSearchHistory' import { selectSearchHistory } from 'uniswap/src/features/search/selectSearchHistory'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { UniverseChainId } from 'uniswap/src/types/chains'
import { dismissNativeKeyboard } from 'utilities/src/device/keyboard' import { dismissNativeKeyboard } from 'utilities/src/device/keyboard'
const TrendUpIcon = <TrendUp color="$neutral2" size="$icon.24" /> const TrendUpIcon = <TrendUp color="$neutral2" size="$icon.24" />
export const SUGGESTED_WALLETS: WalletSearchResult[] = [ export function SearchEmptySection({ selectedChain }: { selectedChain: UniverseChainId | null }): JSX.Element {
{
type: SearchResultType.ENSAddress,
address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
ensName: 'vitalik.eth',
},
{
type: SearchResultType.ENSAddress,
address: '0x50EC05ADe8280758E2077fcBC08D878D4aef79C3',
ensName: 'hayden.eth',
},
]
export function SearchEmptySection(): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const colors = useSporeColors() const colors = useSporeColors()
const dispatch = useDispatch() const dispatch = useDispatch()
...@@ -90,20 +77,12 @@ export function SearchEmptySection(): JSX.Element { ...@@ -90,20 +77,12 @@ export function SearchEmptySection(): JSX.Element {
title={t('explore.search.section.popularTokens')} title={t('explore.search.section.popularTokens')}
onPress={onPopularTokenInfoPress} onPress={onPopularTokenInfoPress}
/> />
<SearchPopularTokens /> <SearchPopularTokens selectedChain={selectedChain} />
</Flex> </Flex>
<Flex gap="$spacing4"> <Flex gap="$spacing4">
<SectionHeaderText icon={TrendUpIcon} title={t('explore.search.section.popularNFT')} /> <SectionHeaderText icon={TrendUpIcon} title={t('explore.search.section.popularNFT')} />
<SearchPopularNFTCollections /> <SearchPopularNFTCollections />
</Flex> </Flex>
<FlatList
ListHeaderComponent={
<SectionHeaderText icon={TrendUpIcon} title={t('explore.search.section.suggestedWallets')} />
}
data={SUGGESTED_WALLETS}
keyExtractor={walletKey}
renderItem={renderSearchItem}
/>
</AnimatedFlex> </AnimatedFlex>
<WarningModal <WarningModal
backgroundIconColor={colors.surface2.get()} backgroundIconColor={colors.surface2.get()}
...@@ -120,10 +99,6 @@ export function SearchEmptySection(): JSX.Element { ...@@ -120,10 +99,6 @@ export function SearchEmptySection(): JSX.Element {
) )
} }
const walletKey = (wallet: WalletSearchResult): string => {
return wallet.address
}
export const RecentIcon = (): JSX.Element => { export const RecentIcon = (): JSX.Element => {
const colors = useSporeColors() const colors = useSporeColors()
return <ClockIcon color={colors.neutral2.get()} height={iconSizes.icon20} width={iconSizes.icon20} /> return <ClockIcon color={colors.neutral2.get()} height={iconSizes.icon20} width={iconSizes.icon20} />
......
...@@ -3,6 +3,7 @@ import { SearchPopularTokens } from 'src/components/explore/search/SearchPopular ...@@ -3,6 +3,7 @@ import { SearchPopularTokens } from 'src/components/explore/search/SearchPopular
import { render, screen } from 'src/test/test-utils' import { render, screen } from 'src/test/test-utils'
import { ethToken, usdcToken, wethToken } from 'uniswap/src/test/fixtures' import { ethToken, usdcToken, wethToken } from 'uniswap/src/test/fixtures'
import { queryResolvers } from 'uniswap/src/test/utils' import { queryResolvers } from 'uniswap/src/test/utils'
import { UniverseChainId } from 'uniswap/src/types/chains'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
const { resolvers } = queryResolvers({ const { resolvers } = queryResolvers({
...@@ -14,7 +15,7 @@ describe(SearchPopularTokens, () => { ...@@ -14,7 +15,7 @@ describe(SearchPopularTokens, () => {
// TODO(MOB-3146): this test is flaky // TODO(MOB-3146): this test is flaky
jest.retryTimes(3) jest.retryTimes(3)
it.skip('renders without error', async () => { it.skip('renders without error', async () => {
const tree = render(<SearchPopularTokens />, { resolvers }) const tree = render(<SearchPopularTokens selectedChain={UniverseChainId.Mainnet} />, { resolvers })
// Loading should show Token loader // Loading should show Token loader
expect(screen.getAllByText('Token Full Name')).toBeDefined() expect(screen.getAllByText('Token Full Name')).toBeDefined()
......
import { TokenRankingsStat } from '@uniswap/client-explore/dist/uniswap/explore/v1/service_pb'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { FlatList, ListRenderItemInfo } from 'react-native' import { FlatList, ListRenderItemInfo } from 'react-native'
import { SearchTokenItem } from 'src/components/explore/search/items/SearchTokenItem' import { SearchTokenItem } from 'src/components/explore/search/items/SearchTokenItem'
import { getSearchResultId } from 'src/components/explore/search/utils' import { getSearchResultId } from 'src/components/explore/search/utils'
import { Flex, Loader } from 'ui/src' import { Flex, Loader } from 'ui/src'
import { ALL_NETWORKS_ARG } from 'uniswap/src/data/rest/base'
import { useTokenRankingsQuery } from 'uniswap/src/data/rest/tokenRankings'
import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' import { fromGraphQLChain } from 'uniswap/src/features/chains/utils'
import { getCurrencySafetyInfo } from 'uniswap/src/features/dataApi/utils'
import { SearchResultType, TokenSearchResult } from 'uniswap/src/features/search/SearchResult' import { SearchResultType, TokenSearchResult } from 'uniswap/src/features/search/SearchResult'
import { TopToken, usePopularTokens } from 'uniswap/src/features/tokens/hooks' import { UniverseChainId } from 'uniswap/src/types/chains'
import { RankingType } from 'wallet/src/features/wallet/types'
function gqlTokenToTokenSearchResult(token: Maybe<TopToken>): TokenSearchResult | null { const MAX_TOKEN_RESULTS_AMOUNT = 8
if (!token || !token.project) {
function tokenStatsToTokenSearchResult(token: Maybe<TokenRankingsStat>): TokenSearchResult | null {
if (!token) {
return null return null
} }
const { name, chain, address, symbol, project, protectionInfo } = token const { chain, address, symbol, name, logo } = token
const chainId = fromGraphQLChain(chain) const chainId = fromGraphQLChain(chain)
if (!chainId || !symbol || !name) { if (!chainId || !symbol || !name) {
return null return null
} }
...@@ -25,20 +31,24 @@ function gqlTokenToTokenSearchResult(token: Maybe<TopToken>): TokenSearchResult ...@@ -25,20 +31,24 @@ function gqlTokenToTokenSearchResult(token: Maybe<TopToken>): TokenSearchResult
address: address ?? null, address: address ?? null,
name, name,
symbol, symbol,
logoUrl: project?.logoUrl ?? null, logoUrl: logo ?? null,
safetyLevel: project?.safetyLevel ?? null, safetyLevel: null,
safetyInfo: getCurrencySafetyInfo(project.safetyLevel, protectionInfo),
} }
} }
export function SearchPopularTokens(): JSX.Element { export function SearchPopularTokens({ selectedChain }: { selectedChain: UniverseChainId | null }): JSX.Element {
const { popularTokens, loading } = usePopularTokens() const { data, isLoading } = useTokenRankingsQuery({
const tokens = useMemo( chainId: selectedChain?.toString() ?? ALL_NETWORKS_ARG,
() => popularTokens?.map(gqlTokenToTokenSearchResult).filter((t): t is TokenSearchResult => Boolean(t)), })
const popularTokens = data?.tokenRankings?.[RankingType.Popularity]?.tokens.slice(0, MAX_TOKEN_RESULTS_AMOUNT)
const formattedTokens = useMemo(
() => popularTokens?.map(tokenStatsToTokenSearchResult).filter((t): t is TokenSearchResult => Boolean(t)),
[popularTokens], [popularTokens],
) )
if (loading) { if (isLoading) {
return ( return (
<Flex px="$spacing24" py="$spacing8"> <Flex px="$spacing24" py="$spacing8">
<Loader.Token repeat={2} /> <Loader.Token repeat={2} />
...@@ -46,7 +56,7 @@ export function SearchPopularTokens(): JSX.Element { ...@@ -46,7 +56,7 @@ export function SearchPopularTokens(): JSX.Element {
) )
} }
return <FlatList data={tokens} keyExtractor={getSearchResultId} renderItem={renderTokenItem} /> return <FlatList data={formattedTokens} keyExtractor={getSearchResultId} renderItem={renderTokenItem} />
} }
const renderTokenItem = ({ item }: ListRenderItemInfo<TokenSearchResult>): JSX.Element => ( const renderTokenItem = ({ item }: ListRenderItemInfo<TokenSearchResult>): JSX.Element => (
......
...@@ -21,20 +21,20 @@ export const SearchResultsLoader = (): JSX.Element => { ...@@ -21,20 +21,20 @@ export const SearchResultsLoader = (): JSX.Element => {
</Flex> </Flex>
<Flex gap="$spacing12"> <Flex gap="$spacing12">
<SectionHeaderText <SectionHeaderText
icon={<Gallery color="$neutral2" size="$icon.24" />} icon={<Person color="$neutral2" size="$icon.24" />}
title={t('explore.search.section.nft')} title={t('explore.search.section.wallets')}
/> />
<AnimatedFlex entering={FadeIn} exiting={FadeOut} mx="$spacing24"> <AnimatedFlex entering={FadeIn} exiting={FadeOut} mx="$spacing24">
<Loader.Token repeat={2} /> <Loader.Token />
</AnimatedFlex> </AnimatedFlex>
</Flex> </Flex>
<Flex gap="$spacing12"> <Flex gap="$spacing12">
<SectionHeaderText <SectionHeaderText
icon={<Person color="$neutral2" size="$icon.24" />} icon={<Gallery color="$neutral2" size="$icon.24" />}
title={t('explore.search.section.wallets')} title={t('explore.search.section.nft')}
/> />
<AnimatedFlex entering={FadeIn} exiting={FadeOut} mx="$spacing24"> <AnimatedFlex entering={FadeIn} exiting={FadeOut} mx="$spacing24">
<Loader.Token /> <Loader.Token repeat={2} />
</AnimatedFlex> </AnimatedFlex>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -14,6 +14,7 @@ import { SearchUnitagItem } from 'src/components/explore/search/items/SearchUnit ...@@ -14,6 +14,7 @@ import { SearchUnitagItem } from 'src/components/explore/search/items/SearchUnit
import { SearchWalletByAddressItem } from 'src/components/explore/search/items/SearchWalletByAddressItem' import { SearchWalletByAddressItem } from 'src/components/explore/search/items/SearchWalletByAddressItem'
import { SearchResultOrHeader } from 'src/components/explore/search/types' import { SearchResultOrHeader } from 'src/components/explore/search/types'
import { import {
filterSearchResultsByChainId,
formatNFTCollectionSearchResults, formatNFTCollectionSearchResults,
formatTokenSearchResults, formatTokenSearchResults,
getSearchResultId, getSearchResultId,
...@@ -63,7 +64,13 @@ const EtherscanHeaderItem: (chainId: UniverseChainId) => SearchResultOrHeader = ...@@ -63,7 +64,13 @@ const EtherscanHeaderItem: (chainId: UniverseChainId) => SearchResultOrHeader =
const IGNORED_ERRORS = ['Subgraph provider undefined not supported'] const IGNORED_ERRORS = ['Subgraph provider undefined not supported']
export function SearchResultsSection({ searchQuery }: { searchQuery: string }): JSX.Element { export function SearchResultsSection({
searchQuery,
selectedChain,
}: {
searchQuery: string
selectedChain: UniverseChainId | null
}): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const { defaultChainId } = useEnabledChains() const { defaultChainId } = useEnabledChains()
...@@ -86,8 +93,14 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }): ...@@ -86,8 +93,14 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }):
return undefined return undefined
} }
return formatTokenSearchResults(searchResultsData.searchTokens, searchQuery) const formattedTokenSearchResults = formatTokenSearchResults(searchResultsData.searchTokens, searchQuery)
}, [searchQuery, searchResultsData])
if (!selectedChain) {
return formattedTokenSearchResults
}
return filterSearchResultsByChainId(formattedTokenSearchResults, selectedChain)
}, [selectedChain, searchQuery, searchResultsData])
// Search for matching NFT collections // Search for matching NFT collections
...@@ -96,12 +109,22 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }): ...@@ -96,12 +109,22 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }):
return undefined return undefined
} }
return formatNFTCollectionSearchResults(searchResultsData.nftCollections) const formattedNftCollectionSearchResults = formatNFTCollectionSearchResults(searchResultsData.nftCollections)
}, [searchResultsData])
if (!selectedChain) {
return formattedNftCollectionSearchResults
}
return filterSearchResultsByChainId(formattedNftCollectionSearchResults, selectedChain)
}, [searchResultsData, selectedChain])
// Search for matching wallets // Search for matching wallets
const { wallets: walletSearchResults, exactENSMatch, exactUnitagMatch } = useWalletSearchResults(searchQuery) const {
wallets: walletSearchResults,
exactENSMatch,
exactUnitagMatch,
} = useWalletSearchResults(searchQuery, selectedChain)
const validAddress: Address | undefined = useMemo( const validAddress: Address | undefined = useMemo(
() => getValidAddress(searchQuery, true, false) ?? undefined, () => getValidAddress(searchQuery, true, false) ?? undefined,
...@@ -139,8 +162,8 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }): ...@@ -139,8 +162,8 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }):
// NFTs, then wallets, then tokens // NFTs, then wallets, then tokens
searchResultItems = [...nftsWithHeader, ...walletsWithHeader, ...tokensWithHeader] searchResultItems = [...nftsWithHeader, ...walletsWithHeader, ...tokensWithHeader]
} else { } else {
// Tokens, then NFTs, then wallets // Tokens, then wallets, then NFTs,
searchResultItems = [...tokensWithHeader, ...nftsWithHeader, ...walletsWithHeader] searchResultItems = [...tokensWithHeader, ...walletsWithHeader, ...nftsWithHeader]
} }
// Add etherscan items at end // Add etherscan items at end
......
...@@ -8,7 +8,10 @@ import { getValidAddress } from 'uniswap/src/utils/addresses' ...@@ -8,7 +8,10 @@ import { getValidAddress } from 'uniswap/src/utils/addresses'
import { useIsSmartContractAddress } from 'wallet/src/features/transactions/send/hooks/useIsSmartContractAddress' import { useIsSmartContractAddress } from 'wallet/src/features/transactions/send/hooks/useIsSmartContractAddress'
// eslint-disable-next-line complexity // eslint-disable-next-line complexity
export function useWalletSearchResults(query: string): { export function useWalletSearchResults(
query: string,
selectedChain: UniverseChainId | null,
): {
wallets: WalletSearchResult[] wallets: WalletSearchResult[]
loading: boolean loading: boolean
exactENSMatch: boolean exactENSMatch: boolean
...@@ -42,7 +45,7 @@ export function useWalletSearchResults(query: string): { ...@@ -42,7 +45,7 @@ export function useWalletSearchResults(query: string): {
// Search for matching EOA wallet address // Search for matching EOA wallet address
const { isSmartContractAddress, loading: loadingIsSmartContractAddress } = useIsSmartContractAddress( const { isSmartContractAddress, loading: loadingIsSmartContractAddress } = useIsSmartContractAddress(
validAddress, validAddress,
defaultChainId, selectedChain ?? defaultChainId,
) )
const hasENSResult = dotEthName && dotEthAddress const hasENSResult = dotEthName && dotEthAddress
......
...@@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux' ...@@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux'
import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks' import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks'
import { useExploreTokenContextMenu } from 'src/components/explore/hooks' import { useExploreTokenContextMenu } from 'src/components/explore/hooks'
import { disableOnPress } from 'src/utils/disableOnPress' import { disableOnPress } from 'src/utils/disableOnPress'
import { Flex, ImpactFeedbackStyle, Text, TouchableArea, useIsDarkMode } from 'ui/src' import { Flex, ImpactFeedbackStyle, Text, TouchableArea } from 'ui/src'
import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo' import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo'
import WarningIcon from 'uniswap/src/components/warnings/WarningIcon' import WarningIcon from 'uniswap/src/components/warnings/WarningIcon'
import { getWarningIconColorOverride } from 'uniswap/src/components/warnings/utils' import { getWarningIconColorOverride } from 'uniswap/src/components/warnings/utils'
...@@ -17,7 +17,6 @@ import { getTokenWarningSeverity } from 'uniswap/src/features/tokens/safetyUtils ...@@ -17,7 +17,6 @@ import { getTokenWarningSeverity } from 'uniswap/src/features/tokens/safetyUtils
import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { UniverseChainId } from 'uniswap/src/types/chains' import { UniverseChainId } from 'uniswap/src/types/chains'
import { shortenAddress } from 'uniswap/src/utils/addresses'
import { buildCurrencyId, buildNativeCurrencyId } from 'uniswap/src/utils/currencyId' import { buildCurrencyId, buildNativeCurrencyId } from 'uniswap/src/utils/currencyId'
type SearchTokenItemProps = { type SearchTokenItemProps = {
...@@ -26,7 +25,6 @@ type SearchTokenItemProps = { ...@@ -26,7 +25,6 @@ type SearchTokenItemProps = {
} }
export function SearchTokenItem({ token, searchContext }: SearchTokenItemProps): JSX.Element { export function SearchTokenItem({ token, searchContext }: SearchTokenItemProps): JSX.Element {
const isDarkMode = useIsDarkMode()
const dispatch = useDispatch() const dispatch = useDispatch()
const tokenDetailsNavigation = useTokenDetailsNavigation() const tokenDetailsNavigation = useTokenDetailsNavigation()
...@@ -99,13 +97,6 @@ export function SearchTokenItem({ token, searchContext }: SearchTokenItemProps): ...@@ -99,13 +97,6 @@ export function SearchTokenItem({ token, searchContext }: SearchTokenItemProps):
<Text color="$neutral2" numberOfLines={1} variant="subheading2"> <Text color="$neutral2" numberOfLines={1} variant="subheading2">
{symbol} {symbol}
</Text> </Text>
{address && (
<Flex shrink>
<Text color={isDarkMode ? '$neutral3' : '$neutral2'} numberOfLines={1} variant="subheading2">
{shortenAddress(address)}
</Text>
</Flex>
)}
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -9,11 +9,19 @@ import { ...@@ -9,11 +9,19 @@ import {
TokenSearchResult, TokenSearchResult,
} from 'uniswap/src/features/search/SearchResult' } from 'uniswap/src/features/search/SearchResult'
import { searchResultId } from 'uniswap/src/features/search/searchHistorySlice' import { searchResultId } from 'uniswap/src/features/search/searchHistorySlice'
import { UniverseChainId } from 'uniswap/src/types/chains'
const MAX_TOKEN_RESULTS_COUNT = 4 const MAX_TOKEN_RESULTS_COUNT = 4
type ExploreSearchResult = NonNullable<ExploreSearchQuery> type ExploreSearchResult = NonNullable<ExploreSearchQuery>
export function filterSearchResultsByChainId<T extends { chainId: null | UniverseChainId }>(
tokenSearchResults: Array<T> | undefined,
chainId: UniverseChainId | null,
): Array<T> | undefined {
return tokenSearchResults?.filter((searchResult): boolean => chainId === null || searchResult.chainId === chainId)
}
// Formats the tokens portion of explore search results into sorted array of TokenSearchResult // Formats the tokens portion of explore search results into sorted array of TokenSearchResult
export function formatTokenSearchResults( export function formatTokenSearchResults(
data: ExploreSearchResult['searchTokens'], data: ExploreSearchResult['searchTokens'],
......
...@@ -47,14 +47,17 @@ export function HeaderScrollScreen({ ...@@ -47,14 +47,17 @@ export function HeaderScrollScreen({
const scrollY = useSharedValue(0) const scrollY = useSharedValue(0)
// On scroll, centerElement and the bottom border fade in // On scroll, centerElement and the bottom border fade in
const scrollHandler = useAnimatedScrollHandler({ const scrollHandler = useAnimatedScrollHandler(
onScroll: (event) => { {
scrollY.value = event.contentOffset.y onScroll: (event) => {
scrollY.value = event.contentOffset.y
},
onEndDrag: (event) => {
scrollY.value = withTiming(event.contentOffset.y > 0 ? SHOW_HEADER_SCROLL_Y_DISTANCE : 0)
},
}, },
onEndDrag: (event) => { [scrollY],
scrollY.value = withTiming(event.contentOffset.y > 0 ? SHOW_HEADER_SCROLL_Y_DISTANCE : 0) )
},
})
return ( return (
<Screen backgroundColor={backgroundColor} edges={['top', 'left', 'right']} noInsets={fullScreen}> <Screen backgroundColor={backgroundColor} edges={['top', 'left', 'right']} noInsets={fullScreen}>
......
import React, { memo, PropsWithChildren } from 'react' import React, { memo, PropsWithChildren } from 'react'
import { Flex } from 'ui/src' import { Flex } from 'ui/src'
import { useSelectAddressHasNotifications } from 'wallet/src/features/notifications/hooks' import { useSelectAddressHasNotifications } from 'uniswap/src/features/notifications/hooks'
type Props = PropsWithChildren<{ type Props = PropsWithChildren<{
address: Address address: Address
......
import { TokenItemData } from 'src/components/explore/TokenItemData'
import { AppTFunction } from 'ui/src/i18n/types' import { AppTFunction } from 'ui/src/i18n/types'
import { TokenSortableField } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import {
import { ClientTokensOrderBy, TokenMetadataDisplayType, TokensOrderBy } from 'wallet/src/features/wallet/types' CustomRankingType,
ExploreOrderBy,
/** RankingType,
* Returns server and client orderBy values to use for topTokens query and client side sorting TokenMetadataDisplayType,
* } from 'wallet/src/features/wallet/types'
* Uses server side sort by Volume if applying a client side sort after
* ex. % change sorting use the top 100 tokens by Uniswap Volume, then sorts by % change export function getTokenMetadataDisplayType(orderBy: ExploreOrderBy): TokenMetadataDisplayType {
*
* Note that server side sort by Volume (TokenSortableField.Volume) requires an
* additional client side sort because there may be a discrepancy in the server's
* sort by Volume list which is calculated once per 24h and each token's
* token.market.volume which is updated more frequently
*
* @param orderBy currently selected TokensOrderBy value to sort tokens by
* @returns serverOrderBy to be used in topTokens query, clientOrderBy to be used to determine if client side sort is necessary
*/
export function getTokensOrderByValues(orderBy: TokensOrderBy): {
serverOrderBy: TokenSortableField
clientOrderBy: ClientTokensOrderBy | undefined
} {
const requiresClientOrderBy = Object.values<string>(ClientTokensOrderBy).includes(orderBy)
return {
serverOrderBy: requiresClientOrderBy ? TokenSortableField.Volume : (orderBy as TokenSortableField),
clientOrderBy: requiresClientOrderBy
? (orderBy as ClientTokensOrderBy)
: orderBy === TokenSortableField.Volume
? ClientTokensOrderBy.Volume24hDesc
: undefined,
}
}
/**
* Returns a compare function to sort tokens client side.
*/
export function getClientTokensOrderByCompareFn(
orderBy: ClientTokensOrderBy,
): (a: TokenItemData, b: TokenItemData) => number {
let compareField: keyof TokenItemData
let direction = 0
switch (orderBy) { switch (orderBy) {
case ClientTokensOrderBy.PriceChangePercentage24hAsc: case RankingType.MarketCap:
compareField = 'pricePercentChange24h'
direction = 1
break
case ClientTokensOrderBy.PriceChangePercentage24hDesc:
compareField = 'pricePercentChange24h'
direction = -1
break
case ClientTokensOrderBy.Volume24hDesc:
compareField = 'volume24h'
direction = -1
break
}
return (a: TokenItemData, b: TokenItemData) => {
// undefined values sort to bottom
if (a[compareField] === undefined) {
return 1
}
if (b[compareField] === undefined) {
return -1
}
return Number(a[compareField]) - Number(b[compareField]) > 0 ? direction : -1 * direction
}
}
export function getTokenMetadataDisplayType(orderBy: TokensOrderBy): TokenMetadataDisplayType {
switch (orderBy) {
case TokenSortableField.MarketCap:
return TokenMetadataDisplayType.MarketCap return TokenMetadataDisplayType.MarketCap
case TokenSortableField.Volume: case RankingType.Volume:
return TokenMetadataDisplayType.Volume return TokenMetadataDisplayType.Volume
case TokenSortableField.TotalValueLocked: case RankingType.TotalValueLocked:
return TokenMetadataDisplayType.TVL return TokenMetadataDisplayType.TVL
case ClientTokensOrderBy.PriceChangePercentage24hDesc: case CustomRankingType.PricePercentChange1DayDesc:
return TokenMetadataDisplayType.Symbol case CustomRankingType.PricePercentChange1DayAsc:
case ClientTokensOrderBy.PriceChangePercentage24hAsc:
return TokenMetadataDisplayType.Symbol return TokenMetadataDisplayType.Symbol
default: default:
throw new Error('Unexpected order by value ' + orderBy) throw new Error('Unexpected order by value ' + orderBy)
...@@ -87,17 +23,17 @@ export function getTokenMetadataDisplayType(orderBy: TokensOrderBy): TokenMetada ...@@ -87,17 +23,17 @@ export function getTokenMetadataDisplayType(orderBy: TokensOrderBy): TokenMetada
} }
// Label shown in the popover context menu. // Label shown in the popover context menu.
export function getTokensOrderByMenuLabel(orderBy: TokensOrderBy, t: AppTFunction): string { export function getTokensOrderByMenuLabel(orderBy: ExploreOrderBy, t: AppTFunction): string {
switch (orderBy) { switch (orderBy) {
case TokenSortableField.MarketCap: case RankingType.MarketCap:
return t('explore.tokens.sort.option.marketCap') return t('explore.tokens.sort.option.marketCap')
case TokenSortableField.Volume: case RankingType.Volume:
return t('explore.tokens.sort.option.volume') return t('explore.tokens.sort.option.volume')
case TokenSortableField.TotalValueLocked: case RankingType.TotalValueLocked:
return t('explore.tokens.sort.option.totalValueLocked') return t('explore.tokens.sort.option.totalValueLocked')
case ClientTokensOrderBy.PriceChangePercentage24hDesc: case CustomRankingType.PricePercentChange1DayDesc:
return t('explore.tokens.sort.option.priceIncrease') return t('explore.tokens.sort.option.priceIncrease')
case ClientTokensOrderBy.PriceChangePercentage24hAsc: case CustomRankingType.PricePercentChange1DayAsc:
return t('explore.tokens.sort.option.priceDecrease') return t('explore.tokens.sort.option.priceDecrease')
default: default:
throw new Error('Unexpected order by value ' + orderBy) throw new Error('Unexpected order by value ' + orderBy)
...@@ -105,17 +41,17 @@ export function getTokensOrderByMenuLabel(orderBy: TokensOrderBy, t: AppTFunctio ...@@ -105,17 +41,17 @@ export function getTokensOrderByMenuLabel(orderBy: TokensOrderBy, t: AppTFunctio
} }
// Label shown when option is selected in dropdown. // Label shown when option is selected in dropdown.
export function getTokensOrderBySelectedLabel(orderBy: TokensOrderBy, t: AppTFunction): string { export function getTokensOrderBySelectedLabel(orderBy: ExploreOrderBy, t: AppTFunction): string {
switch (orderBy) { switch (orderBy) {
case TokenSortableField.MarketCap: case RankingType.MarketCap:
return t('explore.tokens.sort.label.marketCap') return t('explore.tokens.sort.label.marketCap')
case TokenSortableField.Volume: case RankingType.Volume:
return t('explore.tokens.sort.label.volume') return t('explore.tokens.sort.label.volume')
case TokenSortableField.TotalValueLocked: case RankingType.TotalValueLocked:
return t('explore.tokens.sort.label.totalValueLocked') return t('explore.tokens.sort.label.totalValueLocked')
case ClientTokensOrderBy.PriceChangePercentage24hDesc: case CustomRankingType.PricePercentChange1DayDesc:
return t('explore.tokens.sort.label.priceIncrease') return t('explore.tokens.sort.label.priceIncrease')
case ClientTokensOrderBy.PriceChangePercentage24hAsc: case CustomRankingType.PricePercentChange1DayAsc:
return t('explore.tokens.sort.label.priceDecrease') return t('explore.tokens.sort.label.priceDecrease')
default: default:
throw new Error('Unexpected order by value in option text ' + orderBy) throw new Error('Unexpected order by value in option text ' + orderBy)
......
...@@ -10,6 +10,8 @@ import { Flex, TouchableArea, useHapticFeedback } from 'ui/src' ...@@ -10,6 +10,8 @@ import { Flex, TouchableArea, useHapticFeedback } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'uniswap/src/features/notifications/types'
import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useEnabledChains } from 'uniswap/src/features/settings/hooks'
import { ElementName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { ElementName, WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
...@@ -19,8 +21,6 @@ import { ShareableEntity } from 'uniswap/src/types/sharing' ...@@ -19,8 +21,6 @@ import { ShareableEntity } from 'uniswap/src/types/sharing'
import { setClipboard } from 'uniswap/src/utils/clipboard' import { setClipboard } from 'uniswap/src/utils/clipboard'
import { ExplorerDataType, getExplorerLink, openUri } from 'uniswap/src/utils/linking' import { ExplorerDataType, getExplorerLink, openUri } from 'uniswap/src/utils/linking'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'wallet/src/features/notifications/types'
import { getProfileUrl } from 'wallet/src/utils/linking' import { getProfileUrl } from 'wallet/src/utils/linking'
type MenuAction = { type MenuAction = {
......
...@@ -2,7 +2,6 @@ import React, { memo, useCallback, useMemo, useState } from 'react' ...@@ -2,7 +2,6 @@ import React, { memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { StatusBar, StyleSheet } from 'react-native' import { StatusBar, StyleSheet } from 'react-native'
import { FadeIn } from 'react-native-reanimated' import { FadeIn } from 'react-native-reanimated'
import Svg, { ClipPath, Defs, RadialGradient, Rect, Stop } from 'react-native-svg'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { BackButton } from 'src/components/buttons/BackButton' import { BackButton } from 'src/components/buttons/BackButton'
import { Favorite } from 'src/components/icons/Favorite' import { Favorite } from 'src/components/icons/Favorite'
...@@ -35,6 +34,7 @@ import { CurrencyField } from 'uniswap/src/types/currency' ...@@ -35,6 +34,7 @@ import { CurrencyField } from 'uniswap/src/types/currency'
import { openUri } from 'uniswap/src/utils/linking' import { openUri } from 'uniswap/src/utils/linking'
import { RecipientSelectSpeedBumps } from 'wallet/src/components/RecipientSearch/RecipientSelectSpeedBumps' import { RecipientSelectSpeedBumps } from 'wallet/src/components/RecipientSearch/RecipientSelectSpeedBumps'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { HeaderRadial, solidHeaderProps } from 'wallet/src/features/unitags/HeaderRadial'
import { useDisplayName } from 'wallet/src/features/wallet/hooks' import { useDisplayName } from 'wallet/src/features/wallet/hooks'
import { DisplayNameType } from 'wallet/src/features/wallet/types' import { DisplayNameType } from 'wallet/src/features/wallet/types'
...@@ -45,13 +45,6 @@ interface ProfileHeaderProps { ...@@ -45,13 +45,6 @@ interface ProfileHeaderProps {
address: Address address: Address
} }
const HEADER_SOLID_COLOR_OPACITY = 0.1
export const solidHeaderProps = {
minOpacity: HEADER_SOLID_COLOR_OPACITY,
maxOpacity: HEADER_SOLID_COLOR_OPACITY,
}
export const ProfileHeader = memo(function ProfileHeader({ address }: ProfileHeaderProps): JSX.Element { export const ProfileHeader = memo(function ProfileHeader({ address }: ProfileHeaderProps): JSX.Element {
const colors = useSporeColors() const colors = useSporeColors()
const dispatch = useDispatch() const dispatch = useDispatch()
...@@ -265,40 +258,6 @@ export const ProfileHeader = memo(function ProfileHeader({ address }: ProfileHea ...@@ -265,40 +258,6 @@ export const ProfileHeader = memo(function ProfileHeader({ address }: ProfileHea
) )
}) })
export const HeaderRadial = memo(function HeaderRadial({
color,
borderRadius,
minOpacity,
maxOpacity,
}: {
color: string
borderRadius?: number
minOpacity?: number
maxOpacity?: number
}): JSX.Element {
return (
<Svg height="100%" width="100%">
<Defs>
<ClipPath id="clip">
<Rect height="100%" rx={borderRadius} width="100%" />
</ClipPath>
<RadialGradient cy="-0.1" id="background" rx="0.8" ry="1.1">
<Stop offset="0" stopColor={color} stopOpacity={maxOpacity ?? '0.6'} />
<Stop offset="1" stopColor={color} stopOpacity={minOpacity ?? '0'} />
</RadialGradient>
</Defs>
<Rect
clipPath={borderRadius ? 'url(#clip)' : undefined}
fill="url(#background)"
height="100%"
width="100%"
x="0"
y="0"
/>
</Svg>
)
})
const styles = StyleSheet.create({ const styles = StyleSheet.create({
buttonShadow: { buttonShadow: {
elevation: 2, elevation: 2,
......
...@@ -165,6 +165,35 @@ export const FiatOnRampAmountSection = forwardRef<FiatOnRampAmountSectionRef, Fi ...@@ -165,6 +165,35 @@ export const FiatOnRampAmountSection = forwardRef<FiatOnRampAmountSectionRef, Fi
currency.currencyInfo?.currency, currency.currencyInfo?.currency,
) )
// Workaround to avoid incorrect input width calculations by react-native
// Decimal numbers were manually calculated for Basel Grotesk fonts and will
// require an adjustment when the font is changed
const calculatedInputWidth = [...value].reduce(
(acc, numStr) => {
switch (numStr) {
case '1':
return acc + fontSize * 0.393
case '2':
case '6':
case '8':
return acc + fontSize * 0.596
case '3':
return acc + fontSize * 0.595
case '4':
case '0':
default:
return acc + fontSize * 0.62
case '5':
case '7':
return acc + fontSize * 0.602
case '9':
return acc + fontSize * 0.607
}
},
// ensures a proper width for a "0" placeholder or adds 3 points for the input caret
value.length === 0 ? fontSize * 0.62 : 3,
)
return ( return (
<Flex <Flex
alignItems="center" alignItems="center"
...@@ -213,6 +242,7 @@ export const FiatOnRampAmountSection = forwardRef<FiatOnRampAmountSectionRef, Fi ...@@ -213,6 +242,7 @@ export const FiatOnRampAmountSection = forwardRef<FiatOnRampAmountSectionRef, Fi
placeholderTextColor="$neutral3" placeholderTextColor="$neutral3"
px="$none" px="$none"
py="$none" py="$none"
minWidth={calculatedInputWidth}
returnKeyType={undefined} returnKeyType={undefined}
showSoftInputOnFocus={false} showSoftInputOnFocus={false}
textAlign="left" textAlign="left"
......
import React from 'react' import React from 'react'
import { useSelector } from 'react-redux'
import { ScantasticCompleteNotification } from 'src/features/notifications/ScantasticCompleteNotification' import { ScantasticCompleteNotification } from 'src/features/notifications/ScantasticCompleteNotification'
import { WCNotification } from 'src/features/notifications/WCNotification' import { WCNotification } from 'src/features/notifications/WCNotification'
import { useSelectAddressNotifications } from 'uniswap/src/features/notifications/hooks'
import { AppNotification, AppNotificationType } from 'uniswap/src/features/notifications/types'
import { SharedNotificationToastRouter } from 'wallet/src/features/notifications/components/SharedNotificationToastRouter' import { SharedNotificationToastRouter } from 'wallet/src/features/notifications/components/SharedNotificationToastRouter'
import { selectActiveAccountNotifications } from 'wallet/src/features/notifications/selectors' import { useActiveAccountAddress } from 'wallet/src/features/wallet/hooks'
import { AppNotification, AppNotificationType } from 'wallet/src/features/notifications/types'
export function NotificationToastWrapper(): JSX.Element | null { export function NotificationToastWrapper(): JSX.Element | null {
const notifications = useSelector(selectActiveAccountNotifications) const activeAccountAddress = useActiveAccountAddress()
const notifications = useSelectAddressNotifications(activeAccountAddress)
const notification = notifications?.[0] const notification = notifications?.[0]
if (!notification) { if (!notification) {
......
...@@ -2,8 +2,8 @@ import React from 'react' ...@@ -2,8 +2,8 @@ import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Flex } from 'ui/src' import { Flex } from 'ui/src'
import { Check, Laptop } from 'ui/src/components/icons' import { Check, Laptop } from 'ui/src/components/icons'
import { ScantasticCompleteNotification as ScantasticCompleteNotificationType } from 'uniswap/src/features/notifications/types'
import { NotificationToast } from 'wallet/src/features/notifications/components/NotificationToast' import { NotificationToast } from 'wallet/src/features/notifications/components/NotificationToast'
import { ScantasticCompleteNotification as ScantasticCompleteNotificationType } from 'wallet/src/features/notifications/types'
export function ScantasticCompleteNotification({ export function ScantasticCompleteNotification({
notification: { hideDelay }, notification: { hideDelay },
......
...@@ -3,13 +3,13 @@ import { useDispatch } from 'react-redux' ...@@ -3,13 +3,13 @@ import { useDispatch } from 'react-redux'
import { openModal } from 'src/features/modals/modalSlice' import { openModal } from 'src/features/modals/modalSlice'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { toSupportedChainId } from 'uniswap/src/features/chains/utils'
import { WalletConnectNotification } from 'uniswap/src/features/notifications/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { WalletConnectEvent } from 'uniswap/src/types/walletConnect'
import { DappLogoWithTxStatus } from 'wallet/src/components/CurrencyLogo/LogoWithTxStatus' import { DappLogoWithTxStatus } from 'wallet/src/components/CurrencyLogo/LogoWithTxStatus'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { NotificationToast } from 'wallet/src/features/notifications/components/NotificationToast' import { NotificationToast } from 'wallet/src/features/notifications/components/NotificationToast'
import { NOTIFICATION_ICON_SIZE } from 'wallet/src/features/notifications/constants' import { NOTIFICATION_ICON_SIZE } from 'wallet/src/features/notifications/constants'
import { WalletConnectNotification } from 'wallet/src/features/notifications/types'
import { formWCNotificationTitle } from 'wallet/src/features/notifications/utils' import { formWCNotificationTitle } from 'wallet/src/features/notifications/utils'
export function WCNotification({ notification }: { notification: WalletConnectNotification }): JSX.Element { export function WCNotification({ notification }: { notification: WalletConnectNotification }): JSX.Element {
......
...@@ -10,12 +10,12 @@ import { AlertTriangleFilled, Faceid, Laptop, LinkBrokenHorizontal, Wifi } from ...@@ -10,12 +10,12 @@ import { AlertTriangleFilled, Faceid, Laptop, LinkBrokenHorizontal, Wifi } from
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ONE_MINUTE_MS, ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_MINUTE_MS, ONE_SECOND_MS } from 'utilities/src/time/time'
import { useInterval } from 'utilities/src/time/timing' import { useInterval } from 'utilities/src/time/timing'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { useSignerAccounts } from 'wallet/src/features/wallet/hooks' import { useSignerAccounts } from 'wallet/src/features/wallet/hooks'
import { getOtpDurationString } from 'wallet/src/utils/duration' import { getOtpDurationString } from 'wallet/src/utils/duration'
......
...@@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next' ...@@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
import { UnitagStackScreenProp } from 'src/app/navigation/types' import { UnitagStackScreenProp } from 'src/app/navigation/types'
import { Screen } from 'src/components/layout/Screen' import { Screen } from 'src/components/layout/Screen'
import { UnitagWithProfilePicture } from 'src/components/unitags/UnitagWithProfilePicture'
import { AnimatePresence, Button, Flex, Text } from 'ui/src' import { AnimatePresence, Button, Flex, Text } from 'ui/src'
import { AnimateInOrder } from 'ui/src/animations' import { AnimateInOrder } from 'ui/src/animations'
import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions'
...@@ -21,6 +20,7 @@ import { ...@@ -21,6 +20,7 @@ import {
SwapElement, SwapElement,
TextElement, TextElement,
} from 'wallet/src/components/landing/elements' } from 'wallet/src/components/landing/elements'
import { UnitagWithProfilePicture } from 'wallet/src/features/unitags/UnitagWithProfilePicture'
import { UNITAG_SUFFIX } from 'wallet/src/features/unitags/constants' import { UNITAG_SUFFIX } from 'wallet/src/features/unitags/constants'
export function UnitagConfirmationScreen({ export function UnitagConfirmationScreen({
......
import { StackActions } from '@react-navigation/core' import { StackActions } from '@react-navigation/core'
import { dispatchNavigationAction } from 'src/app/navigation/rootNavigation' import { dispatchNavigationAction } from 'src/app/navigation/rootNavigation'
import { call, put, takeEvery } from 'typed-redux-saga' import { call, put, takeEvery } from 'typed-redux-saga'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n/i18n'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { restoreMnemonicComplete } from 'wallet/src/features/wallet/slice' import { restoreMnemonicComplete } from 'wallet/src/features/wallet/slice'
/** /**
......
...@@ -2,12 +2,11 @@ ...@@ -2,12 +2,11 @@
import { NativeModules } from 'react-native' import { NativeModules } from 'react-native'
import { isAndroid } from 'utilities/src/platform' import { isAndroid } from 'utilities/src/platform'
const { RNWalletConnect } = NativeModules const { RNWalletConnect, RedirectToSourceApp } = NativeModules
export const returnToPreviousApp = (): boolean => { export const returnToPreviousApp = async (): Promise<boolean> => {
// TOOD(MOB-1680): Implement return to previous app for Android
if (isAndroid) { if (isAndroid) {
return false return RedirectToSourceApp.moveAppToBackground()
} }
return RNWalletConnect.returnToPreviousApp() return RNWalletConnect.returnToPreviousApp()
} }
import { WalletConnectState } from 'src/features/walletConnect/walletConnectSlice'
import { logger } from 'utilities/src/logger/logger'
export function fetchDappDetails(
topic: string,
currentState: Readonly<WalletConnectState>,
): { dappIcon: string | null; dappName: string } {
try {
const sessions = Object.values(currentState.byAccount).find((account) => account?.sessions?.[topic])?.sessions
if (sessions && sessions[topic]) {
const wcSession = sessions[topic]
return {
dappIcon: wcSession?.dapp?.icon || null,
dappName: wcSession?.dapp?.name || '',
}
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Error retrieving session data'
logger.warn('walletConnect/saga.ts', 'createDappNotification', message)
}
return { dappIcon: null, dappName: '' }
}
import { AnyAction } from '@reduxjs/toolkit' import { AnyAction } from '@reduxjs/toolkit'
import { IWalletKit, WalletKit, WalletKitTypes } from '@reown/walletkit'
import { Core } from '@walletconnect/core' import { Core } from '@walletconnect/core'
import '@walletconnect/react-native-compat' import '@walletconnect/react-native-compat'
import { PendingRequestTypes, ProposalTypes } from '@walletconnect/types' import { PendingRequestTypes, ProposalTypes } from '@walletconnect/types'
import { buildApprovedNamespaces, getSdkError } from '@walletconnect/utils' import { buildApprovedNamespaces, getSdkError } from '@walletconnect/utils'
import { IWeb3Wallet, Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'
import { Alert } from 'react-native' import { Alert } from 'react-native'
import { EventChannel, eventChannel } from 'redux-saga' import { EventChannel, eventChannel } from 'redux-saga'
import { MobileState } from 'src/app/mobileReducer'
import { registerWCClientForPushNotifications } from 'src/features/walletConnect/api' import { registerWCClientForPushNotifications } from 'src/features/walletConnect/api'
import { fetchDappDetails } from 'src/features/walletConnect/fetchDappDetails'
import { import {
getAccountAddressFromEIP155String, getAccountAddressFromEIP155String,
getChainIdFromEIP155String, getChainIdFromEIP155String,
...@@ -24,13 +26,16 @@ import { ...@@ -24,13 +26,16 @@ import {
import { call, fork, put, select, take } from 'typed-redux-saga' import { call, fork, put, select, take } from 'typed-redux-saga'
import { config } from 'uniswap/src/config' import { config } from 'uniswap/src/config'
import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n/i18n'
import { COMBINED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' import { COMBINED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains'
import { EthEvent, EthMethod } from 'uniswap/src/types/walletConnect' import { EthEvent, EthMethod, WalletConnectEvent } from 'uniswap/src/types/walletConnect'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { selectAccounts, selectActiveAccountAddress } from 'wallet/src/features/wallet/selectors' import { selectAccounts, selectActiveAccountAddress } from 'wallet/src/features/wallet/selectors'
export let wcWeb3Wallet: IWeb3Wallet export let wcWeb3Wallet: IWalletKit
let wcWeb3WalletReadyResolve: () => void let wcWeb3WalletReadyResolve: () => void
let wcWeb3WalletReadyReject: (e: unknown) => void let wcWeb3WalletReadyReject: (e: unknown) => void
...@@ -38,7 +43,6 @@ const wcWeb3WalletReady = new Promise<void>((resolve, reject) => { ...@@ -38,7 +43,6 @@ const wcWeb3WalletReady = new Promise<void>((resolve, reject) => {
wcWeb3WalletReadyResolve = resolve wcWeb3WalletReadyResolve = resolve
wcWeb3WalletReadyReject = reject wcWeb3WalletReadyReject = reject
}) })
export const waitForWcWeb3WalletIsReady = () => wcWeb3WalletReady export const waitForWcWeb3WalletIsReady = () => wcWeb3WalletReady
export async function initializeWeb3Wallet(): Promise<void> { export async function initializeWeb3Wallet(): Promise<void> {
...@@ -47,7 +51,7 @@ export async function initializeWeb3Wallet(): Promise<void> { ...@@ -47,7 +51,7 @@ export async function initializeWeb3Wallet(): Promise<void> {
projectId: config.walletConnectProjectId, projectId: config.walletConnectProjectId,
}) })
wcWeb3Wallet = await Web3Wallet.init({ wcWeb3Wallet = await WalletKit.init({
core: wcCore, core: wcCore,
metadata: { metadata: {
name: 'Uniswap Wallet', name: 'Uniswap Wallet',
...@@ -58,7 +62,7 @@ export async function initializeWeb3Wallet(): Promise<void> { ...@@ -58,7 +62,7 @@ export async function initializeWeb3Wallet(): Promise<void> {
}, },
}) })
const clientId = await wcCore.crypto.getClientId() const clientId = await wcWeb3Wallet.engine.signClient.core.crypto.getClientId()
await registerWCClientForPushNotifications(clientId) await registerWCClientForPushNotifications(clientId)
wcWeb3WalletReadyResolve?.() wcWeb3WalletReadyResolve?.()
} catch (e) { } catch (e) {
...@@ -73,17 +77,17 @@ function createWalletConnectChannel(): EventChannel<AnyAction> { ...@@ -73,17 +77,17 @@ function createWalletConnectChannel(): EventChannel<AnyAction> {
* and the proposal namespaces (chains, methods, events) * and the proposal namespaces (chains, methods, events)
*/ */
const sessionProposalHandler = async ( const sessionProposalHandler = async (
proposalEvent: Omit<Web3WalletTypes.BaseEventArgs<ProposalTypes.Struct>, 'topic'>, proposalEvent: Omit<WalletKitTypes.BaseEventArgs<ProposalTypes.Struct>, 'topic'>,
): Promise<void> => { ): Promise<void> => {
const { params: proposal } = proposalEvent const { params: proposal } = proposalEvent
emit({ type: 'session_proposal', proposal }) emit({ type: 'session_proposal', proposal })
} }
const sessionRequestHandler = async (request: Web3WalletTypes.SessionRequest): Promise<void> => { const sessionRequestHandler = async (request: WalletKitTypes.SessionRequest): Promise<void> => {
emit({ type: 'session_request', request }) emit({ type: 'session_request', request })
} }
const sessionDeleteHandler = async (session: Web3WalletTypes.SessionDelete): Promise<void> => { const sessionDeleteHandler = async (session: WalletKitTypes.SessionDelete): Promise<void> => {
emit({ type: 'session_delete', session }) emit({ type: 'session_delete', session })
} }
...@@ -133,6 +137,28 @@ function showAlert(title: string, message: string): Promise<boolean> { ...@@ -133,6 +137,28 @@ function showAlert(title: string, message: string): Promise<boolean> {
}) })
} }
function* cancelErrorSession(dappName: string, chainLabels: string, proposalId: number) {
yield* call([wcWeb3Wallet, wcWeb3Wallet.rejectSession], {
id: proposalId,
reason: getSdkError('UNSUPPORTED_CHAINS'),
})
yield* call(
showAlert,
i18n.t('walletConnect.error.connection.title'),
i18n.t('walletConnect.error.connection.message', {
chainNames: chainLabels,
dappName,
}),
)
// Set error state to cancel loading state in WalletConnectModal UI
yield* put(setHasPendingSessionError(true))
// Allow users to rescan again
yield* put(setHasPendingSessionError(false))
}
function* handleSessionProposal(proposal: ProposalTypes.Struct) { function* handleSessionProposal(proposal: ProposalTypes.Struct) {
const activeAccountAddress = yield* select(selectActiveAccountAddress) const activeAccountAddress = yield* select(selectActiveAccountAddress)
...@@ -141,6 +167,15 @@ function* handleSessionProposal(proposal: ProposalTypes.Struct) { ...@@ -141,6 +167,15 @@ function* handleSessionProposal(proposal: ProposalTypes.Struct) {
proposer: { metadata: dapp }, proposer: { metadata: dapp },
} = proposal } = proposal
const namespaceCheck = proposal.requiredNamespaces
const firstNamespace = Object.keys(namespaceCheck)[0]
if (firstNamespace && firstNamespace !== 'eip155') {
const chainLabels = COMBINED_CHAIN_IDS.map((chainId) => UNIVERSE_CHAIN_INFO[chainId].label).join(', ')
yield* cancelErrorSession(dapp.name, chainLabels, proposal.id)
return
}
try { try {
const supportedEip155Chains = COMBINED_CHAIN_IDS.map((chainId) => `eip155:${chainId}`) const supportedEip155Chains = COMBINED_CHAIN_IDS.map((chainId) => `eip155:${chainId}`)
const accounts = supportedEip155Chains.map((chain) => `${chain}:${activeAccountAddress}`) const accounts = supportedEip155Chains.map((chain) => `${chain}:${activeAccountAddress}`)
...@@ -188,28 +223,9 @@ function* handleSessionProposal(proposal: ProposalTypes.Struct) { ...@@ -188,28 +223,9 @@ function* handleSessionProposal(proposal: ProposalTypes.Struct) {
}), }),
) )
} catch (e) { } catch (e) {
// Reject pending session if required namespaces includes non-EVM chains or unsupported EVM chains
yield* call([wcWeb3Wallet, wcWeb3Wallet.rejectSession], {
id: proposal.id,
reason: getSdkError('UNSUPPORTED_CHAINS'),
})
const chainLabels = COMBINED_CHAIN_IDS.map((chainId) => UNIVERSE_CHAIN_INFO[chainId].label).join(', ') const chainLabels = COMBINED_CHAIN_IDS.map((chainId) => UNIVERSE_CHAIN_INFO[chainId].label).join(', ')
const confirmed = yield* call( yield* cancelErrorSession(dapp.name, chainLabels, proposal.id)
showAlert,
i18n.t('walletConnect.error.connection.title'),
i18n.t('walletConnect.error.connection.message', {
chainNames: chainLabels,
dappName: dapp.name,
}),
)
if (confirmed) {
yield* put(setHasPendingSessionError(false))
}
// Set error state to cancel loading state in WalletConnectModal UI
yield* put(setHasPendingSessionError(true))
logger.debug( logger.debug(
'WalletConnectSaga', 'WalletConnectSaga',
...@@ -273,9 +289,23 @@ function* handleSessionRequest(sessionRequest: PendingRequestTypes.Struct) { ...@@ -273,9 +289,23 @@ function* handleSessionRequest(sessionRequest: PendingRequestTypes.Struct) {
} }
} }
function* handleSessionDelete(event: Web3WalletTypes.SessionDelete) { function* handleSessionDelete(event: WalletKitTypes.SessionDelete) {
const { topic } = event const { topic } = event
const currentState = yield* select((state: MobileState) => state.walletConnect)
const { dappName, dappIcon } = fetchDappDetails(topic, currentState)
yield* put(
pushNotification({
type: AppNotificationType.WalletConnect,
event: WalletConnectEvent.Disconnected,
dappName,
imageUrl: dappIcon,
hideDelay: 3 * ONE_SECOND_MS,
}),
)
yield* put(removeSession({ sessionId: topic })) yield* put(removeSession({ sessionId: topic }))
} }
......
...@@ -3,14 +3,14 @@ import { wcWeb3Wallet } from 'src/features/walletConnect/saga' ...@@ -3,14 +3,14 @@ import { wcWeb3Wallet } from 'src/features/walletConnect/saga'
import { TransactionRequest, UwuLinkErc20Request } from 'src/features/walletConnect/walletConnectSlice' import { TransactionRequest, UwuLinkErc20Request } from 'src/features/walletConnect/walletConnectSlice'
import { call, put } from 'typed-redux-saga' import { call, put } from 'typed-redux-saga'
import { AssetType } from 'uniswap/src/entities/assets' import { AssetType } from 'uniswap/src/entities/assets'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { getEnabledChainIdsSaga } from 'uniswap/src/features/settings/saga' import { getEnabledChainIdsSaga } from 'uniswap/src/features/settings/saga'
import { TransactionOriginType, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails' import { TransactionOriginType, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails'
import { UniverseChainId } from 'uniswap/src/types/chains' import { UniverseChainId } from 'uniswap/src/types/chains'
import { DappInfo, EthMethod, EthSignMethod, UwULinkMethod, WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { DappInfo, EthMethod, EthSignMethod, UwULinkMethod, WalletConnectEvent } from 'uniswap/src/types/walletConnect'
import { createSaga } from 'uniswap/src/utils/saga' import { createSaga } from 'uniswap/src/utils/saga'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { SendTransactionParams, sendTransaction } from 'wallet/src/features/transactions/sendTransactionSaga' import { SendTransactionParams, sendTransaction } from 'wallet/src/features/transactions/sendTransactionSaga'
import { Account } from 'wallet/src/features/wallet/accounts/types' import { Account } from 'wallet/src/features/wallet/accounts/types'
import { getSignerManager } from 'wallet/src/features/wallet/context' import { getSignerManager } from 'wallet/src/features/wallet/context'
......
import { import {
decodeMessage,
getAccountAddressFromEIP155String, getAccountAddressFromEIP155String,
getChainIdFromEIP155String, getChainIdFromEIP155String,
getSupportedWalletConnectChains, getSupportedWalletConnectChains,
isHexString,
} from 'src/features/walletConnect/utils' } from 'src/features/walletConnect/utils'
import { UniverseChainId } from 'uniswap/src/types/chains' import { UniverseChainId } from 'uniswap/src/types/chains'
...@@ -56,3 +58,41 @@ describe(getChainIdFromEIP155String, () => { ...@@ -56,3 +58,41 @@ describe(getChainIdFromEIP155String, () => {
expect(getChainIdFromEIP155String(EIP155_LINEA_UNSUPPORTED)).toBeNull() expect(getChainIdFromEIP155String(EIP155_LINEA_UNSUPPORTED)).toBeNull()
}) })
}) })
describe('isHexString', () => {
test('should return true for valid hex string', () => {
const validHex = '0x5468697320697320612074657374206d657373616765'
expect(isHexString(validHex)).toBe(true)
})
test('should return false for invalid hex string', () => {
const invalidHex = '546869732069732G20612074657374206d657373616765'
expect(isHexString(invalidHex)).toBe(false)
})
test('should return false for plain text', () => {
const plainText = 'This is a plain text message'
expect(isHexString(plainText)).toBe(false)
})
})
describe('decodeMessage', () => {
test('should decode hex-encoded message', () => {
const hexMessage = '0x5468697320697320612074657374206d657373616765'
const expectedMessage = 'This is a test message'
const result = decodeMessage(hexMessage)
expect(result).toBe(expectedMessage)
})
test('should return original hex string if decoding fails', () => {
const invalidHex = '0x12345'
const result = decodeMessage(invalidHex)
expect(result).toBe(invalidHex)
})
test('should return plain text message unchanged', () => {
const plainText = 'This is a plain text message'
const result = decodeMessage(plainText)
expect(result).toBe(plainText)
})
})
import { WalletKitTypes } from '@reown/walletkit'
import { PairingTypes, ProposalTypes, SessionTypes, SignClientTypes } from '@walletconnect/types' import { PairingTypes, ProposalTypes, SessionTypes, SignClientTypes } from '@walletconnect/types'
import { Web3WalletTypes } from '@walletconnect/web3wallet'
import { utils } from 'ethers' import { utils } from 'ethers'
import { wcWeb3Wallet } from 'src/features/walletConnect/saga' import { wcWeb3Wallet } from 'src/features/walletConnect/saga'
import { SignRequest, TransactionRequest } from 'src/features/walletConnect/walletConnectSlice' import { SignRequest, TransactionRequest } from 'src/features/walletConnect/walletConnectSlice'
...@@ -78,7 +78,7 @@ export const getAccountAddressFromEIP155String = (account: string): Address | nu ...@@ -78,7 +78,7 @@ export const getAccountAddressFromEIP155String = (account: string): Address | nu
* @param {number} internalId id for the WalletConnect signature request * @param {number} internalId id for the WalletConnect signature request
* @param {ChainId} chainId chain the signature is being requested on * @param {ChainId} chainId chain the signature is being requested on
* @param {SignClientTypes.Metadata} dapp metadata for the dapp requesting the signature * @param {SignClientTypes.Metadata} dapp metadata for the dapp requesting the signature
* @param {Web3WalletTypes.SessionRequest['params']['request']['params']} requestParams parameters of the request * @param {WalletKitTypes.SessionRequest['params']['request']['params']} requestParams parameters of the request
* @returns {{Address, SignRequest}} address of the account receiving the request and formatted SignRequest object * @returns {{Address, SignRequest}} address of the account receiving the request and formatted SignRequest object
*/ */
export const parseSignRequest = ( export const parseSignRequest = (
...@@ -87,7 +87,7 @@ export const parseSignRequest = ( ...@@ -87,7 +87,7 @@ export const parseSignRequest = (
internalId: number, internalId: number,
chainId: UniverseChainId, chainId: UniverseChainId,
dapp: SignClientTypes.Metadata, dapp: SignClientTypes.Metadata,
requestParams: Web3WalletTypes.SessionRequest['params']['request']['params'], requestParams: WalletKitTypes.SessionRequest['params']['request']['params'],
): { account: Address; request: SignRequest } => { ): { account: Address; request: SignRequest } => {
const { address, rawMessage, message } = getAddressAndMessageToSign(method, requestParams) const { address, rawMessage, message } = getAddressAndMessageToSign(method, requestParams)
return { return {
...@@ -120,7 +120,7 @@ export const parseSignRequest = ( ...@@ -120,7 +120,7 @@ export const parseSignRequest = (
* @param {number} internalId id for the WalletConnect transaction request * @param {number} internalId id for the WalletConnect transaction request
* @param {UniverseChainId} chainId chain the signature is being requested on * @param {UniverseChainId} chainId chain the signature is being requested on
* @param {SignClientTypes.Metadata} dapp metadata for the dapp requesting the transaction * @param {SignClientTypes.Metadata} dapp metadata for the dapp requesting the transaction
* @param {Web3WalletTypes.SessionRequest['params']['request']['params']} requestParams parameters of the request * @param {WalletKitTypes.SessionRequest['params']['request']['params']} requestParams parameters of the request
* @returns {{Address, TransactionRequest}} address of the account receiving the request and formatted TransactionRequest object * @returns {{Address, TransactionRequest}} address of the account receiving the request and formatted TransactionRequest object
*/ */
export const parseTransactionRequest = ( export const parseTransactionRequest = (
...@@ -129,7 +129,7 @@ export const parseTransactionRequest = ( ...@@ -129,7 +129,7 @@ export const parseTransactionRequest = (
internalId: number, internalId: number,
chainId: UniverseChainId, chainId: UniverseChainId,
dapp: SignClientTypes.Metadata, dapp: SignClientTypes.Metadata,
requestParams: Web3WalletTypes.SessionRequest['params']['request']['params'], requestParams: WalletKitTypes.SessionRequest['params']['request']['params'],
): { account: Address; request: TransactionRequest } => { ): { account: Address; request: TransactionRequest } => {
// Omit gasPrice and nonce in tx sent from dapp since it is calculated later // Omit gasPrice and nonce in tx sent from dapp since it is calculated later
const { from, to, data, gasLimit, value } = requestParams[0] const { from, to, data, gasLimit, value } = requestParams[0]
...@@ -159,6 +159,27 @@ export const parseTransactionRequest = ( ...@@ -159,6 +159,27 @@ export const parseTransactionRequest = (
} }
} }
export function isHexString(value: string): boolean {
// Check if it starts with '0x' and has an even length after the prefix
return /^0x[0-9a-fA-F]+$/.test(value)
}
export function decodeMessage(value: string): string {
if (isHexString(value)) {
try {
return utils.toUtf8String(value)
} catch (error) {
logger.error(error, {
tags: { file: 'walletConnect/util.ts', function: 'decodeMessage' },
})
return value
}
} else {
return value
}
}
/** /**
* Gets the address receiving the request, raw message, decoded message to sign based on the EthSignMethod. * Gets the address receiving the request, raw message, decoded message to sign based on the EthSignMethod.
* `personal_sign` params are ordered as [message, account] * `personal_sign` params are ordered as [message, account]
...@@ -169,11 +190,11 @@ export const parseTransactionRequest = ( ...@@ -169,11 +190,11 @@ export const parseTransactionRequest = (
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
export function getAddressAndMessageToSign( export function getAddressAndMessageToSign(
ethMethod: EthSignMethod, ethMethod: EthSignMethod,
params: Web3WalletTypes.SessionRequest['params']['request']['params'], params: WalletKitTypes.SessionRequest['params']['request']['params'],
): { address: string; rawMessage: string; message: string | null } { ): { address: string; rawMessage: string; message: string | null } {
switch (ethMethod) { switch (ethMethod) {
case EthMethod.PersonalSign: case EthMethod.PersonalSign:
return { address: params[1], rawMessage: params[0], message: utils.toUtf8String(params[0]) } return { address: params[1], rawMessage: params[0], message: decodeMessage(params[0]) }
case EthMethod.EthSign: case EthMethod.EthSign:
return { address: params[0], rawMessage: params[1], message: utils.toUtf8String(params[1]) } return { address: params[0], rawMessage: params[1], message: utils.toUtf8String(params[1]) }
case EthMethod.SignTypedData: case EthMethod.SignTypedData:
...@@ -184,7 +205,7 @@ export function getAddressAndMessageToSign( ...@@ -184,7 +205,7 @@ export function getAddressAndMessageToSign(
export async function pairWithWalletConnectURI(uri: string): Promise<void | PairingTypes.Struct> { export async function pairWithWalletConnectURI(uri: string): Promise<void | PairingTypes.Struct> {
try { try {
return await wcWeb3Wallet.core.pairing.pair({ uri }) return await wcWeb3Wallet.pair({ uri })
} catch (error) { } catch (error) {
logger.error(error, { logger.error(error, {
tags: { file: 'walletConnectV2/utils', function: 'pairWithWalletConnectURI' }, tags: { file: 'walletConnectV2/utils', function: 'pairWithWalletConnectURI' },
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment