ci(release): publish latest release

parent b42b73d0
IPFS hash of the deployment: IPFS hash of the deployment:
- CIDv0: `QmXAfrFgiMF3naNbfoknu5e6H1ussSDJbvb1C1Paw5Ugpi` - CIDv0: `QmbEmY4ebbPrQirzx9PNZGKqyjK7pgUMVce2F2tMtJHbJ5`
- CIDv1: `bafybeiedfku63hfv4tr7zi3kx357dkdjvcoxp4wfv5i75y3qfabtwo3eym` - CIDv1: `bafybeif7uyvbcyhd5ib6xkikxoyvm4voodg4pgagjckny4m52o7qtclpsy`
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,51 @@ You can also access the Uniswap Interface from an IPFS gateway. ...@@ -10,15 +10,51 @@ 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://bafybeiedfku63hfv4tr7zi3kx357dkdjvcoxp4wfv5i75y3qfabtwo3eym.ipfs.dweb.link/ - https://bafybeif7uyvbcyhd5ib6xkikxoyvm4voodg4pgagjckny4m52o7qtclpsy.ipfs.dweb.link/
- https://bafybeiedfku63hfv4tr7zi3kx357dkdjvcoxp4wfv5i75y3qfabtwo3eym.ipfs.cf-ipfs.com/ - https://bafybeif7uyvbcyhd5ib6xkikxoyvm4voodg4pgagjckny4m52o7qtclpsy.ipfs.cf-ipfs.com/
- [ipfs://QmXAfrFgiMF3naNbfoknu5e6H1ussSDJbvb1C1Paw5Ugpi/](ipfs://QmXAfrFgiMF3naNbfoknu5e6H1ussSDJbvb1C1Paw5Ugpi/) - [ipfs://QmbEmY4ebbPrQirzx9PNZGKqyjK7pgUMVce2F2tMtJHbJ5/](ipfs://QmbEmY4ebbPrQirzx9PNZGKqyjK7pgUMVce2F2tMtJHbJ5/)
### 5.23.5 (2024-04-15) ## 5.24.0 (2024-04-17)
### Features
* **web:** [wagmi] Add viem to ethers adapters (#7237) 276faae
* **web:** deprecate token logo lookups (#6921) 0ade412
* **web:** deprecate+delete token safety lookups (#7132) b4642e9
* **web:** update signatures from subscription (#7389) f867870
### Bug Fixes ### Bug Fixes
* **web:** parse native MATIC correctly (#7523) 12a6205 * **web:** allow TrustWallet nodes in CSP (#7515) 0a5b28d
* **web:** change background color of LP warning banner (#7491) f543ab0
* **web:** change warning icon color; add learn more link (#7483) 352be3b
* **web:** checkbox color on open-limits drawer (#7410) 05dfaed
* **web:** dismiss chart tooltip when clicking outside of chart (#7285) 3d97a14
* **web:** ellipsis on unitag text in side drawer (#7386) 471661c
* **web:** fix cypress tests (#7347) e54ceb9
* **web:** fix icon cutoff (#7583) a01e106
* **web:** fix translations with JSX rendering as [object Object] (#7336) 67df76c
* **web:** fix uniswapx e2e tests (#7458) c6de35e
* **web:** fix x-chain token logos (#7374) 4367e3a
* **web:** functions pass-through (#7338) ace3062
* **web:** make sure SimpleToken hits the cache in all cases (#7488) 1be2954
* **web:** parse matic correctly from gql response (#7519) 800eae5
* **web:** remove assets repo fallback for all tokens (#7476) 8e2142c
* **web:** Send crashing on useENSAvatar while disconnected (#7595) 131c6a3
* **web:** set usePoolData errorPolicy to all (#7466) 55e3e8a
* **web:** SimpleTokenDetails fragment to be used in all queries (#7549) 678e624
* **web:** switch currency when input equals output (staging) 20277d2
* **web:** TokenBalanceProvider account change (#7432) 516b781
* **web:** use accent warning soft for outage banner icon wrapper (#7428) cda7ea9
* **web:** use cache-first policy for TopTokens query (#7484) 25d0950
### Code Refactoring
* **web:** isolate the subscription updater (#7387) 6caabbd
* **web:** split out parseRemote signature (#7388) 223e766
* **web:** use a single codepath for functions response transform (#7363) 60471a8
web/5.23.5 web/5.24.0
\ No newline at end of file \ No newline at end of file
...@@ -111,6 +111,7 @@ Add the following to your .rc file ...@@ -111,6 +111,7 @@ Add the following to your .rc file
Install [Android Studio](https://developer.android.com/studio) Install [Android Studio](https://developer.android.com/studio)
Add the following to your .rc file Add the following to your .rc file
``` ```
export ANDROID_HOME=$HOME/Library/Android/sdk export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator export PATH=$PATH:$ANDROID_HOME/emulator
...@@ -175,7 +176,7 @@ These are some tools you might want to familiarize yourself with to understand t ...@@ -175,7 +176,7 @@ These are some tools you might want to familiarize yourself with to understand t
## Migrations ## Migrations
We use `redux-persist` to persist Redux state between user sessions. When the Redux state schema is altered, a migration may be needed to transfer the existing persisted state to the new Redux schema. Failing to define a migration results in the app defaulting to the persisted schema, which will very likely cause `undefined` errors because the code has references to Redux state properties that were dropped in favor the the persisted schema. We use `redux-persist` to persist Redux state between user sessions. When the Redux state schema is altered, a migration may be needed to transfer the existing persisted state to the new Redux schema. Failing to define a migration results in the app defaulting to the persisted schema, which will very likely cause `undefined` errors because the code has references to Redux state properties that were dropped in favor the persisted schema.
### When to define a migration ### When to define a migration
......
...@@ -1151,12 +1151,8 @@ PODS: ...@@ -1151,12 +1151,8 @@ PODS:
- React-Core - React-Core
- react-native-restart (0.0.27): - react-native-restart (0.0.27):
- React-Core - React-Core
- react-native-safe-area-context (4.5.0): - react-native-safe-area-context (4.9.0):
- RCT-Folly
- RCTRequired
- RCTTypeSafety
- React-Core - React-Core
- ReactCommon/turbomodule/core
- react-native-skia (0.1.187): - react-native-skia (0.1.187):
- React - React
- React-callinvoker - React-callinvoker
...@@ -1749,7 +1745,7 @@ SPEC CHECKSUMS: ...@@ -1749,7 +1745,7 @@ SPEC CHECKSUMS:
react-native-onesignal: ab800900cffeca4d9db70a05244013fc8a36ceb8 react-native-onesignal: ab800900cffeca4d9db70a05244013fc8a36ceb8
react-native-pager-view: 3051346698a0ba0c4e13e40097cc11b00ee03cca react-native-pager-view: 3051346698a0ba0c4e13e40097cc11b00ee03cca
react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162 react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b
react-native-skia: e7385e2f5ebe284df53f0def573198fe69a7bd72 react-native-skia: e7385e2f5ebe284df53f0def573198fe69a7bd72
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457 react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581 react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
......
...@@ -4,10 +4,6 @@ const preset = require('../../config/jest-presets/jest/jest-preset') ...@@ -4,10 +4,6 @@ const preset = require('../../config/jest-presets/jest/jest-preset')
module.exports = { module.exports = {
...preset, ...preset,
preset: 'jest-expo', preset: 'jest-expo',
transform: {
...preset.transform,
'^.+\\.jsx?$': 'babel-jest',
},
displayName: 'Mobile Wallet', displayName: 'Mobile Wallet',
collectCoverageFrom: [ collectCoverageFrom: [
'src/**/*.{js,ts,tsx}', 'src/**/*.{js,ts,tsx}',
......
...@@ -171,7 +171,6 @@ ...@@ -171,7 +171,6 @@
"@uniswap/eslint-config": "workspace:^", "@uniswap/eslint-config": "workspace:^",
"@walletconnect/types": "2.11.2", "@walletconnect/types": "2.11.2",
"@welldone-software/why-did-you-render": "7.0.1", "@welldone-software/why-did-you-render": "7.0.1",
"babel-jest": "29.6.1",
"babel-loader": "8.2.3", "babel-loader": "8.2.3",
"babel-plugin-react-native-web": "0.17.5", "babel-plugin-react-native-web": "0.17.5",
"babel-plugin-react-require": "4.0.0", "babel-plugin-react-require": "4.0.0",
......
...@@ -45,15 +45,14 @@ import { ...@@ -45,15 +45,14 @@ import {
getSentryTracesSamplingRate, getSentryTracesSamplingRate,
getStatsigEnvironmentTier, getStatsigEnvironmentTier,
} from 'src/utils/version' } from 'src/utils/version'
import { Statsig, StatsigProvider } from 'statsig-react-native' import { StatsigProvider } from 'statsig-react-native'
import { flexStyles, useIsDarkMode } from 'ui/src' import { flexStyles, useIsDarkMode } from 'ui/src'
import { config } from 'uniswap/src/config' import { config } from 'uniswap/src/config'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { import { DUMMY_STATSIG_SDK_KEY } from 'uniswap/src/features/statsig/constants'
DUMMY_STATSIG_SDK_KEY, import { WALLET_EXPERIMENTS } from 'uniswap/src/features/statsig/experiments'
ExperimentsWallet, import { WALLET_FEATURE_FLAG_NAMES } from 'uniswap/src/features/statsig/flags'
} from 'uniswap/src/features/experiments/constants' import { Statsig } from 'uniswap/src/features/statsig/sdk/statsig'
import { WALLET_FEATURE_FLAG_NAMES } from 'uniswap/src/features/experiments/flags'
import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context' import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n/i18n'
import { CurrencyId } from 'uniswap/src/types/currency' import { CurrencyId } from 'uniswap/src/types/currency'
...@@ -175,12 +174,13 @@ function SentryTags({ children }: PropsWithChildren): JSX.Element { ...@@ -175,12 +174,13 @@ function SentryTags({ children }: PropsWithChildren): JSX.Element {
Sentry.setTag(`featureFlag.${flagKey}`, Statsig.checkGateWithExposureLoggingDisabled(flagKey)) Sentry.setTag(`featureFlag.${flagKey}`, Statsig.checkGateWithExposureLoggingDisabled(flagKey))
} }
Object.entries(ExperimentsWallet).map(([_, experimentName]) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [_, experimentDef] of WALLET_EXPERIMENTS.entries()) {
Sentry.setTag( Sentry.setTag(
`experiment.${experimentName}`, `experiment.${experimentDef.name}`,
Statsig.getExperimentWithExposureLoggingDisabled(experimentName).getGroupName() Statsig.getExperimentWithExposureLoggingDisabled(experimentDef.name).getGroupName()
) )
}) }
}, []) }, [])
return <>{children}</> return <>{children}</>
......
...@@ -6,25 +6,21 @@ import { useAppStackNavigation } from 'src/app/navigation/types' ...@@ -6,25 +6,21 @@ import { useAppStackNavigation } from 'src/app/navigation/types'
import { closeModal, openModal } from 'src/features/modals/modalSlice' import { closeModal, openModal } from 'src/features/modals/modalSlice'
import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex' import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex'
import { Screens } from 'src/screens/Screens' import { Screens } from 'src/screens/Screens'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { import {
NavigateToNftItemArgs, NavigateToNftItemArgs,
NavigateToSendArgs, NavigateToSendFlowArgs,
NavigateToSwapFlowArgs, NavigateToSwapFlowArgs,
ShareNftArgs, ShareNftArgs,
ShareTokenArgs, ShareTokenArgs,
WalletNavigationProvider, WalletNavigationProvider,
getNavigateToSendFlowArgsInitialState,
getNavigateToSwapFlowArgsInitialState, getNavigateToSwapFlowArgsInitialState,
} from 'wallet/src/contexts/WalletNavigationContext' } from 'wallet/src/contexts/WalletNavigationContext'
import { AssetType } from 'wallet/src/entities/assets'
import { useFiatOnRampIpAddressQuery } from 'wallet/src/features/fiatOnRamp/api' import { useFiatOnRampIpAddressQuery } from 'wallet/src/features/fiatOnRamp/api'
import {
CurrencyField,
TransactionState,
} from 'wallet/src/features/transactions/transactionState/types'
import { sendWalletAnalyticsEvent } from 'wallet/src/telemetry' import { sendWalletAnalyticsEvent } from 'wallet/src/telemetry'
import { ModalName, ShareableEntity, WalletEventName } from 'wallet/src/telemetry/constants' import { ModalName, ShareableEntity, WalletEventName } from 'wallet/src/telemetry/constants'
import { getNftUrl, getTokenUrl } from 'wallet/src/utils/linking' import { getNftUrl, getTokenUrl } from 'wallet/src/utils/linking'
...@@ -122,24 +118,12 @@ function useNavigateToReceive(): () => void { ...@@ -122,24 +118,12 @@ function useNavigateToReceive(): () => void {
}, [dispatch]) }, [dispatch])
} }
function useNavigateToSend(): (args: NavigateToSendArgs) => void { function useNavigateToSend(): (args: NavigateToSendFlowArgs) => void {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
return useCallback( return useCallback(
(args: NavigateToSendArgs) => { (args: NavigateToSendFlowArgs) => {
const initialSendState: TransactionState = { const initialSendState = getNavigateToSendFlowArgsInitialState(args)
exactCurrencyField: CurrencyField.INPUT,
exactAmountToken: '',
[CurrencyField.INPUT]: args
? {
address: args.currencyAddress,
chainId: args.chainId,
type: AssetType.Currency,
}
: null,
[CurrencyField.OUTPUT]: null,
showRecipientSelector: true,
}
dispatch(openModal({ name: ModalName.Send, initialState: initialSendState })) dispatch(openModal({ name: ModalName.Send, initialState: initialSendState }))
}, },
[dispatch] [dispatch]
...@@ -160,9 +144,18 @@ function useNavigateToSwapFlow(): (args: NavigateToSwapFlowArgs) => void { ...@@ -160,9 +144,18 @@ function useNavigateToSwapFlow(): (args: NavigateToSwapFlowArgs) => void {
} }
function useNavigateToTokenDetails(): (currencyId: string) => void { function useNavigateToTokenDetails(): (currencyId: string) => void {
return useCallback((currencyId: string): void => { const appNavigation = useAppStackNavigation()
exploreNavigationRef.navigate(Screens.TokenDetails, { currencyId })
}, []) return useCallback(
(currencyId: string): void => {
if (exploreNavigationRef.isFocused()) {
exploreNavigationRef.navigate(Screens.TokenDetails, { currencyId })
} else {
appNavigation.navigate(Screens.TokenDetails, { currencyId })
}
},
[appNavigation]
)
} }
function useNavigateToNftDetails(): (args: NavigateToNftItemArgs) => void { function useNavigateToNftDetails(): (args: NavigateToNftItemArgs) => void {
......
...@@ -62,6 +62,7 @@ import { ...@@ -62,6 +62,7 @@ import {
v5Schema, v5Schema,
v60Schema, v60Schema,
v61Schema, v61Schema,
v62Schema,
v6Schema, v6Schema,
v7Schema, v7Schema,
v8Schema, v8Schema,
...@@ -1390,4 +1391,11 @@ describe('Redux state migrations', () => { ...@@ -1390,4 +1391,11 @@ describe('Redux state migrations', () => {
expect(v62.behaviorHistory.extensionOnboardingState).toBe(ExtensionOnboardingState.Undefined) expect(v62.behaviorHistory.extensionOnboardingState).toBe(ExtensionOnboardingState.Undefined)
}) })
it('migrates from v62 to 63', () => {
const v62Stub = { ...v62Schema }
const v63 = migrations[63](v62Stub)
expect(v63.wallet.isUnlocked).toBe(undefined)
})
}) })
...@@ -875,4 +875,11 @@ export const migrations = { ...@@ -875,4 +875,11 @@ export const migrations = {
return newState return newState
}, },
63: function removeWalletIsUnlockedState(state: any) {
const newState = { ...state }
delete newState.wallet.isUnlocked
return newState
},
} }
...@@ -22,8 +22,8 @@ import { ...@@ -22,8 +22,8 @@ import {
useSporeColors, useSporeColors,
} from 'ui/src' } from 'ui/src'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'uniswap/src/utils/platform'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { ActionSheetModal, MenuItemProp } from 'wallet/src/components/modals/ActionSheetModal' import { ActionSheetModal, MenuItemProp } from 'wallet/src/components/modals/ActionSheetModal'
......
...@@ -6,11 +6,6 @@ import { useAppDispatch, useAppSelector } from 'src/app/hooks' ...@@ -6,11 +6,6 @@ import { useAppDispatch, useAppSelector } from 'src/app/hooks'
import { closeModal } from 'src/features/modals/modalSlice' import { closeModal } from 'src/features/modals/modalSlice'
import { selectCustomEndpoint } from 'src/features/tweaks/selectors' import { selectCustomEndpoint } from 'src/features/tweaks/selectors'
import { setCustomEndpoint } from 'src/features/tweaks/slice' import { setCustomEndpoint } from 'src/features/tweaks/slice'
import {
ConfigResult,
Statsig,
useExperimentWithExposureLoggingDisabled,
} from 'statsig-react-native'
import { import {
Accordion, Accordion,
Button, Button,
...@@ -23,15 +18,20 @@ import { ...@@ -23,15 +18,20 @@ import {
} from 'ui/src' } from 'ui/src'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { import {
EXPERIMENT_VALUES_BY_EXPERIMENT, Experiments,
ExperimentsWallet, WALLET_EXPERIMENTS,
} from 'uniswap/src/features/experiments/constants' getExperimentDefinition,
} from 'uniswap/src/features/statsig/experiments'
import { import {
FeatureFlags, FeatureFlags,
WALLET_FEATURE_FLAG_NAMES, WALLET_FEATURE_FLAG_NAMES,
getFeatureFlagName, getFeatureFlagName,
} from 'uniswap/src/features/experiments/flags' } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlagWithExposureLoggingDisabled } from 'uniswap/src/features/experiments/hooks' import {
useExperimentValueWithExposureLoggingDisabled,
useFeatureFlagWithExposureLoggingDisabled,
} from 'uniswap/src/features/statsig/hooks'
import { Statsig } from 'uniswap/src/features/statsig/sdk/statsig'
import { Switch } from 'wallet/src/components/buttons/Switch' import { Switch } from 'wallet/src/components/buttons/Switch'
import { TextInput } from 'wallet/src/components/input/TextInput' import { TextInput } from 'wallet/src/components/input/TextInput'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
...@@ -65,11 +65,16 @@ export function ExperimentsModal(): JSX.Element { ...@@ -65,11 +65,16 @@ export function ExperimentsModal(): JSX.Element {
} }
} }
const featureFlagRows = [] const featureFlagRows: JSX.Element[] = []
for (const [flag, flagName] of WALLET_FEATURE_FLAG_NAMES.entries()) { for (const [flag, flagName] of WALLET_FEATURE_FLAG_NAMES.entries()) {
featureFlagRows.push(<FeatureFlagRow key={flagName} flag={flag} />) featureFlagRows.push(<FeatureFlagRow key={flagName} flag={flag} />)
} }
const experimentRows: JSX.Element[] = []
for (const [experiment, experimentDef] of WALLET_EXPERIMENTS.entries()) {
experimentRows.push(<ExperimentRow key={experimentDef.name} experiment={experiment} />)
}
return ( return (
<BottomSheetModal <BottomSheetModal
fullScreen fullScreen
...@@ -150,9 +155,7 @@ export function ExperimentsModal(): JSX.Element { ...@@ -150,9 +155,7 @@ export function ExperimentsModal(): JSX.Element {
</Text> </Text>
<Flex gap="$spacing24" mt="$spacing12"> <Flex gap="$spacing24" mt="$spacing12">
{Object.values(ExperimentsWallet).map((experiment) => { {experimentRows}
return <ExperimentRow key={experiment} name={experiment} />
})}
</Flex> </Flex>
</Accordion.Content> </Accordion.Content>
</Accordion.Item> </Accordion.Item>
...@@ -196,75 +199,52 @@ function FeatureFlagRow({ flag }: { flag: FeatureFlags }): JSX.Element { ...@@ -196,75 +199,52 @@ function FeatureFlagRow({ flag }: { flag: FeatureFlags }): JSX.Element {
) )
} }
function ExperimentRow({ name }: { name: string }): JSX.Element { function ExperimentRow({ experiment }: { experiment: Experiments }): JSX.Element {
const experiment = useExperimentWithExposureLoggingDisabled(name) const experimentDef = getExperimentDefinition(experiment)
const params = Object.entries(experiment.config.value).map(([key, value]) => (
<Flex
key={key}
row
alignItems="center"
gap="$spacing16"
justifyContent="space-between"
paddingStart="$spacing16">
<Text variant="body2">{key}</Text>
<ExperimentValueSwitch
configValueContent={value}
configValueName={key}
experiment={experiment}
/>
</Flex>
))
return ( return (
<> <>
<Separator /> <Separator />
<Flex> <Flex>
<Text variant="body1">{name}</Text> <Text variant="body1">{experimentDef.name}</Text>
<Flex gap="$spacing4">{params}</Flex> <Flex gap="$spacing4">
<Flex
key={experimentDef.name}
row
alignItems="center"
gap="$spacing16"
justifyContent="space-between"
paddingStart="$spacing16">
<Text variant="body2" />
<ExperimentValueSwitch experiment={experiment} />
</Flex>
</Flex>
</Flex> </Flex>
</> </>
) )
} }
function ExperimentValueSwitch({ function ExperimentValueSwitch({ experiment }: { experiment: Experiments }): JSX.Element {
experiment,
configValueContent,
configValueName,
}: {
experiment: ConfigResult
configValueContent: unknown
configValueName: string
}): JSX.Element {
const colors = useSporeColors() const colors = useSporeColors()
const experimentName = experiment.config.getName() const experimentDef = getExperimentDefinition(experiment)
const currentValue = useExperimentValueWithExposureLoggingDisabled(experiment)
const onValueChange = (newValue: boolean | string): void => {
Statsig.overrideConfig(experimentName, {
...experiment.config.value,
[configValueName]: newValue,
})
}
if (typeof configValueContent === 'boolean') { return (
return <Switch value={configValueContent} onValueChange={onValueChange} /> <Flex gap="$spacing8">
} {experimentDef.values.map((value) => (
<Flex
const variants = EXPERIMENT_VALUES_BY_EXPERIMENT[experimentName]?.[configValueName] key={value}
gap="$spacing4"
if (variants && typeof configValueContent === 'string') { onPressOut={() => {
return ( Statsig.overrideConfig(experimentDef.name, {
<Flex gap="$spacing8"> [experimentDef.key]: value,
{Object.entries(variants).map(([_, value]) => ( })
<Flex key={value} gap="$spacing4" onPressOut={(): void => onValueChange(value)}> }}>
<Text color={value === configValueContent ? colors.accent1.val : colors.neutral1.val}> <Text color={value === currentValue ? colors.accent1.val : colors.neutral1.val}>
{value} {value}
</Text> </Text>
</Flex> </Flex>
))} ))}
</Flex> </Flex>
) )
}
return <Text variant="body3">Unknown Variants</Text>
} }
...@@ -5,8 +5,8 @@ import { selectModalState } from 'src/features/modals/selectModalState' ...@@ -5,8 +5,8 @@ import { selectModalState } from 'src/features/modals/selectModalState'
import { TransferFlow } from 'src/features/transactions/transfer/TransferFlow' import { TransferFlow } from 'src/features/transactions/transfer/TransferFlow'
import { TransferFlow as TransferFlowRewrite } from 'src/features/transactions/transfer/transferRewrite/TransferFlow' import { TransferFlow as TransferFlowRewrite } from 'src/features/transactions/transfer/transferRewrite/TransferFlow'
import { useSporeColors } from 'ui/src' import { useSporeColors } from 'ui/src'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
......
...@@ -93,7 +93,7 @@ export function useEagerExternalProfileRootNavigation(): { ...@@ -93,7 +93,7 @@ export function useEagerExternalProfileRootNavigation(): {
/** /**
* Utility hook that checks if the caller is part of the navigation tree. * Utility hook that checks if the caller is part of the navigation tree.
* *
* Inspired by how the navigation library checks if the the navigation object exists. * Inspired by how the navigation library checks if the navigation object exists.
* https://github.com/react-navigation/react-navigation/blob/d7032ba8bb6ae24030a47f0724b61b561132fca6/packages/core/src/useNavigation.tsx#L18 * https://github.com/react-navigation/react-navigation/blob/d7032ba8bb6ae24030a47f0724b61b561132fca6/packages/core/src/useNavigation.tsx#L18
*/ */
export function useIsPartOfNavigationTree(): boolean { export function useIsPartOfNavigationTree(): boolean {
......
...@@ -64,8 +64,8 @@ import { TokenDetailsScreen } from 'src/screens/TokenDetailsScreen' ...@@ -64,8 +64,8 @@ import { TokenDetailsScreen } from 'src/screens/TokenDetailsScreen'
import { WebViewScreen } from 'src/screens/WebViewScreen' import { WebViewScreen } from 'src/screens/WebViewScreen'
import { Icons, useDeviceInsets, useSporeColors } from 'ui/src' import { Icons, useDeviceInsets, useSporeColors } from 'ui/src'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { OnboardingEntryPoint } from 'wallet/src/features/onboarding/types' import { OnboardingEntryPoint } from 'wallet/src/features/onboarding/types'
import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks'
import { selectFinishedOnboarding } from 'wallet/src/features/wallet/selectors' import { selectFinishedOnboarding } from 'wallet/src/features/wallet/selectors'
......
...@@ -475,6 +475,19 @@ export const v62Schema = { ...@@ -475,6 +475,19 @@ export const v62Schema = {
}, },
} }
const v63SchemaIntermediate = {
...v62Schema,
wallet: {
...v62Schema.wallet,
isUnlocked: undefined,
},
}
// We will no longer keep track of this in the redux state.
delete v63SchemaIntermediate.wallet.isUnlocked
export const v63Schema = v63SchemaIntermediate
// 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 v62Schema => v62Schema export const getSchema = (): typeof v63Schema => v63Schema
...@@ -75,7 +75,7 @@ export const persistConfig = { ...@@ -75,7 +75,7 @@ export const persistConfig = {
key: 'root', key: 'root',
storage: reduxStorage, storage: reduxStorage,
whitelist, whitelist,
version: 62, version: 63,
migrate: createMigrate(migrations), migrate: createMigrate(migrations),
} }
......
...@@ -128,7 +128,9 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -128,7 +128,9 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
}, [permissionStatus, requestPermissionResponse, t]) }, [permissionStatus, requestPermissionResponse, t])
const overlayWidth = (overlayLayout?.height ?? 0) / CAMERA_ASPECT_RATIO const overlayWidth = (overlayLayout?.height ?? 0) / CAMERA_ASPECT_RATIO
const scannerSize = Math.min(overlayWidth, dimensions.fullWidth) * SCAN_ICON_WIDTH_RATIO const cameraWidth = dimensions.fullWidth
const cameraHeight = CAMERA_ASPECT_RATIO * cameraWidth
const scannerSize = Math.min(overlayWidth, cameraWidth) * SCAN_ICON_WIDTH_RATIO
return ( return (
<AnimatedFlex <AnimatedFlex
...@@ -138,10 +140,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -138,10 +140,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
exiting={FadeOut} exiting={FadeOut}
overflow="hidden"> overflow="hidden">
<Flex justifyContent="center" style={StyleSheet.absoluteFill}> <Flex justifyContent="center" style={StyleSheet.absoluteFill}>
<Flex <Flex height={cameraHeight} overflow="hidden" width={cameraWidth}>
height={Math.max(dimensions.fullHeight, CAMERA_ASPECT_RATIO * dimensions.fullWidth)}
overflow="hidden"
width={dimensions.fullWidth}>
{permissionStatus === PermissionStatus.GRANTED && !isReadingImageFile && ( {permissionStatus === PermissionStatus.GRANTED && !isReadingImageFile && (
<Camera <Camera
barCodeScannerSettings={{ barCodeScannerSettings={{
......
...@@ -61,7 +61,6 @@ const getPreloadedState = (props?: PreloadedStateProps): PreloadedState<MobileSt ...@@ -61,7 +61,6 @@ const getPreloadedState = (props?: PreloadedStateProps): PreloadedState<MobileSt
...(hasInactiveAccounts && { [inactiveAccount.address]: inactiveAccount }), ...(hasInactiveAccounts && { [inactiveAccount.address]: inactiveAccount }),
}, },
activeAccountAddress: activeAccount.address, activeAccountAddress: activeAccount.address,
isUnlocked: true,
settings: { settings: {
swapProtection: SwapProtectionSetting.On, swapProtection: SwapProtectionSetting.On,
hideSmallBalances: false, hideSmallBalances: false,
......
...@@ -29,6 +29,7 @@ type ModalWithOverlayProps = PropsWithChildren< ...@@ -29,6 +29,7 @@ type ModalWithOverlayProps = PropsWithChildren<
scrollDownButtonText?: string scrollDownButtonText?: string
onReject: () => void onReject: () => void
onConfirm: () => void onConfirm: () => void
disableConfirm?: boolean
} }
> >
...@@ -46,6 +47,7 @@ export function ModalWithOverlay({ ...@@ -46,6 +47,7 @@ export function ModalWithOverlay({
scrollDownButtonText, scrollDownButtonText,
onReject, onReject,
onConfirm, onConfirm,
disableConfirm,
...bottomSheetModalProps ...bottomSheetModalProps
}: ModalWithOverlayProps): JSX.Element { }: ModalWithOverlayProps): JSX.Element {
const scrollViewRef = useRef<ScrollView>(null) const scrollViewRef = useRef<ScrollView>(null)
...@@ -124,7 +126,7 @@ export function ModalWithOverlay({ ...@@ -124,7 +126,7 @@ export function ModalWithOverlay({
<ModalFooter <ModalFooter
confirmationButtonText={confirmationButtonText} confirmationButtonText={confirmationButtonText}
confirmationEnabled={confirmationEnabled} confirmationEnabled={!disableConfirm && confirmationEnabled}
scrollDownButtonText={scrollDownButtonText} scrollDownButtonText={scrollDownButtonText}
showScrollDownOverlay={showOverlay} showScrollDownOverlay={showOverlay}
onConfirm={onConfirm} onConfirm={onConfirm}
......
...@@ -3,7 +3,7 @@ import React from 'react' ...@@ -3,7 +3,7 @@ import React from 'react'
import { Trans } from 'react-i18next' import { Trans } from 'react-i18next'
import { WalletConnectRequest } from 'src/features/walletConnect/walletConnectSlice' import { WalletConnectRequest } from 'src/features/walletConnect/walletConnectSlice'
import { Text } from 'ui/src' import { Text } from 'ui/src'
import { EthMethod } from 'wallet/src/features/walletConnect/types' import { EthMethod, UwULinkMethod } from 'wallet/src/features/walletConnect/types'
import { ValueType, getCurrencyAmount } from 'wallet/src/utils/getCurrencyAmount' import { ValueType, getCurrencyAmount } from 'wallet/src/utils/getCurrencyAmount'
export function HeaderText({ export function HeaderText({
...@@ -54,13 +54,17 @@ export function HeaderText({ ...@@ -54,13 +54,17 @@ export function HeaderText({
) )
} }
const getReadableMethodName = (ethMethod: EthMethod, dappNameOrUrl: string): JSX.Element => { const getReadableMethodName = (
ethMethod: EthMethod | UwULinkMethod,
dappNameOrUrl: string
): JSX.Element => {
switch (ethMethod) { switch (ethMethod) {
case EthMethod.PersonalSign: case EthMethod.PersonalSign:
case EthMethod.EthSign: case EthMethod.EthSign:
case EthMethod.SignTypedData: case EthMethod.SignTypedData:
return <Trans i18nKey="qrScanner.request.method.signature" values={{ dappNameOrUrl }} /> return <Trans i18nKey="qrScanner.request.method.signature" values={{ dappNameOrUrl }} />
case EthMethod.EthSendTransaction: case EthMethod.EthSendTransaction:
case UwULinkMethod.Erc20Send:
return <Trans i18nKey="qrScanner.request.method.transaction" values={{ dappNameOrUrl }} /> return <Trans i18nKey="qrScanner.request.method.transaction" values={{ dappNameOrUrl }} />
} }
......
import { useBottomSheetInternal } from '@gorhom/bottom-sheet'
import { formatUnits } from 'ethers/lib/utils'
import { useTranslation } from 'react-i18next'
import Animated, { useAnimatedStyle } from 'react-native-reanimated'
import { ModalWithOverlay } from 'src/components/WalletConnect/ModalWithOverlay/ModalWithOverlay'
import { UwuLinkErc20Request } from 'src/features/walletConnect/walletConnectSlice'
import { Flex, Text } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { CurrencyInfo } from 'uniswap/src/features/dataApi/types'
import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo'
import { SpinningLoader } from 'wallet/src/components/loading/SpinningLoader'
import { CHAIN_INFO } from 'wallet/src/constants/chains'
import { useOnChainCurrencyBalance } from 'wallet/src/features/portfolio/api'
import { NativeCurrency } from 'wallet/src/features/tokens/NativeCurrency'
import { useCurrencyInfo } from 'wallet/src/features/tokens/useCurrencyInfo'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
import { ModalName } from 'wallet/src/telemetry/constants'
import { buildCurrencyId } from 'wallet/src/utils/currencyId'
type Props = {
onClose: () => void
onConfirm: () => void
onReject: () => void
request: UwuLinkErc20Request
hasSufficientGasFunds: boolean
}
export function UwULinkErc20SendModal({
onClose,
onConfirm,
onReject,
request,
hasSufficientGasFunds,
}: Props): JSX.Element {
const { t } = useTranslation()
const activeAccountAddress = useActiveAccountAddressWithThrow()
// TODO: wallet should determine if the currency is stablecoin
const { chainId, tokenAddress, amount } = request
const currencyInfo = useCurrencyInfo(buildCurrencyId(chainId, tokenAddress))
const { balance } = useOnChainCurrencyBalance(currencyInfo?.currency, activeAccountAddress)
const hasSufficientTokenFunds = !balance?.lessThan(amount)
return (
<ModalWithOverlay
confirmationButtonText={t('common.button.pay')}
disableConfirm={!hasSufficientTokenFunds || !hasSufficientGasFunds}
name={ModalName.UwULinkErc20SendModal}
scrollDownButtonText={t('walletConnect.request.button.scrollDown')}
onClose={onClose}
onConfirm={onConfirm}
onReject={onReject}>
<UwULinkErc20SendModalContent
currencyInfo={currencyInfo}
hasSufficientGasFunds={hasSufficientGasFunds}
hasSufficientTokenFunds={hasSufficientTokenFunds}
loading={!balance || !currencyInfo}
request={request}
/>
</ModalWithOverlay>
)
}
function UwULinkErc20SendModalContent({
request,
loading,
currencyInfo,
hasSufficientGasFunds,
hasSufficientTokenFunds,
}: {
request: UwuLinkErc20Request
loading: boolean
hasSufficientGasFunds: boolean
hasSufficientTokenFunds: boolean
currencyInfo: Maybe<CurrencyInfo>
}): JSX.Element {
const { t } = useTranslation()
const { animatedFooterHeight } = useBottomSheetInternal()
const bottomSpacerStyle = useAnimatedStyle(() => ({
height: animatedFooterHeight.value,
}))
const { chainId, isStablecoin } = request
const nativeCurrency = chainId && NativeCurrency.onChain(chainId)
if (loading || !currencyInfo) {
return (
<Flex centered py="$spacing12">
<SpinningLoader color="$accent1" size={iconSizes.icon64} />
<Animated.View style={bottomSpacerStyle} />
</Flex>
)
}
const {
logoUrl,
currency: { name, symbol, decimals },
} = currencyInfo
return (
<Flex centered gap="$spacing12" justifyContent="space-between">
<Text variant="subheading1">{request.recipient.name}</Text>
<Flex centered flex={1} gap="$spacing12" py="$spacing16">
{!hasSufficientTokenFunds && (
<Text color="red">
{t('uwulink.error.insufficientTokens', {
tokenSymbol: symbol,
chain: CHAIN_INFO[chainId].label,
})}
</Text>
)}
<Text fontSize={64} my="$spacing4" pt={42}>{`${isStablecoin ? '$' : ''}${formatUnits(
request.amount,
decimals
)}`}</Text>
<Flex row gap="$spacing4">
<TokenLogo
chainId={chainId}
name={name}
size={iconSizes.icon24}
symbol={symbol}
url={logoUrl}
/>
<Text>{symbol}</Text>
</Flex>
</Flex>
{!hasSufficientGasFunds && (
<Text color="$DEP_accentWarning" pt="$spacing8" textAlign="center" variant="body3">
{t('walletConnect.request.error.insufficientFunds', {
currencySymbol: nativeCurrency?.symbol,
})}
</Text>
)}
<Animated.View style={bottomSpacerStyle} />
</Flex>
)
}
...@@ -18,21 +18,26 @@ import { wcWeb3Wallet } from 'src/features/walletConnect/saga' ...@@ -18,21 +18,26 @@ 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 { import {
SignRequest, WalletConnectRequest,
TransactionRequest,
isTransactionRequest, isTransactionRequest,
} from 'src/features/walletConnect/walletConnectSlice' } from 'src/features/walletConnect/walletConnectSlice'
import { useTransactionGasFee } from 'wallet/src/features/gas/hooks' import { useTransactionGasFee } from 'wallet/src/features/gas/hooks'
import { GasSpeed } from 'wallet/src/features/gas/types' import { GasSpeed } from 'wallet/src/features/gas/types'
import { useIsBlocked, useIsBlockedActiveAddress } from 'wallet/src/features/trm/hooks' import { useIsBlocked, useIsBlockedActiveAddress } from 'wallet/src/features/trm/hooks'
import { useSignerAccounts } from 'wallet/src/features/wallet/hooks' import { useSignerAccounts } from 'wallet/src/features/wallet/hooks'
import { EthMethod, WCEventType, WCRequestOutcome } from 'wallet/src/features/walletConnect/types' import {
EthMethod,
UwULinkMethod,
WCEventType,
WCRequestOutcome,
} from 'wallet/src/features/walletConnect/types'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
import { areAddressesEqual } from 'wallet/src/utils/addresses' import { areAddressesEqual } from 'wallet/src/utils/addresses'
import { UwULinkErc20SendModal } from './UwULinkErc20SendModal'
interface Props { interface Props {
onClose: () => void onClose: () => void
request: SignRequest | TransactionRequest request: WalletConnectRequest
} }
const VALID_REQUEST_TYPES = [ const VALID_REQUEST_TYPES = [
...@@ -41,6 +46,7 @@ const VALID_REQUEST_TYPES = [ ...@@ -41,6 +46,7 @@ const VALID_REQUEST_TYPES = [
EthMethod.SignTypedDataV4, EthMethod.SignTypedDataV4,
EthMethod.EthSign, EthMethod.EthSign,
EthMethod.EthSendTransaction, EthMethod.EthSendTransaction,
UwULinkMethod.Erc20Send,
] ]
export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Element | null { export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Element | null {
...@@ -62,6 +68,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -62,6 +68,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
areAddressesEqual(account.address, request.account) areAddressesEqual(account.address, request.account)
) )
const gasFee = useTransactionGasFee(tx, GasSpeed.Urgent) const gasFee = useTransactionGasFee(tx, GasSpeed.Urgent)
const hasSufficientFunds = useHasSufficientFunds({ const hasSufficientFunds = useHasSufficientFunds({
account: request.account, account: request.account,
chainId, chainId,
...@@ -145,7 +152,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -145,7 +152,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
if (!confirmEnabled || !signerAccount) { if (!confirmEnabled || !signerAccount) {
return return
} }
if (request.type === EthMethod.EthSendTransaction) { if (request.type === EthMethod.EthSendTransaction || request.type === UwULinkMethod.Erc20Send) {
if (!gasFee.params) { if (!gasFee.params) {
return return
} // appeasing typescript } // appeasing typescript
...@@ -153,7 +160,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -153,7 +160,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
signWcRequestActions.trigger({ signWcRequestActions.trigger({
sessionId: request.sessionId, sessionId: request.sessionId,
requestInternalId: request.internalId, requestInternalId: request.internalId,
method: request.type, method: EthMethod.EthSendTransaction,
transaction: { ...tx, ...gasFee.params }, transaction: { ...tx, ...gasFee.params },
account: signerAccount, account: signerAccount,
dapp: request.dapp, dapp: request.dapp,
...@@ -198,6 +205,14 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -198,6 +205,14 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
const { trigger: actionButtonTrigger } = useBiometricPrompt(onConfirm) const { trigger: actionButtonTrigger } = useBiometricPrompt(onConfirm)
const { requiredForTransactions } = useBiometricAppSettings() const { requiredForTransactions } = useBiometricAppSettings()
const onConfirmPress = async (): Promise<void> => {
if (requiredForTransactions) {
await actionButtonTrigger()
} else {
await onConfirm()
}
}
if (!VALID_REQUEST_TYPES.includes(request.type)) { if (!VALID_REQUEST_TYPES.includes(request.type)) {
return null return null
} }
...@@ -210,6 +225,18 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -210,6 +225,18 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
} }
} }
if (request.type === UwULinkMethod.Erc20Send) {
return (
<UwULinkErc20SendModal
hasSufficientGasFunds={hasSufficientFunds}
request={request}
onClose={handleClose}
onConfirm={onConfirmPress}
onReject={onReject}
/>
)
}
return ( return (
<ModalWithOverlay <ModalWithOverlay
confirmationButtonText={ confirmationButtonText={
...@@ -217,16 +244,11 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -217,16 +244,11 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
? t('common.button.accept') ? t('common.button.accept')
: t('walletConnect.request.button.sign') : t('walletConnect.request.button.sign')
} }
disableConfirm={!hasSufficientFunds || Boolean(gasFee.error)}
name={ModalName.WCSignRequest} name={ModalName.WCSignRequest}
scrollDownButtonText={t('walletConnect.request.button.scrollDown')} scrollDownButtonText={t('walletConnect.request.button.scrollDown')}
onClose={handleClose} onClose={handleClose}
onConfirm={async (): Promise<void> => { onConfirm={onConfirmPress}
if (requiredForTransactions) {
await actionButtonTrigger()
} else {
await onConfirm()
}
}}
onReject={onReject}> onReject={onReject}>
<WalletConnectRequestModalContent <WalletConnectRequestModalContent
gasFee={gasFee} gasFee={gasFee}
......
...@@ -9,6 +9,7 @@ import Animated, { useAnimatedStyle } from 'react-native-reanimated' ...@@ -9,6 +9,7 @@ import Animated, { useAnimatedStyle } from 'react-native-reanimated'
import { ClientDetails, PermitInfo } from 'src/components/WalletConnect/RequestModal/ClientDetails' import { ClientDetails, PermitInfo } from 'src/components/WalletConnect/RequestModal/ClientDetails'
import { RequestDetails } from 'src/components/WalletConnect/RequestModal/RequestDetails' import { RequestDetails } from 'src/components/WalletConnect/RequestModal/RequestDetails'
import { import {
SignRequest,
TransactionRequest, TransactionRequest,
WalletConnectRequest, WalletConnectRequest,
isTransactionRequest, isTransactionRequest,
...@@ -52,7 +53,9 @@ const getPermitInfo = (request: WalletConnectRequest): PermitInfo | undefined => ...@@ -52,7 +53,9 @@ const getPermitInfo = (request: WalletConnectRequest): PermitInfo | undefined =>
return { currencyId, amount } return { currencyId, amount }
} catch (error) { } catch (error) {
logger.error(error, { tags: { file: 'WalletConnectRequestModal', function: 'getPermitInfo' } }) logger.error(error, {
tags: { file: 'WalletConnectRequestModal', function: 'getPermitInfo' },
})
return undefined return undefined
} }
} }
...@@ -60,7 +63,7 @@ const getPermitInfo = (request: WalletConnectRequest): PermitInfo | undefined => ...@@ -60,7 +63,7 @@ const getPermitInfo = (request: WalletConnectRequest): PermitInfo | undefined =>
type WalletConnectRequestModalContentProps = { type WalletConnectRequestModalContentProps = {
gasFee: GasFeeResult gasFee: GasFeeResult
hasSufficientFunds: boolean hasSufficientFunds: boolean
request: WalletConnectRequest request: SignRequest | TransactionRequest
isBlocked: boolean isBlocked: boolean
} }
......
import React from 'react' import React from 'react'
import { Flex, Separator, Text, Unicon, UniconV2, useSporeColors } from 'ui/src' import { Flex, Separator, Text, Unicon, UniconV2, useSporeColors } from 'ui/src'
import Check from 'ui/src/assets/icons/check.svg' import Check from 'ui/src/assets/icons/check.svg'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { DisplayNameText } from 'wallet/src/components/accounts/DisplayNameText' import { DisplayNameText } from 'wallet/src/components/accounts/DisplayNameText'
import { Account } from 'wallet/src/features/wallet/accounts/types' import { Account } from 'wallet/src/features/wallet/accounts/types'
import { useDisplayName } from 'wallet/src/features/wallet/hooks' import { useDisplayName } from 'wallet/src/features/wallet/hooks'
......
...@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react' ...@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, 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 { useAppDispatch, useAppSelector } from 'src/app/hooks' import { useAppDispatch } from 'src/app/hooks'
import { useEagerExternalProfileRootNavigation } from 'src/app/navigation/hooks' import { useEagerExternalProfileRootNavigation } from 'src/app/navigation/hooks'
import { QRCodeScanner } from 'src/components/QRCodeScanner/QRCodeScanner' import { QRCodeScanner } from 'src/components/QRCodeScanner/QRCodeScanner'
import Trace from 'src/components/Trace/Trace' import Trace from 'src/components/Trace/Trace'
...@@ -10,8 +10,10 @@ import { ConnectedDappsList } from 'src/components/WalletConnect/ConnectedDapps/ ...@@ -10,8 +10,10 @@ import { ConnectedDappsList } from 'src/components/WalletConnect/ConnectedDapps/
import { import {
URIType, URIType,
UWULINK_PREFIX, UWULINK_PREFIX,
findAllowedTokenRecipient,
getSupportedURI, getSupportedURI,
isAllowedUwuLinkRequest, isAllowedUwuLinkRequest,
toTokenTransferRequest,
useUwuLinkContractAllowlist, useUwuLinkContractAllowlist,
} from 'src/components/WalletConnect/ScanSheet/util' } from 'src/components/WalletConnect/ScanSheet/util'
import { BackButtonView } from 'src/components/layout/BackButtonView' import { BackButtonView } from 'src/components/layout/BackButtonView'
...@@ -23,14 +25,15 @@ import { Flex, HapticFeedback, Text, TouchableArea, useIsDarkMode, useSporeColor ...@@ -23,14 +25,15 @@ import { Flex, HapticFeedback, Text, TouchableArea, useIsDarkMode, useSporeColor
import Scan from 'ui/src/assets/icons/receive.svg' import Scan from 'ui/src/assets/icons/receive.svg'
import ScanQRIcon from 'ui/src/assets/icons/scan.svg' import ScanQRIcon from 'ui/src/assets/icons/scan.svg'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { WalletQRCode } from 'wallet/src/components/QRCodeScanner/WalletQRCode' import { WalletQRCode } from 'wallet/src/components/QRCodeScanner/WalletQRCode'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { selectActiveAccountAddress } from 'wallet/src/features/wallet/selectors' import { useContractManager, useProviderManager } from 'wallet/src/features/wallet/context'
import { EthMethod, UwULinkRequest } from 'wallet/src/features/walletConnect/types' import { useActiveAccount } from 'wallet/src/features/wallet/hooks'
import { EthMethod, UwULinkMethod, UwULinkRequest } from 'wallet/src/features/walletConnect/types'
import { ElementName, ModalName } from 'wallet/src/telemetry/constants' import { ElementName, ModalName } from 'wallet/src/telemetry/constants'
type Props = { type Props = {
...@@ -45,8 +48,8 @@ export function WalletConnectModal({ ...@@ -45,8 +48,8 @@ export function WalletConnectModal({
const { t } = useTranslation() const { t } = useTranslation()
const colors = useSporeColors() const colors = useSporeColors()
const isDarkMode = useIsDarkMode() const isDarkMode = useIsDarkMode()
const activeAddress = useAppSelector(selectActiveAccountAddress) const activeAccount = useActiveAccount()
const { sessions, hasPendingSessionError } = useWalletConnect(activeAddress) const { sessions, hasPendingSessionError } = useWalletConnect(activeAccount?.address)
const [currentScreenState, setCurrentScreenState] = const [currentScreenState, setCurrentScreenState] =
useState<ScannerModalState>(initialScreenState) useState<ScannerModalState>(initialScreenState)
const [shouldFreezeCamera, setShouldFreezeCamera] = useState(false) const [shouldFreezeCamera, setShouldFreezeCamera] = useState(false)
...@@ -57,6 +60,9 @@ export function WalletConnectModal({ ...@@ -57,6 +60,9 @@ export function WalletConnectModal({
const uwuLinkContractAllowlist = useUwuLinkContractAllowlist() const uwuLinkContractAllowlist = useUwuLinkContractAllowlist()
const providerManager = useProviderManager()
const contractManager = useContractManager()
// Update QR scanner states when pending session error alert is shown from WCv2 saga event channel // Update QR scanner states when pending session error alert is shown from WCv2 saga event channel
useEffect(() => { useEffect(() => {
if (hasPendingSessionError) { if (hasPendingSessionError) {
...@@ -68,7 +74,7 @@ export function WalletConnectModal({ ...@@ -68,7 +74,7 @@ export function WalletConnectModal({
const onScanCode = useCallback( const onScanCode = useCallback(
async (uri: string) => { async (uri: string) => {
// don't scan any QR codes if there is an error popup open or camera is frozen // don't scan any QR codes if there is an error popup open or camera is frozen
if (!activeAddress || hasPendingSessionError || shouldFreezeCamera) { if (!activeAccount || hasPendingSessionError || shouldFreezeCamera) {
return return
} }
await HapticFeedback.selection() await HapticFeedback.selection()
...@@ -149,7 +155,6 @@ export function WalletConnectModal({ ...@@ -149,7 +155,6 @@ export function WalletConnectModal({
try { try {
const parsedUwulinkRequest: UwULinkRequest = JSON.parse(supportedURI.value) const parsedUwulinkRequest: UwULinkRequest = JSON.parse(supportedURI.value)
const isAllowed = isAllowedUwuLinkRequest(parsedUwulinkRequest, uwuLinkContractAllowlist) const isAllowed = isAllowedUwuLinkRequest(parsedUwulinkRequest, uwuLinkContractAllowlist)
if (!isAllowed) { if (!isAllowed) {
Alert.alert( Alert.alert(
t('walletConnect.error.uwu.title'), t('walletConnect.error.uwu.title'),
...@@ -166,25 +171,62 @@ export function WalletConnectModal({ ...@@ -166,25 +171,62 @@ export function WalletConnectModal({
return return
} }
dispatch( const newRequest = {
addRequest({ sessionId: UWULINK_PREFIX, // session/internalId is WalletConnect specific, but not needed here
account: activeAddress, internalId: UWULINK_PREFIX,
request: { account: activeAccount?.address,
type: EthMethod.EthSendTransaction, dapp: {
transaction: { from: activeAddress, ...parsedUwulinkRequest.value }, name: '',
sessionId: UWULINK_PREFIX, // session/internalId is WalletConnect specific, but not needed here url: '',
internalId: UWULINK_PREFIX, ...parsedUwulinkRequest.dapp,
account: activeAddress, source: UWULINK_PREFIX,
dapp: { chain_id: parsedUwulinkRequest.chainId,
...parsedUwulinkRequest.dapp, webhook: parsedUwulinkRequest.webhook,
source: UWULINK_PREFIX, },
chain_id: parsedUwulinkRequest.chainId, chainId: parsedUwulinkRequest.chainId,
webhook: parsedUwulinkRequest.webhook, }
if (parsedUwulinkRequest.method === UwULinkMethod.Erc20Send) {
const preparedTransaction = await toTokenTransferRequest(
parsedUwulinkRequest,
activeAccount,
providerManager,
contractManager
)
const tokenRecipient = findAllowedTokenRecipient(
parsedUwulinkRequest,
uwuLinkContractAllowlist
)
dispatch(
addRequest({
account: activeAccount.address,
request: {
...newRequest,
type: UwULinkMethod.Erc20Send,
recipient: {
address: parsedUwulinkRequest.recipient,
name: tokenRecipient?.name ?? '',
},
amount: parsedUwulinkRequest.amount,
tokenAddress: parsedUwulinkRequest.tokenAddress,
isStablecoin: parsedUwulinkRequest.isStablecoin,
transaction: { from: activeAccount.address, ...preparedTransaction },
}, },
chainId: parsedUwulinkRequest.chainId, })
}, )
}) } else {
) dispatch(
addRequest({
account: activeAccount.address,
request: {
...newRequest,
type: EthMethod.EthSendTransaction,
transaction: { from: activeAccount.address, ...parsedUwulinkRequest.value },
},
})
)
}
onClose() onClose()
} catch (_) { } catch (_) {
setShouldFreezeCamera(false) setShouldFreezeCamera(false)
...@@ -206,7 +248,7 @@ export function WalletConnectModal({ ...@@ -206,7 +248,7 @@ export function WalletConnectModal({
} }
}, },
[ [
activeAddress, activeAccount,
hasPendingSessionError, hasPendingSessionError,
shouldFreezeCamera, shouldFreezeCamera,
isUwULinkEnabled, isUwULinkEnabled,
...@@ -217,6 +259,8 @@ export function WalletConnectModal({ ...@@ -217,6 +259,8 @@ export function WalletConnectModal({
onClose, onClose,
dispatch, dispatch,
uwuLinkContractAllowlist, uwuLinkContractAllowlist,
providerManager,
contractManager,
] ]
) )
...@@ -236,7 +280,7 @@ export function WalletConnectModal({ ...@@ -236,7 +280,7 @@ export function WalletConnectModal({
setCurrentScreenState(ScannerModalState.ScanQr) setCurrentScreenState(ScannerModalState.ScanQr)
} }
if (!activeAddress) { if (!activeAccount) {
return null return null
} }
...@@ -269,7 +313,7 @@ export function WalletConnectModal({ ...@@ -269,7 +313,7 @@ export function WalletConnectModal({
)} )}
{currentScreenState === ScannerModalState.WalletQr && ( {currentScreenState === ScannerModalState.WalletQr && (
<Trace logImpression element={ElementName.WalletQRCode}> <Trace logImpression element={ElementName.WalletQRCode}>
<WalletQRCode address={activeAddress} /> <WalletQRCode address={activeAccount.address} />
</Trace> </Trace>
)} )}
<Flex centered mb="$spacing12" mt="$spacing16" mx="$spacing16"> <Flex centered mb="$spacing12" mt="$spacing16" mx="$spacing16">
......
...@@ -6,12 +6,24 @@ import { ...@@ -6,12 +6,24 @@ import {
UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM, UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM,
UNISWAP_WALLETCONNECT_URL, UNISWAP_WALLETCONNECT_URL,
} from 'src/features/deepLinking/constants' } from 'src/features/deepLinking/constants'
import { DynamicConfigs } from 'uniswap/src/features/experiments/configs' import { DynamicConfigs } from 'uniswap/src/features/statsig/configs'
import { useDynamicConfig } from 'uniswap/src/features/experiments/hooks' import { useDynamicConfig } from 'uniswap/src/features/statsig/hooks'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { RPCType } from 'wallet/src/constants/chains'
import { AssetType } from 'wallet/src/entities/assets'
import { ContractManager } from 'wallet/src/features/contracts/ContractManager'
import { ProviderManager } from 'wallet/src/features/providers'
import { ScantasticParams, ScantasticParamsSchema } from 'wallet/src/features/scantastic/types' import { ScantasticParams, ScantasticParamsSchema } from 'wallet/src/features/scantastic/types'
import { UwULinkRequest } from 'wallet/src/features/walletConnect/types' import { getTokenTransferRequest } from 'wallet/src/features/transactions/transfer/hooks/useTransferTransactionRequest'
import { getValidAddress } from 'wallet/src/utils/addresses' import { TransferCurrencyParams } from 'wallet/src/features/transactions/transfer/types'
import { Account } from 'wallet/src/features/wallet/accounts/types'
import {
EthTransaction,
UwULinkErc20SendRequest,
UwULinkMethod,
UwULinkRequest,
} from 'wallet/src/features/walletConnect/types'
import { areAddressesEqual, getValidAddress } from 'wallet/src/utils/addresses'
export enum URIType { export enum URIType {
WalletConnectURL = 'walletconnect', WalletConnectURL = 'walletconnect',
...@@ -34,19 +46,23 @@ interface EnabledFeatureFlags { ...@@ -34,19 +46,23 @@ interface EnabledFeatureFlags {
// This type must match the format in statsig dynamic config for uwulink // This type must match the format in statsig dynamic config for uwulink
// https://console.statsig.com/5HjUux4OvSGzgqWIfKFt8i/dynamic_configs/uwulink_config // https://console.statsig.com/5HjUux4OvSGzgqWIfKFt8i/dynamic_configs/uwulink_config
type UwuLinkAllowlistItem = { type UwULinkAllowlistItem = {
chainId: number chainId: number
contractAddress: string address: string
name: string name: string
icon?: string icon?: string
} }
type UwuLinkAllowlist = UwuLinkAllowlistItem[]
type UwULinkAllowlist = {
contracts: UwULinkAllowlistItem[]
tokenRecipients: UwULinkAllowlistItem[]
}
const UWULINK_MAX_TXN_VALUE = '0.001' const UWULINK_MAX_TXN_VALUE = '0.001'
const EASTER_EGG_QR_CODE = 'DO_NOT_SCAN_OR_ELSE_YOU_WILL_GO_TO_MOBILE_TEAM_JAIL' const EASTER_EGG_QR_CODE = 'DO_NOT_SCAN_OR_ELSE_YOU_WILL_GO_TO_MOBILE_TEAM_JAIL'
export const CUSTOM_UNI_QR_CODE_PREFIX = 'hello_uniwallet:' export const CUSTOM_UNI_QR_CODE_PREFIX = 'hello_uniwallet:'
export const UWULINK_PREFIX = 'uwulink' export const UWULINK_PREFIX = 'uwulink' as const
export const truncateQueryParams = (url: string): string => { export const truncateQueryParams = (url: string): string => {
// In fact, the first element will be always returned below. url is // In fact, the first element will be always returned below. url is
...@@ -102,8 +118,9 @@ export async function getSupportedURI( ...@@ -102,8 +118,9 @@ export async function getSupportedURI(
return { type: URIType.EasterEgg, value: uri } return { type: URIType.EasterEgg, value: uri }
} }
if (enabledFeatureFlags?.isUwULinkEnabled && isUwULink(uri)) { if (isUwULink(uri)) {
return { type: URIType.UwULink, value: uri.slice(UWULINK_PREFIX.length) } // remove escape strings from the stringified JSON before parsing it
return { type: URIType.UwULink, value: uri.slice(UWULINK_PREFIX.length).replaceAll('\\', '') }
} }
} }
...@@ -132,11 +149,24 @@ function isUwULink(uri: string): boolean { ...@@ -132,11 +149,24 @@ function isUwULink(uri: string): boolean {
// Gets the UWULink contract allow list from statsig dynamic config. // Gets the UWULink contract allow list from statsig dynamic config.
// We can safely cast as long as the statsig config format matches our `UwuLinkAllowlist` type. // We can safely cast as long as the statsig config format matches our `UwuLinkAllowlist` type.
export function useUwuLinkContractAllowlist(): UwuLinkAllowlist { export function useUwuLinkContractAllowlist(): UwULinkAllowlist {
const uwuLinkConfig = useDynamicConfig(DynamicConfigs.UwuLink) const uwuLinkConfig = useDynamicConfig(DynamicConfigs.UwuLink)
return uwuLinkConfig.getValue('allowlist') as UwuLinkAllowlist return uwuLinkConfig.getValue('allowlist') as UwULinkAllowlist
} }
export function findAllowedTokenRecipient(
request: UwULinkRequest,
allowlist: UwULinkAllowlist
): UwULinkAllowlistItem | undefined {
if (request.method !== UwULinkMethod.Erc20Send) {
return
}
const { chainId, recipient } = request
return allowlist.tokenRecipients.find(
(item) => item.chainId === chainId && areAddressesEqual(item.address, recipient)
)
}
/** /**
* Util function to check if a UwULinkRequest is valid. * Util function to check if a UwULinkRequest is valid.
* *
...@@ -144,17 +174,26 @@ export function useUwuLinkContractAllowlist(): UwuLinkAllowlist { ...@@ -144,17 +174,26 @@ export function useUwuLinkContractAllowlist(): UwuLinkAllowlist {
* 1. The to address is in the UWULINK_CONTRACT_ALLOWLIST * 1. The to address is in the UWULINK_CONTRACT_ALLOWLIST
* 2. The value is less than or equal to UWULINK_MAX_TXN_VALUE * 2. The value is less than or equal to UWULINK_MAX_TXN_VALUE
* *
* TODO: also check for validity of the entire request object (e.g. all the required fields exist)
*
* @param request parsed UwULinkRequest * @param request parsed UwULinkRequest
* @returns boolean for whether the UwULinkRequest is allowed * @returns boolean for whether the UwULinkRequest is allowed
*/ */
export function isAllowedUwuLinkRequest( export function isAllowedUwuLinkRequest(
request: UwULinkRequest, request: UwULinkRequest,
allowList: UwuLinkAllowlist allowlist: UwULinkAllowlist
): boolean { ): boolean {
// token sends
if (request.method === UwULinkMethod.Erc20Send) {
return Boolean(findAllowedTokenRecipient(request, allowlist))
}
// generic transactions
const { to, value } = request.value const { to, value } = request.value
const belowMaximumValue = const belowMaximumValue =
value && parseFloat(value) <= parseEther(UWULINK_MAX_TXN_VALUE).toNumber() value && parseFloat(value) <= parseEther(UWULINK_MAX_TXN_VALUE).toNumber()
const isAllowedContractAddress = to && allowList.some((item) => item.contractAddress === to) const isAllowedContractAddress =
to && allowlist.contracts.some((item) => areAddressesEqual(item.address, to))
if (!belowMaximumValue || !isAllowedContractAddress) { if (!belowMaximumValue || !isAllowedContractAddress) {
return false return false
...@@ -238,3 +277,22 @@ export function parseScantasticParams(uri: string): ScantasticParams | undefined ...@@ -238,3 +277,22 @@ export function parseScantasticParams(uri: string): ScantasticParams | undefined
}) })
} }
} }
export async function toTokenTransferRequest(
request: UwULinkErc20SendRequest,
account: Account,
providerManager: ProviderManager,
contractManager: ContractManager
): Promise<EthTransaction> {
const provider = providerManager.getProvider(request.chainId, RPCType.Public)
const params: TransferCurrencyParams = {
type: AssetType.Currency,
account,
chainId: request.chainId,
toAddress: request.recipient,
tokenAddress: request.tokenAddress,
amountInWei: request.amount.toString(),
}
const transaction = await getTokenTransferRequest(params, provider, contractManager)
return transaction as EthTransaction
}
...@@ -10,8 +10,8 @@ import { SectionHeaderText } from 'src/components/explore/search/SearchSectionHe ...@@ -10,8 +10,8 @@ import { SectionHeaderText } from 'src/components/explore/search/SearchSectionHe
import { AnimatedFlex, Flex, Icons, Text, TouchableArea, useSporeColors } from 'ui/src' import { AnimatedFlex, Flex, Icons, Text, TouchableArea, useSporeColors } from 'ui/src'
import ClockIcon from 'ui/src/assets/icons/clock.svg' import ClockIcon from 'ui/src/assets/icons/clock.svg'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { clearSearchHistory } from 'wallet/src/features/search/searchHistorySlice' import { clearSearchHistory } from 'wallet/src/features/search/searchHistorySlice'
import { import {
SearchResult, SearchResult,
......
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Trace from 'src/components/Trace/Trace'
import { MobileEventName } from 'src/features/telemetry/constants'
import { Button, Icons } from 'ui/src' import { Button, Icons } from 'ui/src'
import { SpinningLoader } from 'wallet/src/components/loading/SpinningLoader' import { SpinningLoader } from 'wallet/src/components/loading/SpinningLoader'
import { ElementName } from 'wallet/src/telemetry/constants'
interface FiatOnRampCtaButtonProps { interface FiatOnRampCtaButtonProps {
onPress: () => void onPress: () => void
isLoading?: boolean isLoading?: boolean
eligible: boolean eligible: boolean
disabled: boolean disabled: boolean
analyticsProperties?: Record<string, unknown>
continueButtonText: string continueButtonText: string
} }
...@@ -20,35 +16,28 @@ export function FiatOnRampCtaButton({ ...@@ -20,35 +16,28 @@ export function FiatOnRampCtaButton({
isLoading, isLoading,
eligible, eligible,
disabled, disabled,
analyticsProperties,
onPress, onPress,
}: FiatOnRampCtaButtonProps): JSX.Element { }: FiatOnRampCtaButtonProps): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const buttonAvailable = eligible || isLoading const buttonAvailable = eligible || isLoading
const continueText = eligible ? continueButtonText : t('fiatOnRamp.error.unsupported') const continueText = eligible ? continueButtonText : t('fiatOnRamp.error.unsupported')
return ( return (
<Trace <Button
logPress // TODO: remove when https://linear.app/uniswap/issue/MOB-3182/disabled-ui-on-enabled-button is fixed
element={ElementName.FiatOnRampWidgetButton} key={Math.random()}
pressEvent={MobileEventName.FiatOnRampWidgetOpened} color={buttonAvailable ? '$white' : '$neutral2'}
properties={analyticsProperties}> disabled={disabled}
<Button icon={
// TODO: remove when https://linear.app/uniswap/issue/MOB-3182/disabled-ui-on-enabled-button is fixed isLoading ? (
key={Math.random()} <SpinningLoader color="$sporeWhite" />
color={buttonAvailable ? '$white' : '$neutral2'} ) : !eligible ? (
disabled={disabled} <Icons.InfoCircleFilled color="$neutral3" />
icon={ ) : undefined
isLoading ? ( }
<SpinningLoader color="$sporeWhite" /> size="large"
) : !eligible ? ( theme={buttonAvailable ? 'primary' : 'tertiary'}
<Icons.InfoCircleFilled color="$neutral3" /> onPress={onPress}>
) : undefined {!isLoading && continueText}
} </Button>
size="large"
theme={buttonAvailable ? 'primary' : 'tertiary'}
onPress={onPress}>
{!isLoading && continueText}
</Button>
</Trace>
) )
} }
...@@ -5,8 +5,8 @@ import { SeedPhraseDisplay } from 'src/components/mnemonic/SeedPhraseDisplay' ...@@ -5,8 +5,8 @@ import { SeedPhraseDisplay } from 'src/components/mnemonic/SeedPhraseDisplay'
import { APP_STORE_LINK } from 'src/constants/urls' import { APP_STORE_LINK } from 'src/constants/urls'
import { UpgradeStatus } from 'src/features/forceUpgrade/types' import { UpgradeStatus } from 'src/features/forceUpgrade/types'
import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import { DynamicConfigs } from 'uniswap/src/features/experiments/configs' import { DynamicConfigs } from 'uniswap/src/features/statsig/configs'
import { useDynamicConfig } from 'uniswap/src/features/experiments/hooks' import { useDynamicConfig } from 'uniswap/src/features/statsig/hooks'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { WarningModal } from 'wallet/src/components/modals/WarningModal/WarningModal' import { WarningModal } from 'wallet/src/components/modals/WarningModal/WarningModal'
import { WarningSeverity } from 'wallet/src/features/transactions/WarningModal/types' import { WarningSeverity } from 'wallet/src/features/transactions/WarningModal/types'
......
...@@ -2,13 +2,13 @@ import { FlashList } from '@shopify/flash-list' ...@@ -2,13 +2,13 @@ import { FlashList } from '@shopify/flash-list'
import React, { forwardRef, memo, useCallback, useMemo } from 'react' import React, { forwardRef, memo, useCallback, useMemo } from 'react'
import { RefreshControl } from 'react-native' import { RefreshControl } from 'react-native'
import { useAppStackNavigation } from 'src/app/navigation/types' import { useAppStackNavigation } from 'src/app/navigation/types'
import { NftView } from 'src/components/NFT/NftView'
import { useAdaptiveFooter } from 'src/components/home/hooks' import { useAdaptiveFooter } from 'src/components/home/hooks'
import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers' import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers'
import { Screens } from 'src/screens/Screens' import { Screens } from 'src/screens/Screens'
import { Flex, useDeviceInsets, useSporeColors } from 'ui/src' import { Flex, useDeviceInsets, useSporeColors } from 'ui/src'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries' import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'uniswap/src/utils/platform'
import { NftView } from 'wallet/src/components/nfts/NftView'
import { NftsList } from 'wallet/src/components/nfts/NftsList' import { NftsList } from 'wallet/src/components/nfts/NftsList'
import { NFTItem } from 'wallet/src/features/nfts/types' import { NFTItem } from 'wallet/src/features/nfts/types'
......
...@@ -6,8 +6,8 @@ import { openModal } from 'src/features/modals/modalSlice' ...@@ -6,8 +6,8 @@ import { openModal } from 'src/features/modals/modalSlice'
import { Flex, Icons, Text, TouchableArea } from 'ui/src' import { Flex, Icons, Text, TouchableArea } from 'ui/src'
import PaperStackIcon from 'ui/src/assets/icons/paper-stack.svg' import PaperStackIcon from 'ui/src/assets/icons/paper-stack.svg'
import { iconSizes, colors as rawColors } from 'ui/src/theme' import { iconSizes, colors as rawColors } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { AccountType } from 'wallet/src/features/wallet/accounts/types' import { AccountType } from 'wallet/src/features/wallet/accounts/types'
import { useActiveAccount } from 'wallet/src/features/wallet/hooks' import { useActiveAccount } from 'wallet/src/features/wallet/hooks'
......
import { NftView } from 'src/components/NFT/NftView'
import { useDeviceInsets, useSporeColors } from 'ui/src' import { useDeviceInsets, useSporeColors } from 'ui/src'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { NftView } from 'wallet/src/components/nfts/NftView'
import { NftsList } from 'wallet/src/components/nfts/NftsList' import { NftsList } from 'wallet/src/components/nfts/NftsList'
import { NFTItem } from 'wallet/src/features/nfts/types' import { NFTItem } from 'wallet/src/features/nfts/types'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
......
...@@ -8,8 +8,8 @@ import { sendMobileAnalyticsEvent } from 'src/features/telemetry' ...@@ -8,8 +8,8 @@ import { sendMobileAnalyticsEvent } from 'src/features/telemetry'
import { MobileEventName } from 'src/features/telemetry/constants' import { MobileEventName } from 'src/features/telemetry/constants'
import { selectCustomEndpoint } from 'src/features/tweaks/selectors' import { selectCustomEndpoint } from 'src/features/tweaks/selectors'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { isNonJestDev } from 'utilities/src/environment' import { isNonJestDev } from 'utilities/src/environment'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks' import { useAsyncData } from 'utilities/src/react/hooks'
......
# Universal Links # Universal Links
Universal links allow 3rd parties to prompt the app to open to specific screens when it is installed on their device. If the app isn't installed it will open that page in Safari (a 404 on uniswap.org in this case). All universal links must use the the prefix `https://uniswap.org/app`. Universal links allow 3rd parties to prompt the app to open to specific screens when it is installed on their device. If the app isn't installed it will open that page in Safari (a 404 on uniswap.org in this case). All universal links must use the prefix `https://uniswap.org/app`.
## Supported Screens ## Supported Screens
......
...@@ -27,7 +27,7 @@ import { Screens } from 'src/screens/Screens' ...@@ -27,7 +27,7 @@ import { Screens } from 'src/screens/Screens'
import { Statsig } from 'statsig-react-native' import { Statsig } from 'statsig-react-native'
import { call, put, takeLatest } from 'typed-redux-saga' import { call, put, takeLatest } from 'typed-redux-saga'
import { UNISWAP_APP_HOSTNAME } from 'uniswap/src/constants/urls' import { UNISWAP_APP_HOSTNAME } from 'uniswap/src/constants/urls'
import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/experiments/flags' import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/statsig/flags'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n/i18n'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { selectExtensionOnboardingState } from 'wallet/src/features/behaviorHistory/selectors' import { selectExtensionOnboardingState } from 'wallet/src/features/behaviorHistory/selectors'
......
...@@ -27,8 +27,8 @@ import { ...@@ -27,8 +27,8 @@ import {
} from 'ui/src' } from 'ui/src'
import { ENS_LOGO } from 'ui/src/assets' import { ENS_LOGO } from 'ui/src/assets'
import { iconSizes, imageSizes } from 'ui/src/theme' import { iconSizes, imageSizes } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { useENSDescription, useENSName, useENSTwitterUsername } from 'wallet/src/features/ens/api' import { useENSDescription, useENSName, useENSTwitterUsername } from 'wallet/src/features/ens/api'
import { selectWatchedAddressSet } from 'wallet/src/features/favorites/selectors' import { selectWatchedAddressSet } from 'wallet/src/features/favorites/selectors'
......
...@@ -131,6 +131,14 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element { ...@@ -131,6 +131,14 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element {
useTimeout( useTimeout(
async () => { async () => {
if (fiatOnRampHostUrl) { if (fiatOnRampHostUrl) {
if (currency?.moonpayCurrencyCode) {
sendWalletAnalyticsEvent(FiatOnRampEventName.FiatOnRampWidgetOpened, {
externalTransactionId,
serviceProvider: 'MOONPAY',
fiatCurrency: moonpaySupportedFiatCurrency.code.toLowerCase(),
cryptoCurrency: currency.moonpayCurrencyCode.toLowerCase(),
})
}
await openUri(fiatOnRampHostUrl) await openUri(fiatOnRampHostUrl)
dispatchAddTransaction() dispatchAddTransaction()
onClose() onClose()
...@@ -241,7 +249,6 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element { ...@@ -241,7 +249,6 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element {
/> />
)} )}
<FiatOnRampCtaButton <FiatOnRampCtaButton
analyticsProperties={{ externalTransactionId }}
continueButtonText={ continueButtonText={
appFiatCurrencySupportedInMoonpay appFiatCurrencySupportedInMoonpay
? t('fiatOnRamp.button.continueCheckout') ? t('fiatOnRamp.button.continueCheckout')
......
...@@ -4,8 +4,8 @@ import { OnboardingStackBaseParams, useOnboardingStackNavigation } from 'src/app ...@@ -4,8 +4,8 @@ import { OnboardingStackBaseParams, useOnboardingStackNavigation } from 'src/app
import { sendMobileAnalyticsEvent } from 'src/features/telemetry' import { sendMobileAnalyticsEvent } from 'src/features/telemetry'
import { MobileEventName } from 'src/features/telemetry/constants' import { MobileEventName } from 'src/features/telemetry/constants'
import { Screens } from 'src/screens/Screens' import { Screens } from 'src/screens/Screens'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext'
import { import {
setHasSkippedUnitagPrompt, setHasSkippedUnitagPrompt,
......
...@@ -4,7 +4,12 @@ import { MobileEventName } from 'src/features/telemetry/constants' ...@@ -4,7 +4,12 @@ import { MobileEventName } from 'src/features/telemetry/constants'
import { WidgetEvent, WidgetType } from 'src/features/widgets/widgets' import { WidgetEvent, WidgetType } from 'src/features/widgets/widgets'
import { TraceProps } from 'utilities/src/telemetry/trace/Trace' import { TraceProps } from 'utilities/src/telemetry/trace/Trace'
import { ImportType } from 'wallet/src/features/onboarding/types' import { ImportType } from 'wallet/src/features/onboarding/types'
import { EthMethod, WCEventType, WCRequestOutcome } from 'wallet/src/features/walletConnect/types' import {
EthMethod,
UwULinkMethod,
WCEventType,
WCRequestOutcome,
} from 'wallet/src/features/walletConnect/types'
import { ShareableEntity } from 'wallet/src/telemetry/constants' import { ShareableEntity } from 'wallet/src/telemetry/constants'
// Events related to Moonpay internal transactions // Events related to Moonpay internal transactions
...@@ -86,7 +91,7 @@ export type MobileEventProperties = { ...@@ -86,7 +91,7 @@ export type MobileEventProperties = {
[MobileEventName.WalletAdded]: OnboardingCompletedProps & TraceProps [MobileEventName.WalletAdded]: OnboardingCompletedProps & TraceProps
[MobileEventName.WalletConnectSheetCompleted]: { [MobileEventName.WalletConnectSheetCompleted]: {
request_type: WCEventType request_type: WCEventType
eth_method?: EthMethod eth_method?: EthMethod | UwULinkMethod
dapp_url: string dapp_url: string
dapp_name: string dapp_name: string
wc_version: string wc_version: string
......
...@@ -28,7 +28,7 @@ import { useParsedSendWarnings } from 'wallet/src/features/transactions/hooks/us ...@@ -28,7 +28,7 @@ import { useParsedSendWarnings } from 'wallet/src/features/transactions/hooks/us
import { useTokenSelectorActionHandlers } from 'wallet/src/features/transactions/hooks/useTokenSelectorActionHandlers' import { useTokenSelectorActionHandlers } from 'wallet/src/features/transactions/hooks/useTokenSelectorActionHandlers'
import { useTransactionGasWarning } from 'wallet/src/features/transactions/hooks/useTransactionGasWarning' import { useTransactionGasWarning } from 'wallet/src/features/transactions/hooks/useTransactionGasWarning'
import { import {
initialState as emptyState, INITIAL_TRANSACTION_STATE,
transactionStateReducer, transactionStateReducer,
} from 'wallet/src/features/transactions/transactionState/transactionState' } from 'wallet/src/features/transactions/transactionState/transactionState'
import { import {
...@@ -67,7 +67,10 @@ export function TransferFlow({ prefilledState, onClose }: TransferFormProps): JS ...@@ -67,7 +67,10 @@ export function TransferFlow({ prefilledState, onClose }: TransferFormProps): JS
const { fullWidth } = useDeviceDimensions() const { fullWidth } = useDeviceDimensions()
const { isSheetReady } = useBottomSheetContext() const { isSheetReady } = useBottomSheetContext()
const [state, dispatch] = useReducer(transactionStateReducer, prefilledState || emptyState) const [state, dispatch] = useReducer(
transactionStateReducer,
prefilledState || INITIAL_TRANSACTION_STATE
)
const derivedTransferInfo = useDerivedTransferInfo(state) const derivedTransferInfo = useDerivedTransferInfo(state)
const [showViewOnlyModal, setShowViewOnlyModal] = useState(false) const [showViewOnlyModal, setShowViewOnlyModal] = useState(false)
const [step, setStep] = useState<TransactionStep>(TransactionStep.FORM) const [step, setStep] = useState<TransactionStep>(TransactionStep.FORM)
...@@ -89,9 +92,8 @@ export function TransferFlow({ prefilledState, onClose }: TransferFormProps): JS ...@@ -89,9 +92,8 @@ export function TransferFlow({ prefilledState, onClose }: TransferFormProps): JS
) )
const transferTxWithGasSettings = useMemo( const transferTxWithGasSettings = useMemo(
(): providers.TransactionRequest | undefined => (): providers.TransactionRequest => ({ ...txRequest, ...gasFee.params }),
gasFee?.params ? { ...txRequest, ...gasFee.params } : txRequest, [gasFee.params, txRequest]
[gasFee?.params, txRequest]
) )
const gasWarning = useTransactionGasWarning({ const gasWarning = useTransactionGasWarning({
......
...@@ -297,7 +297,7 @@ export function ClaimUnitagScreen({ navigation, route }: Props): JSX.Element { ...@@ -297,7 +297,7 @@ export function ClaimUnitagScreen({ navigation, route }: Props): JSX.Element {
borderWidth={0} borderWidth={0}
fontFamily="$heading" fontFamily="$heading"
fontSize={fontSize} fontSize={fontSize}
fontWeight="$large" fontWeight="$medium"
numberOfLines={1} numberOfLines={1}
p="$none" p="$none"
placeholder={inputPlaceholder} placeholder={inputPlaceholder}
......
...@@ -28,8 +28,8 @@ import { ...@@ -28,8 +28,8 @@ import {
} from 'ui/src' } from 'ui/src'
import { borderRadii, fonts, iconSizes, imageSizes, spacing } from 'ui/src/theme' import { borderRadii, fonts, iconSizes, imageSizes, spacing } from 'ui/src/theme'
import { useExtractedColors } from 'ui/src/utils/colors' import { useExtractedColors } from 'ui/src/utils/colors'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { useUnitagUpdater } from 'uniswap/src/features/unitags/context' import { useUnitagUpdater } from 'uniswap/src/features/unitags/context'
import { ProfileMetadata } from 'uniswap/src/features/unitags/types' import { ProfileMetadata } from 'uniswap/src/features/unitags/types'
import { isIOS } from 'uniswap/src/utils/platform' import { isIOS } from 'uniswap/src/utils/platform'
......
...@@ -2,8 +2,8 @@ import { useCallback, useEffect, useState } from 'react' ...@@ -2,8 +2,8 @@ import { useCallback, useEffect, useState } from 'react'
import { useAppSelector } from 'src/app/hooks' import { useAppSelector } from 'src/app/hooks'
import { openModal } from 'src/features/modals/modalSlice' import { openModal } from 'src/features/modals/modalSlice'
import { selectModalState } from 'src/features/modals/selectModalState' import { selectModalState } from 'src/features/modals/selectModalState'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring' import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
import { useNativeAccountExists } from 'wallet/src/features/wallet/hooks' import { useNativeAccountExists } from 'wallet/src/features/wallet/hooks'
......
...@@ -82,7 +82,7 @@ export function* signWcRequest(params: SignMessageParams | SignTransactionParams ...@@ -82,7 +82,7 @@ export function* signWcRequest(params: SignMessageParams | SignTransactionParams
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ method: 'eth_sendTransaction', response: signature }), body: JSON.stringify({ method: 'eth_sendTransaction', response: signature, chainId }),
// TODO: consider adding analytics to track UwuLink usage // TODO: consider adding analytics to track UwuLink usage
}).catch((error) => }).catch((error) =>
logger.error(error, { logger.error(error, {
...@@ -107,7 +107,7 @@ export function* signWcRequest(params: SignMessageParams | SignTransactionParams ...@@ -107,7 +107,7 @@ export function* signWcRequest(params: SignMessageParams | SignTransactionParams
type: AppNotificationType.WalletConnect, type: AppNotificationType.WalletConnect,
event: WalletConnectEvent.TransactionFailed, event: WalletConnectEvent.TransactionFailed,
dappName: params.dapp.name, dappName: params.dapp.name,
imageUrl: params.dapp.icon, imageUrl: params.dapp.icon ?? null,
chainId, chainId,
address: account.address, address: account.address,
}) })
......
...@@ -6,6 +6,7 @@ import { ...@@ -6,6 +6,7 @@ import {
EthMethod, EthMethod,
EthSignMethod, EthSignMethod,
EthTransaction, EthTransaction,
UwULinkMethod,
} from 'wallet/src/features/walletConnect/types' } from 'wallet/src/features/walletConnect/types'
export type WalletConnectPendingSession = { export type WalletConnectPendingSession = {
...@@ -45,11 +46,24 @@ export interface TransactionRequest extends BaseRequest { ...@@ -45,11 +46,24 @@ export interface TransactionRequest extends BaseRequest {
transaction: EthTransaction transaction: EthTransaction
} }
export type WalletConnectRequest = SignRequest | TransactionRequest export interface UwuLinkErc20Request extends BaseRequest {
type: UwULinkMethod.Erc20Send
recipient: {
address: string
name: string
}
tokenAddress: string
amount: string
isStablecoin: boolean
transaction: EthTransaction // the formatted transaction, prepared by the wallet
}
export type WalletConnectRequest = SignRequest | TransactionRequest | UwuLinkErc20Request
export const isTransactionRequest = ( export const isTransactionRequest = (
request: WalletConnectRequest request: WalletConnectRequest
): request is TransactionRequest => request.type === EthMethod.EthSendTransaction ): request is TransactionRequest =>
request.type === EthMethod.EthSendTransaction || request.type === UwULinkMethod.Erc20Send
export interface WalletConnectState { export interface WalletConnectState {
byAccount: { byAccount: {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
// Import the crypto getRandomValues shim BEFORE ethers shims // Import the crypto getRandomValues shim BEFORE ethers shims
import 'react-native-get-random-values' import 'react-native-get-random-values'
// Import the the ethers shims BEFORE ethers // Import the ethers shims BEFORE ethers
import '@ethersproject/shims' import '@ethersproject/shims'
// Add .at() method to Array if necessary (missing before iOS 15) // Add .at() method to Array if necessary (missing before iOS 15)
import 'src/polyfills/arrayAt' import 'src/polyfills/arrayAt'
......
...@@ -120,7 +120,7 @@ export function FiatOnRampConnectingScreen({ navigation }: Props): JSX.Element | ...@@ -120,7 +120,7 @@ export function FiatOnRampConnectingScreen({ navigation }: Props): JSX.Element |
sendWalletAnalyticsEvent(FiatOnRampEventName.FiatOnRampWidgetOpened, { sendWalletAnalyticsEvent(FiatOnRampEventName.FiatOnRampWidgetOpened, {
externalTransactionId, externalTransactionId,
serviceProvider: serviceProvider.serviceProvider, serviceProvider: serviceProvider.serviceProvider,
preselectedServiceProvider: serviceProvider.serviceProvider, preselectedServiceProvider: quotesSections?.[0]?.data?.[0]?.serviceProvider,
countryCode, countryCode,
countryState, countryState,
fiatCurrency: baseCurrencyInfo?.code.toLowerCase(), fiatCurrency: baseCurrencyInfo?.code.toLowerCase(),
......
...@@ -67,8 +67,8 @@ import BuyIcon from 'ui/src/assets/icons/buy.svg' ...@@ -67,8 +67,8 @@ import BuyIcon from 'ui/src/assets/icons/buy.svg'
import ScanIcon from 'ui/src/assets/icons/scan-home.svg' import ScanIcon from 'ui/src/assets/icons/scan-home.svg'
import SendIcon from 'ui/src/assets/icons/send-action.svg' import SendIcon from 'ui/src/assets/icons/send-action.svg'
import { iconSizes, spacing } from 'ui/src/theme' import { iconSizes, spacing } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { useInterval, useTimeout } from 'utilities/src/time/timing' import { useInterval, useTimeout } from 'utilities/src/time/timing'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
......
...@@ -11,8 +11,8 @@ import { OnboardingScreens } from 'src/screens/Screens' ...@@ -11,8 +11,8 @@ import { OnboardingScreens } from 'src/screens/Screens'
import { useAddBackButton } from 'src/utils/useAddBackButton' import { useAddBackButton } from 'src/utils/useAddBackButton'
import { Flex, Icons, Text, TouchableArea, Unicon, UniconV2, useIsDarkMode } from 'ui/src' import { Flex, Icons, Text, TouchableArea, Unicon, UniconV2, useIsDarkMode } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName' import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName'
import { import {
FORMAT_DATE_TIME_SHORT, FORMAT_DATE_TIME_SHORT,
......
...@@ -13,7 +13,6 @@ import { Loader } from 'src/components/loading' ...@@ -13,7 +13,6 @@ import { Loader } from 'src/components/loading'
import { LongMarkdownText } from 'src/components/text/LongMarkdownText' import { LongMarkdownText } from 'src/components/text/LongMarkdownText'
import { selectModalState } from 'src/features/modals/selectModalState' import { selectModalState } from 'src/features/modals/selectModalState'
import { PriceAmount } from 'src/features/nfts/collection/ListPriceCard' import { PriceAmount } from 'src/features/nfts/collection/ListPriceCard'
import { useNFTMenu } from 'src/features/nfts/hooks'
import { BlurredImageBackground } from 'src/features/nfts/item/BlurredImageBackground' import { BlurredImageBackground } from 'src/features/nfts/item/BlurredImageBackground'
import { CollectionPreviewCard } from 'src/features/nfts/item/CollectionPreviewCard' import { CollectionPreviewCard } from 'src/features/nfts/item/CollectionPreviewCard'
import { NFTTraitList } from 'src/features/nfts/item/traits' import { NFTTraitList } from 'src/features/nfts/item/traits'
...@@ -43,6 +42,7 @@ import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' ...@@ -43,6 +42,7 @@ import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { PollingInterval } from 'wallet/src/constants/misc' import { PollingInterval } from 'wallet/src/constants/misc'
import { NFTViewer } from 'wallet/src/features/images/NFTViewer' import { NFTViewer } from 'wallet/src/features/images/NFTViewer'
import { GQLNftAsset } from 'wallet/src/features/nfts/hooks' import { GQLNftAsset } from 'wallet/src/features/nfts/hooks'
import { useNFTContextMenu } from 'wallet/src/features/nfts/useNftContextMenu'
import { pushNotification } from 'wallet/src/features/notifications/slice' import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType, CopyNotificationType } from 'wallet/src/features/notifications/types' import { AppNotificationType, CopyNotificationType } from 'wallet/src/features/notifications/types'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
...@@ -422,7 +422,7 @@ function RightElement({ ...@@ -422,7 +422,7 @@ function RightElement({
}): JSX.Element { }): JSX.Element {
const colors = useSporeColors() const colors = useSporeColors()
const { menuActions, onContextMenuPress, onlyShare } = useNFTMenu({ const { menuActions, onContextMenuPress, onlyShare } = useNFTContextMenu({
contractAddress: asset?.nftContract?.address, contractAddress: asset?.nftContract?.address,
tokenId: asset?.tokenId, tokenId: asset?.tokenId,
owner, owner,
......
...@@ -12,8 +12,8 @@ import { OnboardingScreens, UnitagScreens } from 'src/screens/Screens' ...@@ -12,8 +12,8 @@ import { OnboardingScreens, UnitagScreens } from 'src/screens/Screens'
import { hideSplashScreen } from 'src/utils/splashScreen' import { hideSplashScreen } from 'src/utils/splashScreen'
import { isDevBuild } from 'src/utils/version' import { isDevBuild } from 'src/utils/version'
import { Button, Flex, HapticFeedback, Text, TouchableArea, useIsDarkMode } from 'ui/src' import { Button, Flex, HapticFeedback, Text, TouchableArea, useIsDarkMode } from 'ui/src'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { useTimeout } from 'utilities/src/time/timing' import { useTimeout } from 'utilities/src/time/timing'
import { ImportType, OnboardingEntryPoint } from 'wallet/src/features/onboarding/types' import { ImportType, OnboardingEntryPoint } from 'wallet/src/features/onboarding/types'
import { useCanAddressClaimUnitag } from 'wallet/src/features/unitags/hooks' import { useCanAddressClaimUnitag } from 'wallet/src/features/unitags/hooks'
......
...@@ -51,8 +51,8 @@ import { ONBOARDING_QR_ETCHING_VIDEO_DARK, ONBOARDING_QR_ETCHING_VIDEO_LIGHT } f ...@@ -51,8 +51,8 @@ import { ONBOARDING_QR_ETCHING_VIDEO_DARK, ONBOARDING_QR_ETCHING_VIDEO_LIGHT } f
import LockIcon from 'ui/src/assets/icons/lock.svg' import LockIcon from 'ui/src/assets/icons/lock.svg'
import { AnimatedFlex, flexStyles } from 'ui/src/components/layout' import { AnimatedFlex, flexStyles } from 'ui/src/components/layout'
import { fonts, iconSizes, opacify, spacing } from 'ui/src/theme' import { fonts, iconSizes, opacify, spacing } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { QRCodeDisplay } from 'wallet/src/components/QRCodeScanner/QRCode' import { QRCodeDisplay } from 'wallet/src/components/QRCodeScanner/QRCode'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { Arrow } from 'wallet/src/components/icons/Arrow' import { Arrow } from 'wallet/src/components/icons/Arrow'
......
...@@ -47,8 +47,8 @@ import MessageQuestion from 'ui/src/assets/icons/message-question.svg' ...@@ -47,8 +47,8 @@ import MessageQuestion from 'ui/src/assets/icons/message-question.svg'
import UniswapIcon from 'ui/src/assets/icons/uniswap-logo.svg' import UniswapIcon from 'ui/src/assets/icons/uniswap-logo.svg'
import { iconSizes, spacing } from 'ui/src/theme' import { iconSizes, spacing } from 'ui/src/theme'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName' import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'uniswap/src/utils/platform'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
......
...@@ -34,8 +34,8 @@ import NotificationIcon from 'ui/src/assets/icons/bell.svg' ...@@ -34,8 +34,8 @@ import NotificationIcon from 'ui/src/assets/icons/bell.svg'
import GlobalIcon from 'ui/src/assets/icons/global.svg' import GlobalIcon from 'ui/src/assets/icons/global.svg'
import TextEditIcon from 'ui/src/assets/icons/textEdit.svg' import TextEditIcon from 'ui/src/assets/icons/textEdit.svg'
import { iconSizes, spacing } from 'ui/src/theme' import { iconSizes, spacing } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { Switch } from 'wallet/src/components/buttons/Switch' import { Switch } from 'wallet/src/components/buttons/Switch'
import { ChainId } from 'wallet/src/constants/chains' import { ChainId } from 'wallet/src/constants/chains'
......
...@@ -14,8 +14,8 @@ import { Screen } from 'src/components/layout/Screen' ...@@ -14,8 +14,8 @@ import { Screen } from 'src/components/layout/Screen'
import { UnitagBanner } from 'src/components/unitags/UnitagBanner' import { UnitagBanner } from 'src/components/unitags/UnitagBanner'
import { Button, Flex, Icons, Text } from 'ui/src' import { Button, Flex, Icons, Text } from 'ui/src'
import { fonts } from 'ui/src/theme' import { fonts } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { isIOS } from 'uniswap/src/utils/platform' import { isIOS } from 'uniswap/src/utils/platform'
import { TextInput } from 'wallet/src/components/input/TextInput' import { TextInput } from 'wallet/src/components/input/TextInput'
import { NICKNAME_MAX_LENGTH } from 'wallet/src/constants/accounts' import { NICKNAME_MAX_LENGTH } from 'wallet/src/constants/accounts'
......
...@@ -58,13 +58,11 @@ describe('Uni tags support', () => { ...@@ -58,13 +58,11 @@ describe('Uni tags support', () => {
cy.contains(haydenUnitag).should('be.visible') cy.contains(haydenUnitag).should('be.visible')
cy.contains('0x50EC...79C3').should('be.visible') cy.contains('0x50EC...79C3').should('be.visible')
}) })
cy.get(getTestSelector('secondary-identifiers')) cy.get(getTestSelector('secondary-identifiers')).trigger('mouseover').click()
.trigger('mouseover') cy.get(getTestSelector('secondary-identifiers-dropdown')).within(() => {
.click() cy.contains(haydenENS)
.within(() => { cy.contains('0x50EC...79C3')
cy.contains(haydenENS).should('be.visible') })
cy.contains('0x50EC...79C3').should('be.visible')
})
}) })
}) })
}) })
...@@ -106,7 +106,8 @@ describe('Permit2', () => { ...@@ -106,7 +106,8 @@ describe('Permit2', () => {
* already 0 to mitigate the race condition described here: * already 0 to mitigate the race condition described here:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*/ */
it('swaps USDT with existing permit, and existing but insufficient token approval', () => { // TODO re-enable web test
it.skip('swaps USDT with existing permit, and existing but insufficient token approval', () => {
cy.hardhat().then(async (hardhat) => { cy.hardhat().then(async (hardhat) => {
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDT, 2e6)) await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDT, 2e6))
await hardhat.mine() await hardhat.mine()
......
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { getTestSelector } from '../utils' import { getTestSelector } from '../utils'
describe('Wallet Dropdown', () => { describe('Wallet Dropdown', () => {
......
...@@ -2,7 +2,7 @@ import 'cypress-hardhat/lib/browser' ...@@ -2,7 +2,7 @@ import 'cypress-hardhat/lib/browser'
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge' import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
import { FeatureFlagClient, FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/experiments/flags' import { FeatureFlagClient, FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/statsig/flags'
import { UserState, initialState } from '../../src/state/user/reducer' import { UserState, initialState } from '../../src/state/user/reducer'
import { CONNECTED_WALLET_USER_STATE, setInitialUserState } from '../utils/user-state' import { CONNECTED_WALLET_USER_STATE, setInitialUserState } from '../utils/user-state'
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
/* eslint-disable import/no-unused-modules */ /* eslint-disable import/no-unused-modules */
import { paths } from '../src/pages/paths' import { paths } from '../src/pages/paths'
import { MetaTagInjector } from './components/metaTagInjector' import { transformResponse } from './utils/transformResponse'
function doesMatchPath(path: string): boolean { function doesMatchPath(path: string): boolean {
const regexPaths = paths.map((p) => '^' + p.replace(/:[^/]+/g, '[^/]+').replace(/\*/g, '.*') + '$') const regexPaths = paths.map((p) => '^' + p.replace(/:[^/]+/g, '[^/]+').replace(/\*/g, '.*') + '$')
...@@ -21,13 +21,13 @@ export const onRequest: PagesFunction = async ({ request, next }) => { ...@@ -21,13 +21,13 @@ export const onRequest: PagesFunction = async ({ request, next }) => {
url: request.url, url: request.url,
description: 'Swap or provide liquidity on the Uniswap Protocol', description: 'Swap or provide liquidity on the Uniswap Protocol',
} }
const res = next() const response = next()
if (doesMatchPath(requestURL.pathname)) { if (doesMatchPath(requestURL.pathname)) {
try { try {
return new HTMLRewriter().on('head', new MetaTagInjector(data, request)).transform(await res) return transformResponse(request, await response, data)
} catch (e) { } catch (e) {
return res return response
} }
} }
return res return response
} }
...@@ -10,6 +10,8 @@ type MetaTagInjectorInput = { ...@@ -10,6 +10,8 @@ type MetaTagInjectorInput = {
* to inject meta tags into the <head> of an HTML document. * to inject meta tags into the <head> of an HTML document.
*/ */
export class MetaTagInjector implements HTMLRewriterElementContentHandlers { export class MetaTagInjector implements HTMLRewriterElementContentHandlers {
static SELECTOR = 'head'
constructor(private input: MetaTagInjectorInput, private request: Request) {} constructor(private input: MetaTagInjectorInput, private request: Request) {}
append(element: Element, property: string, content: string) { append(element: Element, property: string, content: string) {
......
/* eslint-disable import/no-unused-modules */ /* eslint-disable import/no-unused-modules */
import { getMetadataRequest } from '../../utils/getRequest'
import getToken from '../../utils/getToken' import getToken from '../../utils/getToken'
import { transformResponse } from '../../utils/transformResponse'
export const onRequest: PagesFunction = async ({ params, request, next }) => { export const onRequest: PagesFunction = async ({ params, request, next }) => {
const res = next() const response = next()
try { try {
const { index } = params const { index } = params
const networkName = index[0]?.toString() const networkName = index[0]?.toString()
const tokenAddress = index[1]?.toString() const tokenAddress = index[1]?.toString()
if (!tokenAddress) { if (!tokenAddress) {
return res return response
} }
return getMetadataRequest(res, request, () => getToken(networkName, tokenAddress, request.url)) return transformResponse(request, await response, () => getToken(networkName, tokenAddress, request.url))
} catch (e) { } catch (e) {
return res return response
} }
} }
/* eslint-disable import/no-unused-modules */ /* eslint-disable import/no-unused-modules */
import getAsset from '../../utils/getAsset' import getAsset from '../../utils/getAsset'
import { getMetadataRequest } from '../../utils/getRequest' import { transformResponse } from '../../utils/transformResponse'
export const onRequest: PagesFunction = async ({ params, request, next }) => { export const onRequest: PagesFunction = async ({ params, request, next }) => {
const res = next() const response = next()
try { try {
const { index } = params const { index } = params
const collectionAddress = index[0]?.toString() const collectionAddress = index[0]?.toString()
const tokenId = index[1]?.toString() const tokenId = index[1]?.toString()
return getMetadataRequest(res, request, () => getAsset(collectionAddress, tokenId, request.url)) return transformResponse(request, await response, () => getAsset(collectionAddress, tokenId, request.url))
} catch (e) { } catch (e) {
return res return response
} }
} }
/* eslint-disable import/no-unused-modules */ /* eslint-disable import/no-unused-modules */
import getCollection from '../../utils/getCollection' import getCollection from '../../utils/getCollection'
import { getMetadataRequest } from '../../utils/getRequest' import { transformResponse } from '../../utils/transformResponse'
export const onRequest: PagesFunction = async ({ params, request, next }) => { export const onRequest: PagesFunction = async ({ params, request, next }) => {
const res = next() const response = next()
try { try {
const { index } = params const { index } = params
const collectionAddress = index?.toString() const collectionAddress = index?.toString()
return getMetadataRequest(res, request, () => getCollection(collectionAddress, request.url)) return transformResponse(request, await response, () => getCollection(collectionAddress, request.url))
} catch (e) { } catch (e) {
return res return response
} }
} }
import { MetaTagInjector } from '../components/metaTagInjector'
import Cache, { Data } from './cache' import Cache, { Data } from './cache'
export async function getMetadataRequest(
res: Promise<Response>,
request: Request,
getData: () => Promise<Data | undefined>
) {
try {
const cachedData = await getRequest(request.url, getData, (data): data is Data => true)
if (cachedData) {
return new HTMLRewriter().on('head', new MetaTagInjector(cachedData, request)).transform(await res)
} else {
return res
}
} catch (e) {
return res
}
}
export async function getRequest<T extends Data>( export async function getRequest<T extends Data>(
url: string, url: string,
getData: () => Promise<T | undefined>, getData: () => Promise<T | undefined>,
......
/* eslint-disable import/no-unused-modules */
import { MetaTagInjector } from '../components/metaTagInjector'
import { Data } from './cache'
import { getRequest } from './getRequest'
export async function transformResponse(
request: Request,
response: Response,
data: (() => Promise<Data | undefined>) | Data | undefined
) {
try {
if (typeof data === 'function') {
data = await getRequest(request.url, data, (data): data is Data => true)
}
if (data) {
return new HTMLRewriter().on(MetaTagInjector.SELECTOR, new MetaTagInjector(data, request)).transform(response)
} else {
return response
}
} catch (e) {
return response
}
}
...@@ -155,7 +155,7 @@ ...@@ -155,7 +155,7 @@
"ts-jest": "^29.1.1", "ts-jest": "^29.1.1",
"tsafe": "1.6.4", "tsafe": "1.6.4",
"typescript": "5.3.3", "typescript": "5.3.3",
"webpack": "5.89.0", "webpack": "5.90.0",
"webpack-retry-chunk-load-plugin": "3.1.1", "webpack-retry-chunk-load-plugin": "3.1.1",
"wrangler": "3.15.0", "wrangler": "3.15.0",
"yarn-deduplicate": "6.0.0" "yarn-deduplicate": "6.0.0"
...@@ -184,6 +184,7 @@ ...@@ -184,6 +184,7 @@
"@sentry/types": "7.80.0", "@sentry/types": "7.80.0",
"@tamagui/core": "1.94.3", "@tamagui/core": "1.94.3",
"@tamagui/react-native-svg": "1.94.3", "@tamagui/react-native-svg": "1.94.3",
"@tanstack/react-query": "5.28.14",
"@tanstack/react-table": "8.10.7", "@tanstack/react-table": "8.10.7",
"@types/poisson-disk-sampling": "2.2.4", "@types/poisson-disk-sampling": "2.2.4",
"@types/react-scroll-sync": "0.8.7", "@types/react-scroll-sync": "0.8.7",
...@@ -290,6 +291,8 @@ ...@@ -290,6 +291,8 @@
"utilities": "workspace:^", "utilities": "workspace:^",
"uuid": "9.0.0", "uuid": "9.0.0",
"video-extensions": "1.2.0", "video-extensions": "1.2.0",
"viem": "2.x",
"wagmi": "2.5.19",
"wcag-contrast": "3.0.0", "wcag-contrast": "3.0.0",
"web-vitals": "2.1.4", "web-vitals": "2.1.4",
"xml2js": "0.6.2", "xml2js": "0.6.2",
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
"https://*.uniswap.org", "https://*.uniswap.org",
"https://uniswap.org", "https://uniswap.org",
"https://assets.coingecko.com/", "https://assets.coingecko.com/",
"https://*.amazonaws.com", "https://*.amazonaws.com",
"https://basescan.org", "https://basescan.org",
"https://celo-org.github.io/", "https://celo-org.github.io/",
"https://cdn.center.app/", "https://cdn.center.app/",
...@@ -60,43 +60,63 @@ ...@@ -60,43 +60,63 @@
"wss://*.uniswap.org", "wss://*.uniswap.org",
"https://*.uniswap.org", "https://*.uniswap.org",
"https://uniswap.org", "https://uniswap.org",
"https://assets.coingecko.com/", "https://arb1.arbitrum.io",
"https://*.coingecko.com/",
"https://*.alchemy.com",
"https://buy.moonpay.com/", "https://buy.moonpay.com/",
"https://bsc-dataseed1.binance.org/",
"https://cdn.center.app/", "https://cdn.center.app/",
"https://cdn.jsdelivr.net/npm/@rive-app/canvas@2.8.3/rive.wasm", "https://cdn.jsdelivr.net/npm/@rive-app/canvas@2.8.3/rive.wasm",
"https://chain-proxy.wallet.coinbase.com", "https://*.coinbase.com",
"https://statsigapi.net", "https://statsigapi.net",
"https://api.moonpay.com/", "https://api.moonpay.com/",
"https://api.opensea.io", "https://api.opensea.io",
"https://api.thegraph.com/", "https://api.thegraph.com/",
"https://arbitrum-mainnet.infura.io/", "https://arbitrum-mainnet.infura.io/",
"https://assets.coingecko.com",
"https://avalanche-mainnet.infura.io/", "https://avalanche-mainnet.infura.io/",
"https://base-mainnet.infura.io/", "https://base-mainnet.infura.io/",
"https://bridge.arbitrum.io", "https://bridge.arbitrum.io",
"https://celo-mainnet.infura.io/", "https://celo-mainnet.infura.io/",
"https://celo-org.github.io", "https://celo-org.github.io",
"https://cloudflare-ipfs.com", "https://cloudflare-ipfs.com",
"https://explorer-api.walletconnect.com", "https://*.zerion.io",
"https://*.drpc.org/",
"https://*.base.org/",
"https://*.walletconnect.com",
"https://ethereum-optimism.github.io/", "https://ethereum-optimism.github.io/",
"https://*.twnodes.com",
"https://forno.celo.org/", "https://forno.celo.org/",
"https://www.gemini.com", "https://*.gemini.com",
"https://gateway.ipfs.io/", "https://gateway.ipfs.io/",
"https://i.seadn.io/", "https://i.seadn.io/",
"https://ipfs.io/",
"https://lh3.googleusercontent.com/", "https://lh3.googleusercontent.com/",
"https://mainnet.infura.io", "https://mainnet.infura.io",
"https://*.nodereal.io",
"https://o1037921.ingest.sentry.io", "https://o1037921.ingest.sentry.io",
"https://old-wispy-arrow.bsc.quiknode.pro/", "https://old-wispy-arrow.bsc.quiknode.pro/",
"https://openseauserdata.com/", "https://openseauserdata.com/",
"https://optimism-mainnet.infura.io/", "https://performance.radar.cloudflare.com/",
"https://polygon-mainnet.infura.io/", "https://valid.rpki.cloudflare.com",
"https://sparrow.cloudflare.com/",
"https://ipv4-check-perf.radar.cloudflare.com",
"https://ipv6-check-perf.radar.cloudflare.com/",
"https://invalid.rpki.cloudflare.com/",
"https://raw.githubusercontent.com", "https://raw.githubusercontent.com",
"https://raw.seadn.io/", "https://raw.seadn.io/",
"https://s2.coinmarketcap.com/", "https://rpc.ankr.com",
"https://static.optimism.io", "https://rpc.degen.tips",
"https://rpc-mainnet.maticvigil.com",
"https://rpc.mevblocker.io/",
"https://rpc.scroll.io/",
"https://*.coinmarketcap.com/",
"https://*.optimism.io",
"https://sockjs-us3.pusher.com/", "https://sockjs-us3.pusher.com/",
"https://api.studio.thegraph.com/", "https://api.studio.thegraph.com/",
"https://*.googleapis.com",
"https://trustwallet.com", "https://trustwallet.com",
"https://tokenlist.arbitrum.io", "https://*.arbitrum.io",
"https://tokens.coingecko.com", "https://tokens.coingecko.com",
"https://*.twnodes.com", "https://*.twnodes.com",
"https://ultra-blue-flower.quiknode.pro", "https://ultra-blue-flower.quiknode.pro",
...@@ -104,10 +124,13 @@ ...@@ -104,10 +124,13 @@
"https://us-central1-uniswap-mobile.cloudfunctions.net/", "https://us-central1-uniswap-mobile.cloudfunctions.net/",
"https://vercel.com", "https://vercel.com",
"https://vercel.live/", "https://vercel.live/",
"https://wallet.crypto.com",
"https://web3.1inch.io",
"https://www.gemini.com", "https://www.gemini.com",
"https://*.quiknode.pro", "https://*.quiknode.pro",
"https://*.infura.io", "https://*.infura.io",
"wss://relay.walletconnect.com", "wss://relay.walletconnect.com",
"wss://relay.walletconnect.org",
"wss://www.walletlink.org", "wss://www.walletlink.org",
"wss://ws-us3.pusher.com/" "wss://ws-us3.pusher.com/"
], ],
......
...@@ -65,6 +65,7 @@ const UNIButton = styled(WalletButton)` ...@@ -65,6 +65,7 @@ const UNIButton = styled(WalletButton)`
const IconContainer = styled.div` const IconContainer = styled.div`
display: flex; display: flex;
flex: 0 0 auto;
align-items: center; align-items: center;
& > a, & > a,
& > button { & > button {
......
import 'test-utils/tokens/mocks'
import { ChainId, WETH9 } from '@uniswap/sdk-core' import { ChainId, WETH9 } from '@uniswap/sdk-core'
import { formatTimestamp } from 'components/AccountDrawer/MiniPortfolio/formatTimestamp' import { formatTimestamp } from 'components/AccountDrawer/MiniPortfolio/formatTimestamp'
import { DAI, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens' import { DAI } from 'constants/tokens'
import { useCurrency } from 'hooks/Tokens'
import { SignatureType } from 'state/signatures/types' import { SignatureType } from 'state/signatures/types'
import { mocked } from 'test-utils/mocked' import { mocked } from 'test-utils/mocked'
import { render } from 'test-utils/render' import { render } from 'test-utils/render'
import { UniswapXOrderStatus } from 'types/uniswapx'
import { UniswapXOrderStatus } from 'types/uniswapx'
import { OrderContent } from './OffchainActivityModal' import { OrderContent } from './OffchainActivityModal'
jest.mock('hooks/Tokens', () => ({
useCurrency: jest.fn(),
}))
jest.mock('components/AccountDrawer/MiniPortfolio/formatTimestamp', () => ({ jest.mock('components/AccountDrawer/MiniPortfolio/formatTimestamp', () => ({
formatTimestamp: jest.fn(), formatTimestamp: jest.fn(),
})) }))
describe('OrderContent', () => { describe('OrderContent', () => {
beforeEach(() => { beforeEach(() => {
mocked(useCurrency).mockImplementation((currencyId: Maybe<string>) => {
if (currencyId === WETH9[ChainId.MAINNET].address) {
return WRAPPED_NATIVE_CURRENCY[ChainId.MAINNET]
} else {
return DAI
}
})
mocked(formatTimestamp).mockImplementation(() => { mocked(formatTimestamp).mockImplementation(() => {
return 'Mock Date' // This ensures consistent test behavior across local and CI return 'Mock Date' // This ensures consistent test behavior across local and CI
}) })
...@@ -45,7 +36,7 @@ describe('OrderContent', () => { ...@@ -45,7 +36,7 @@ describe('OrderContent', () => {
isUniswapXOrder: true, isUniswapXOrder: true,
type: 1, type: 1,
tradeType: 0, tradeType: 0,
inputCurrencyId: '0x6b175474e89094c44da98b954eedeac495271d0f', inputCurrencyId: DAI.address,
outputCurrencyId: WETH9[ChainId.MAINNET].address, outputCurrencyId: WETH9[ChainId.MAINNET].address,
inputCurrencyAmountRaw: '252074033564766400000', inputCurrencyAmountRaw: '252074033564766400000',
expectedOutputCurrencyAmountRaw: '106841079134757921', expectedOutputCurrencyAmountRaw: '106841079134757921',
......
...@@ -101,7 +101,7 @@ const InsufficientFundsCopyContainer = styled(Row)` ...@@ -101,7 +101,7 @@ const InsufficientFundsCopyContainer = styled(Row)`
const AlertIconContainer = styled.div` const AlertIconContainer = styled.div`
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
background-color: #1f1e02; background-color: ${({ theme }) => theme.deprecated_accentWarningSoft};
width: 40px; width: 40px;
height: 40px; height: 40px;
justify-content: center; justify-content: center;
......
...@@ -66,7 +66,46 @@ Object { ...@@ -66,7 +66,46 @@ Object {
} }
`; `;
exports[`parseRemote parseRemoteActivities should parse closed UniswapX order 1`] = ` exports[`parseRemote parseRemoteActivities should parse eth wrap 1`] = `
Object {
"chainId": 1,
"currencies": Array [
ExtendedEther {
"chainId": 1,
"decimals": 18,
"isNative": true,
"isToken": false,
"name": "Ethereum",
"symbol": "ETH",
},
Token {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "Wrapped Ether",
"sellFeeBps": undefined,
"symbol": "WETH",
},
],
"descriptor": "100 ETH for 100 WETH",
"from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
"hash": "someHash",
"isSpam": false,
"logos": Array [
"https://token-icons.s3.amazonaws.com/eth.png",
"https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
],
"nonce": 12345,
"status": "CONFIRMED",
"timestamp": 10000,
"title": "Wrapped",
}
`;
exports[`parseRemote parseRemoteActivities should parse expired UniswapX order 1`] = `
Object { Object {
"chainId": 1, "chainId": 1,
"currencies": Array [ "currencies": Array [
...@@ -104,6 +143,7 @@ Object { ...@@ -104,6 +143,7 @@ Object {
"addedTime": 10000, "addedTime": 10000,
"chainId": 1, "chainId": 1,
"encodedOrder": undefined, "encodedOrder": undefined,
"expiry": undefined,
"id": "someId", "id": "someId",
"offerer": "someOfferer", "offerer": "someOfferer",
"orderHash": "someHash", "orderHash": "someHash",
...@@ -115,11 +155,11 @@ Object { ...@@ -115,11 +155,11 @@ Object {
"isUniswapXOrder": true, "isUniswapXOrder": true,
"minimumOutputCurrencyAmountRaw": "200000000000000000000", "minimumOutputCurrencyAmountRaw": "200000000000000000000",
"outputCurrencyId": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "outputCurrencyId": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"settledOutputCurrencyAmountRaw": "200000000000000000000", "settledOutputCurrencyAmountRaw": undefined,
"tradeType": 0, "tradeType": 0,
"type": 1, "type": 1,
}, },
"txHash": "someHash", "txHash": undefined,
"type": undefined, "type": undefined,
}, },
"prefixIconSrc": "bolt.svg", "prefixIconSrc": "bolt.svg",
...@@ -130,17 +170,20 @@ Object { ...@@ -130,17 +170,20 @@ Object {
} }
`; `;
exports[`parseRemote parseRemoteActivities should parse eth wrap 1`] = ` exports[`parseRemote parseRemoteActivities should parse filledUniswapX order 1`] = `
Object { Object {
"chainId": 1, "chainId": 1,
"currencies": Array [ "currencies": Array [
ExtendedEther { Token {
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"buyFeeBps": undefined,
"chainId": 1, "chainId": 1,
"decimals": 18, "decimals": 18,
"isNative": true, "isNative": false,
"isToken": false, "isToken": true,
"name": "Ethereum", "name": "DAI",
"symbol": "ETH", "sellFeeBps": undefined,
"symbol": "DAI",
}, },
Token { Token {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
...@@ -154,18 +197,40 @@ Object { ...@@ -154,18 +197,40 @@ Object {
"symbol": "WETH", "symbol": "WETH",
}, },
], ],
"descriptor": "100 ETH for 100 WETH", "descriptor": "100 DAI for 200 WETH",
"from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3", "from": "someOfferer",
"hash": "someHash", "hash": "someHash",
"isSpam": false,
"logos": Array [ "logos": Array [
"https://token-icons.s3.amazonaws.com/eth.png", "someUrl",
"https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", "someUrl",
], ],
"nonce": 12345, "offchainOrderDetails": Object {
"addedTime": 10000,
"chainId": 1,
"encodedOrder": undefined,
"expiry": undefined,
"id": "someId",
"offerer": "someOfferer",
"orderHash": "someHash",
"status": "filled",
"swapInfo": Object {
"expectedOutputCurrencyAmountRaw": "200000000000000000000",
"inputCurrencyAmountRaw": "100000000000000000000",
"inputCurrencyId": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"isUniswapXOrder": true,
"minimumOutputCurrencyAmountRaw": "200000000000000000000",
"outputCurrencyId": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"settledOutputCurrencyAmountRaw": "200000000000000000000",
"tradeType": 0,
"type": 1,
},
"txHash": "someHash",
"type": undefined,
},
"prefixIconSrc": "bolt.svg",
"status": "CONFIRMED", "status": "CONFIRMED",
"timestamp": 10000, "timestamp": 10000,
"title": "Wrapped", "title": "Swapped",
} }
`; `;
......
...@@ -63,56 +63,6 @@ const mockAssetActivityPartsFragment = { ...@@ -63,56 +63,6 @@ const mockAssetActivityPartsFragment = {
}, },
} }
const mockSwapOrderDetailsPartsFragment = {
__typename: 'SwapOrderDetails',
id: 'someId',
offerer: 'someOfferer',
hash: 'someHash',
inputTokenQuantity: '100',
outputTokenQuantity: '200',
orderStatus: SwapOrderStatus.Open,
inputToken: {
__typename: 'Token',
id: DAI.address,
name: 'DAI',
symbol: DAI.symbol,
address: DAI.address,
decimals: 18,
chain: Chain.Ethereum,
standard: TokenStandard.Erc20,
project: {
__typename: 'TokenProject',
id: 'projectId',
isSpam: false,
logo: {
__typename: 'Image',
id: 'imageId',
url: 'someUrl',
},
},
},
outputToken: {
__typename: 'Token',
id: WETH9[1].address,
name: 'Wrapped Ether',
symbol: 'WETH',
address: WETH9[1].address,
decimals: 18,
chain: Chain.Ethereum,
standard: TokenStandard.Erc20,
project: {
__typename: 'TokenProject',
id: 'projectId',
isSpam: false,
logo: {
__typename: 'Image',
id: 'imageId',
url: 'someUrl',
},
},
},
}
const mockNftApprovalPartsFragment: NftApprovalPartsFragment = { const mockNftApprovalPartsFragment: NftApprovalPartsFragment = {
__typename: 'NftApproval', __typename: 'NftApproval',
id: 'approvalId', id: 'approvalId',
...@@ -395,19 +345,6 @@ const mockTokenApprovalPartsFragment: TokenApprovalPartsFragment = { ...@@ -395,19 +345,6 @@ const mockTokenApprovalPartsFragment: TokenApprovalPartsFragment = {
}, },
} }
export const MockOpenUniswapXOrder = {
...mockAssetActivityPartsFragment,
details: mockSwapOrderDetailsPartsFragment,
} as AssetActivityPartsFragment
export const MockClosedUniswapXOrder = {
...mockAssetActivityPartsFragment,
details: {
...mockSwapOrderDetailsPartsFragment,
orderStatus: SwapOrderStatus.Expired,
},
} as AssetActivityPartsFragment
const commonTransactionDetailsFields = { const commonTransactionDetailsFields = {
__typename: 'TransactionDetails', __typename: 'TransactionDetails',
from: MockSenderAddress, from: MockSenderAddress,
......
import { act, renderHook } from '@testing-library/react' import { act, renderHook } from '@testing-library/react'
import ms from 'ms' import ms from 'ms'
import { MockExpiredUniswapXOrder, MockFilledUniswapXOrder, MockOpenUniswapXOrder } from 'state/signatures/fixtures'
import { import {
MockClosedUniswapXOrder,
MockMint, MockMint,
MockMoonpayPurchase, MockMoonpayPurchase,
MockNFTApproval, MockNFTApproval,
MockNFTApprovalForAll, MockNFTApprovalForAll,
MockNFTPurchase, MockNFTPurchase,
MockNFTReceive, MockNFTReceive,
MockOpenUniswapXOrder,
MockRemoveLiquidity, MockRemoveLiquidity,
MockSenderAddress, MockSenderAddress,
MockSpamMint, MockSpamMint,
...@@ -19,10 +18,10 @@ import { ...@@ -19,10 +18,10 @@ import {
MockTokenApproval, MockTokenApproval,
MockTokenReceive, MockTokenReceive,
MockTokenSend, MockTokenSend,
MockWrap,
mockTokenTransferInPartsFragment, mockTokenTransferInPartsFragment,
mockTokenTransferOutPartsFragment, mockTokenTransferOutPartsFragment,
mockTransactionDetailsPartsFragment, mockTransactionDetailsPartsFragment,
MockWrap,
} from './fixtures/activity' } from './fixtures/activity'
import { import {
offchainOrderDetailsFromGraphQLTransactionActivity, offchainOrderDetailsFromGraphQLTransactionActivity,
...@@ -48,8 +47,12 @@ describe('parseRemote', () => { ...@@ -48,8 +47,12 @@ describe('parseRemote', () => {
const result = parseRemoteActivities([MockOpenUniswapXOrder], '', jest.fn()) const result = parseRemoteActivities([MockOpenUniswapXOrder], '', jest.fn())
expect(result).toEqual({}) expect(result).toEqual({})
}) })
it('should parse closed UniswapX order', () => { it('should parse expired UniswapX order', () => {
const result = parseRemoteActivities([MockClosedUniswapXOrder], '', jest.fn()) const result = parseRemoteActivities([MockExpiredUniswapXOrder], '', jest.fn())
expect(result?.['someHash']).toMatchSnapshot()
})
it('should parse filledUniswapX order', () => {
const result = parseRemoteActivities([MockFilledUniswapXOrder], '', jest.fn())
expect(result?.['someHash']).toMatchSnapshot() expect(result?.['someHash']).toMatchSnapshot()
}) })
it('should parse NFT approval', () => { it('should parse NFT approval', () => {
......
import { ChainId, Currency, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, TradeType, UNI_ADDRESSES } from '@uniswap/sdk-core' import { ChainId, Currency, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, TradeType, UNI_ADDRESSES } from '@uniswap/sdk-core'
import UniswapXBolt from 'assets/svg/bolt.svg' import UniswapXBolt from 'assets/svg/bolt.svg'
import moonpayLogoSrc from 'assets/svg/moonpay.svg' import moonpayLogoSrc from 'assets/svg/moonpay.svg'
import { asSupportedChain } from 'constants/chains'
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens' import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
import { BigNumber } from 'ethers/lib/ethers' import { BigNumber } from 'ethers/lib/ethers'
import { formatUnits, parseUnits } from 'ethers/lib/utils' import { formatUnits, parseUnits } from 'ethers/lib/utils'
...@@ -8,9 +9,8 @@ import { gqlToCurrency, logSentryErrorForUnsupportedChain, supportedChainIdFromG ...@@ -8,9 +9,8 @@ import { gqlToCurrency, logSentryErrorForUnsupportedChain, supportedChainIdFromG
import { t } from 'i18n' import { t } from 'i18n'
import ms from 'ms' import ms from 'ms'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import store from 'state' import { OrderActivity, parseRemote as parseRemoteSignature } from 'state/signatures/parseRemote'
import { addSignature } from 'state/signatures/reducer' import { UniswapXOrderDetails } from 'state/signatures/types'
import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types'
import { TransactionType as LocalTransactionType } from 'state/transactions/types' import { TransactionType as LocalTransactionType } from 'state/transactions/types'
import { UniswapXOrderStatus } from 'types/uniswapx' import { UniswapXOrderStatus } from 'types/uniswapx'
import { import {
...@@ -19,9 +19,6 @@ import { ...@@ -19,9 +19,6 @@ import {
NftApprovalPartsFragment, NftApprovalPartsFragment,
NftApproveForAllPartsFragment, NftApproveForAllPartsFragment,
NftTransferPartsFragment, NftTransferPartsFragment,
SwapOrderDetailsPartsFragment,
SwapOrderStatus,
SwapOrderType,
TokenApprovalPartsFragment, TokenApprovalPartsFragment,
TokenAssetPartsFragment, TokenAssetPartsFragment,
TokenTransferPartsFragment, TokenTransferPartsFragment,
...@@ -29,9 +26,8 @@ import { ...@@ -29,9 +26,8 @@ import {
TransactionType, TransactionType,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { isAddress, isSameAddress } from 'utilities/src/addresses' import { isAddress, isSameAddress } from 'utilities/src/addresses'
import { currencyId } from 'utils/currencyId'
import { NumberType, useFormatter } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { MOONPAY_SENDER_ADDRESSES, OrderStatusTable, OrderTextTable } from '../constants' import { MOONPAY_SENDER_ADDRESSES, OrderTextTable } from '../constants'
import { Activity } from './types' import { Activity } from './types'
type TransactionChanges = { type TransactionChanges = {
...@@ -276,7 +272,7 @@ export function offchainOrderDetailsFromGraphQLTransactionActivity( ...@@ -276,7 +272,7 @@ export function offchainOrderDetailsFromGraphQLTransactionActivity(
changes: TransactionChanges, changes: TransactionChanges,
formatNumberOrString: FormatNumberOrStringFunctionType formatNumberOrString: FormatNumberOrStringFunctionType
): UniswapXOrderDetails | undefined { ): UniswapXOrderDetails | undefined {
const chainId = supportedChainIdFromGQLChain(activity.chain) const chainId = asSupportedChain(supportedChainIdFromGQLChain(activity.chain))
if (!activity || !activity.details || !chainId) return undefined if (!activity || !activity.details || !chainId) return undefined
if (changes.TokenTransfer.length < 2) return undefined if (changes.TokenTransfer.length < 2) return undefined
...@@ -333,7 +329,6 @@ function parseLPTransfers(changes: TransactionChanges, formatNumberOrString: For ...@@ -333,7 +329,6 @@ function parseLPTransfers(changes: TransactionChanges, formatNumberOrString: For
} }
type TransactionActivity = AssetActivityPartsFragment & { details: TransactionDetailsPartsFragment } type TransactionActivity = AssetActivityPartsFragment & { details: TransactionDetailsPartsFragment }
type OrderActivity = AssetActivityPartsFragment & { details: SwapOrderDetailsPartsFragment }
function parseSendReceive( function parseSendReceive(
changes: TransactionChanges, changes: TransactionChanges,
...@@ -448,112 +443,31 @@ function getLogoSrcs(changes: TransactionChanges): Array<string | undefined> { ...@@ -448,112 +443,31 @@ function getLogoSrcs(changes: TransactionChanges): Array<string | undefined> {
return Array.from(logoSet) return Array.from(logoSet)
} }
function swapOrderTypeToSignatureType(swapOrderType: SwapOrderType): SignatureType { function parseUniswapXOrder(activity: OrderActivity): Activity | undefined {
switch (swapOrderType) { const signature = parseRemoteSignature(activity)
case SwapOrderType.Limit:
return SignatureType.SIGN_LIMIT
case SwapOrderType.Dutch:
return SignatureType.SIGN_UNISWAPX_ORDER
case SwapOrderType.DutchV2:
return SignatureType.SIGN_UNISWAPX_V2_ORDER
}
}
function parseUniswapXOrder({ details, chain, timestamp }: OrderActivity): Activity | undefined {
const supportedChain = supportedChainIdFromGQLChain(chain)
if (!supportedChain) {
logSentryErrorForUnsupportedChain({
extras: { details },
errorMessage: 'Invalid activity from unsupported chain received from GQL',
})
return undefined
}
// If the order is open, maybe add it to our local records (if it was initiated on this device, this will be a no-op).
if (details.orderStatus === SwapOrderStatus.Open) {
const inputCurrency = gqlToCurrency(details.inputToken)
const outputCurrency = gqlToCurrency(details.outputToken)
const inputTokenQuantity = parseUnits(details.inputTokenQuantity, details.inputToken.decimals).toString()
const outputTokenQuantity = parseUnits(details.outputTokenQuantity, details.outputToken.decimals).toString()
if (inputTokenQuantity === '0' || outputTokenQuantity === '0') { // If the order is open, do not render it.
// TODO(WEB-3765): This is a temporary mitigation for a bug where the backend sends "0.000000" for small amounts. if (signature.status === UniswapXOrderStatus.OPEN) {
throw new Error('Invalid activity received from GQL') return
}
store.dispatch(
addSignature({
type: swapOrderTypeToSignatureType(details.swapOrderType),
offerer: details.offerer,
id: details.hash,
chainId: supportedChain,
orderHash: details.hash,
expiry: details.expiry,
encodedOrder: details.encodedOrder,
swapInfo: {
type: LocalTransactionType.SWAP,
inputCurrencyId: currencyId(inputCurrency),
outputCurrencyId: currencyId(outputCurrency),
isUniswapXOrder: true,
// This doesn't affect the display, but we don't know this value from the remote activity.
tradeType: TradeType.EXACT_INPUT,
inputCurrencyAmountRaw: inputTokenQuantity,
expectedOutputCurrencyAmountRaw: outputTokenQuantity,
minimumOutputCurrencyAmountRaw: outputTokenQuantity,
},
status: UniswapXOrderStatus.OPEN,
addedTime: timestamp * 1000,
})
)
return undefined
} }
// If the order is not open, render it like any other remote activity. const { inputToken, inputTokenQuantity, outputToken, outputTokenQuantity } = activity.details
const { inputToken, inputTokenQuantity, outputToken, outputTokenQuantity, orderStatus } = details
const uniswapXOrderStatus = OrderStatusTable[orderStatus]
const { status, statusMessage, title } = OrderTextTable[uniswapXOrderStatus]
const descriptor = getSwapDescriptor({
tokenIn: inputToken,
inputAmount: inputTokenQuantity,
tokenOut: outputToken,
outputAmount: outputTokenQuantity,
})
return { return {
hash: details.hash, hash: signature.orderHash,
chainId: supportedChain, chainId: signature.chainId,
status, offchainOrderDetails: signature,
statusMessage, timestamp: activity.timestamp,
offchainOrderDetails: {
id: details.id,
type: swapOrderTypeToSignatureType(details.swapOrderType),
encodedOrder: details.encodedOrder,
txHash: details.hash,
orderHash: details.hash,
offerer: details.offerer,
chainId: supportedChain,
status: uniswapXOrderStatus,
addedTime: timestamp,
swapInfo: {
isUniswapXOrder: true,
type: LocalTransactionType.SWAP,
tradeType: TradeType.EXACT_INPUT,
inputCurrencyId: inputToken.address ?? '',
outputCurrencyId: outputToken.address ?? '',
inputCurrencyAmountRaw: parseUnits(inputTokenQuantity, inputToken.decimals).toString(),
expectedOutputCurrencyAmountRaw: parseUnits(outputTokenQuantity, outputToken.decimals).toString(),
minimumOutputCurrencyAmountRaw: parseUnits(outputTokenQuantity, outputToken.decimals).toString(),
settledOutputCurrencyAmountRaw: parseUnits(outputTokenQuantity, outputToken.decimals).toString(),
},
},
timestamp,
logos: [inputToken.project?.logo?.url, outputToken.project?.logo?.url], logos: [inputToken.project?.logo?.url, outputToken.project?.logo?.url],
currencies: [gqlToCurrency(inputToken), gqlToCurrency(outputToken)], currencies: [gqlToCurrency(inputToken), gqlToCurrency(outputToken)],
title, descriptor: getSwapDescriptor({
descriptor, tokenIn: inputToken,
from: details.offerer, inputAmount: inputTokenQuantity,
tokenOut: outputToken,
outputAmount: outputTokenQuantity,
}),
from: signature.offerer,
prefixIconSrc: UniswapXBolt, prefixIconSrc: UniswapXBolt,
...OrderTextTable[signature.status],
} }
} }
......
...@@ -16,6 +16,7 @@ export type Activity = { ...@@ -16,6 +16,7 @@ export type Activity = {
title: string title: string
descriptor?: string descriptor?: string
logos?: Array<string | undefined> logos?: Array<string | undefined>
// TODO(WEB-3839): replace Currency with CurrencyInfo
currencies?: Array<Currency | undefined> currencies?: Array<Currency | undefined>
otherAccount?: string otherAccount?: string
from: string from: string
......
import 'test-utils/tokens/mocks'
import { ChainId, WETH9 } from '@uniswap/sdk-core' import { ChainId, WETH9 } from '@uniswap/sdk-core'
import { Activity } from 'components/AccountDrawer/MiniPortfolio/Activity/types' import { Activity } from 'components/AccountDrawer/MiniPortfolio/Activity/types'
import { LimitDetailActivityRow } from 'components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow' import { LimitDetailActivityRow } from 'components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow'
...@@ -14,20 +16,6 @@ jest.mock('components/AccountDrawer/MiniPortfolio/formatTimestamp', () => { ...@@ -14,20 +16,6 @@ jest.mock('components/AccountDrawer/MiniPortfolio/formatTimestamp', () => {
} }
}) })
jest.mock('hooks/Tokens', () => {
return {
useCurrency: (address?: string) => {
if (address?.toLowerCase() === DAI.address.toLowerCase()) {
return DAI
}
if (address?.toLowerCase() === WETH9[ChainId.MAINNET].address.toLowerCase()) {
return WETH9[ChainId.MAINNET]
}
return undefined
},
}
})
const mockOrderDetails: UniswapXOrderDetails = { const mockOrderDetails: UniswapXOrderDetails = {
type: SignatureType.SIGN_LIMIT, type: SignatureType.SIGN_LIMIT,
orderHash: '0x1234', orderHash: '0x1234',
...@@ -60,6 +48,7 @@ const mockOrder: Activity = { ...@@ -60,6 +48,7 @@ const mockOrder: Activity = {
title: 'Limit pending', title: 'Limit pending',
from: '0x456', from: '0x456',
offchainOrderDetails: mockOrderDetails, offchainOrderDetails: mockOrderDetails,
currencies: [DAI, WETH9[ChainId.MAINNET]],
} }
describe('LimitDetailActivityRow', () => { describe('LimitDetailActivityRow', () => {
......
...@@ -9,7 +9,7 @@ import { FormatType, formatTimestamp } from 'components/AccountDrawer/MiniPortfo ...@@ -9,7 +9,7 @@ import { FormatType, formatTimestamp } from 'components/AccountDrawer/MiniPortfo
import Column from 'components/Column' import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { parseUnits } from 'ethers/lib/utils' import { parseUnits } from 'ethers/lib/utils'
import useTokenLogoSource from 'hooks/useAssetLogoSource' import { useCurrencyInfo } from 'hooks/Tokens'
import { useScreenSize } from 'hooks/useScreenSize' import { useScreenSize } from 'hooks/useScreenSize'
import { Trans } from 'i18n' import { Trans } from 'i18n'
import { Checkbox } from 'nft/components/layout/Checkbox' import { Checkbox } from 'nft/components/layout/Checkbox'
...@@ -50,7 +50,9 @@ const CircleLogoImage = styled.img<{ size: string }>` ...@@ -50,7 +50,9 @@ const CircleLogoImage = styled.img<{ size: string }>`
export function LimitDetailActivityRow({ order, onToggleSelect, selected }: LimitDetailActivityRowProps) { export function LimitDetailActivityRow({ order, onToggleSelect, selected }: LimitDetailActivityRowProps) {
const theme = useTheme() const theme = useTheme()
const { chainId, logos, currencies, offchainOrderDetails } = order const { logos, currencies, offchainOrderDetails } = order
const inputCurrencyInfo = useCurrencyInfo(currencies?.[0])
const outputCurrencyInfo = useCurrencyInfo(currencies?.[1])
const openOffchainActivityModal = useOpenOffchainActivityModal() const openOffchainActivityModal = useOpenOffchainActivityModal()
const { formatReviewSwapCurrencyAmount } = useFormatter() const { formatReviewSwapCurrencyAmount } = useFormatter()
const [hovered, setHovered] = useState(false) const [hovered, setHovered] = useState(false)
...@@ -70,19 +72,11 @@ export function LimitDetailActivityRow({ order, onToggleSelect, selected }: Limi ...@@ -70,19 +72,11 @@ export function LimitDetailActivityRow({ order, onToggleSelect, selected }: Limi
) )
}, [amounts?.inputAmount, amounts?.outputAmount, amountsDefined]) }, [amounts?.inputAmount, amounts?.outputAmount, amountsDefined])
const [inputLogoSrc, nextInputLogoSrc] = useTokenLogoSource({
address: currencies?.[0]?.wrapped.address,
chainId,
isNative: currencies?.[0]?.isNative,
})
const [outputLogoSrc, nextOutputLogoSrc2] = useTokenLogoSource({
address: currencies?.[1]?.wrapped.address,
chainId,
isNative: currencies?.[1]?.isNative,
})
if (!offchainOrderDetails || !amountsDefined) return null if (!offchainOrderDetails || !amountsDefined) return null
const inputLogo = logos?.[0] ?? inputCurrencyInfo?.logoUrl
const outputLogo = logos?.[1] ?? outputCurrencyInfo?.logoUrl
return ( return (
<Row onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)}> <Row onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)}>
<StyledPortfolioRow <StyledPortfolioRow
...@@ -99,12 +93,12 @@ export function LimitDetailActivityRow({ order, onToggleSelect, selected }: Limi ...@@ -99,12 +93,12 @@ export function LimitDetailActivityRow({ order, onToggleSelect, selected }: Limi
descriptor={ descriptor={
<Column> <Column>
<TradeSummaryContainer gap="xs" align="center"> <TradeSummaryContainer gap="xs" align="center">
<CircleLogoImage src={logos?.[0] ?? inputLogoSrc} size="16px" onError={nextInputLogoSrc} /> {inputLogo && <CircleLogoImage src={inputLogo} size="16px" />}
<ThemedText.SubHeader color="neutral1"> <ThemedText.SubHeader color="neutral1">
{formatReviewSwapCurrencyAmount(amounts.inputAmount)} {amounts.inputAmount.currency.symbol} {formatReviewSwapCurrencyAmount(amounts.inputAmount)} {amounts.inputAmount.currency.symbol}
</ThemedText.SubHeader> </ThemedText.SubHeader>
<ArrowRight color={theme.neutral1} size="12px" /> <ArrowRight color={theme.neutral1} size="12px" />
<CircleLogoImage src={logos?.[1] ?? outputLogoSrc} size="16px" onError={nextOutputLogoSrc2} /> {outputLogo && <CircleLogoImage src={outputLogo} size="16px" />}
<ThemedText.SubHeader color="neutral1"> <ThemedText.SubHeader color="neutral1">
{formatReviewSwapCurrencyAmount(amounts.outputAmount)} {amounts.outputAmount.currency.symbol} {formatReviewSwapCurrencyAmount(amounts.outputAmount)} {amounts.outputAmount.currency.symbol}
</ThemedText.SubHeader> </ThemedText.SubHeader>
......
import 'test-utils/tokens/mocks'
import { ChainId, WETH9 } from '@uniswap/sdk-core' import { ChainId, WETH9 } from '@uniswap/sdk-core'
import { useOpenLimitOrders } from 'components/AccountDrawer/MiniPortfolio/Activity/hooks' import { useOpenLimitOrders } from 'components/AccountDrawer/MiniPortfolio/Activity/hooks'
import { Activity } from 'components/AccountDrawer/MiniPortfolio/Activity/types' import { Activity } from 'components/AccountDrawer/MiniPortfolio/Activity/types'
...@@ -19,20 +21,6 @@ jest.mock('components/AccountDrawer/MiniPortfolio/formatTimestamp', () => ({ ...@@ -19,20 +21,6 @@ jest.mock('components/AccountDrawer/MiniPortfolio/formatTimestamp', () => ({
formatTimestamp: () => 'January 26, 2024 at 1:52PM', formatTimestamp: () => 'January 26, 2024 at 1:52PM',
})) }))
jest.mock('hooks/Tokens', () => {
return {
useCurrency: (address?: string) => {
if (address?.toLowerCase() === DAI.address.toLowerCase()) {
return DAI
}
if (address?.toLowerCase() === WETH9[ChainId.MAINNET].address.toLowerCase()) {
return WETH9[ChainId.MAINNET]
}
return undefined
},
}
})
const mockOrderDetails: UniswapXOrderDetails = { const mockOrderDetails: UniswapXOrderDetails = {
type: SignatureType.SIGN_LIMIT, type: SignatureType.SIGN_LIMIT,
orderHash: '0x1234', orderHash: '0x1234',
......
...@@ -117,7 +117,7 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = ` ...@@ -117,7 +117,7 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = `
} }
.c14 { .c14 {
border-color: #22222212; border-color: #CECECE;
display: inline-block; display: inline-block;
margin-right: 1px; margin-right: 1px;
border-radius: 4px; border-radius: 4px;
...@@ -217,6 +217,7 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = ` ...@@ -217,6 +217,7 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = `
> >
<img <img
class="c9" class="c9"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png"
/> />
<div <div
class="c10 css-n8z49y" class="c10 css-n8z49y"
...@@ -248,6 +249,7 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = ` ...@@ -248,6 +249,7 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = `
</svg> </svg>
<img <img
class="c9" class="c9"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
/> />
<div <div
class="c10 css-n8z49y" class="c10 css-n8z49y"
......
...@@ -58,7 +58,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -58,7 +58,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
letter-spacing: -0.01em; letter-spacing: -0.01em;
} }
.c21 { .c20 {
-webkit-letter-spacing: -0.01em; -webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em; -moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em; -ms-letter-spacing: -0.01em;
...@@ -146,7 +146,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -146,7 +146,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
cursor: pointer; cursor: pointer;
} }
.c22 { .c21 {
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
display: -ms-flexbox; display: -ms-flexbox;
...@@ -161,8 +161,8 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -161,8 +161,8 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
line-height: 1; line-height: 1;
} }
.c24 { .c23 {
border-color: #22222212; border-color: #CECECE;
display: inline-block; display: inline-block;
margin-right: 1px; margin-right: 1px;
border-radius: 4px; border-radius: 4px;
...@@ -178,15 +178,15 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -178,15 +178,15 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
transition-duration: 125ms; transition-duration: 125ms;
} }
.c24:hover { .c23:hover {
opacity: 0.6; opacity: 0.6;
} }
.c24:active { .c23:active {
opacity: 0.4; opacity: 0.4;
} }
.c25 { .c24 {
position: absolute; position: absolute;
top: -24px; top: -24px;
-webkit-clip: rect(0 0 0 0); -webkit-clip: rect(0 0 0 0);
...@@ -200,7 +200,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -200,7 +200,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
width: 1px; width: 1px;
} }
.c26 { .c25 {
display: none; display: none;
height: 18px; height: 18px;
width: 18px; width: 18px;
...@@ -217,7 +217,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -217,7 +217,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.c23 { .c22 {
opacity: 0; opacity: 0;
} }
...@@ -228,12 +228,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -228,12 +228,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.c20 {
width: 16px;
height: 16px;
border-radius: 50%;
}
.c1 { .c1 {
width: 100%; width: 100%;
overflow: auto; overflow: auto;
...@@ -410,9 +404,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -410,9 +404,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
<div <div
class="c13 c18 c19" class="c13 c18 c19"
> >
<img
class="c20"
/>
<div <div
class="c5 css-n8z49y" class="c5 css-n8z49y"
> >
...@@ -441,9 +432,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -441,9 +432,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
points="12 5 19 12 12 19" points="12 5 19 12 12 19"
/> />
</svg> </svg>
<img
class="c20"
/>
<div <div
class="c5 css-n8z49y" class="c5 css-n8z49y"
> >
...@@ -453,7 +441,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -453,7 +441,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
</div> </div>
</div> </div>
<div <div
class="c21 css-142zc9n" class="c20 css-142zc9n"
> >
when 0.00042 WETH/DAI when 0.00042 WETH/DAI
</div> </div>
...@@ -461,20 +449,20 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -461,20 +449,20 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
</div> </div>
</div> </div>
<label <label
class="c22 c23" class="c21 c22"
> >
<span <span
aria-hidden="true" aria-hidden="true"
class="c24" class="c23"
size="18" size="18"
/> />
<input <input
class="c25" class="c24"
size="18" size="18"
type="checkbox" type="checkbox"
/> />
<svg <svg
class="c26" class="c25"
fill="none" fill="none"
height="16" height="16"
size="18" size="18"
...@@ -514,9 +502,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -514,9 +502,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
<div <div
class="c13 c18 c19" class="c13 c18 c19"
> >
<img
class="c20"
/>
<div <div
class="c5 css-n8z49y" class="c5 css-n8z49y"
> >
...@@ -545,9 +530,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -545,9 +530,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
points="12 5 19 12 12 19" points="12 5 19 12 12 19"
/> />
</svg> </svg>
<img
class="c20"
/>
<div <div
class="c5 css-n8z49y" class="c5 css-n8z49y"
> >
...@@ -557,7 +539,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -557,7 +539,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
</div> </div>
</div> </div>
<div <div
class="c21 css-142zc9n" class="c20 css-142zc9n"
> >
when 0.00042 WETH/DAI when 0.00042 WETH/DAI
</div> </div>
...@@ -565,20 +547,20 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` ...@@ -565,20 +547,20 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
</div> </div>
</div> </div>
<label <label
class="c22 c23" class="c21 c22"
> >
<span <span
aria-hidden="true" aria-hidden="true"
class="c24" class="c23"
size="18" size="18"
/> />
<input <input
class="c25" class="c24"
size="18" size="18"
type="checkbox" type="checkbox"
/> />
<svg <svg
class="c26" class="c25"
fill="none" fill="none"
height="16" height="16"
size="18" size="18"
...@@ -663,7 +645,7 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -663,7 +645,7 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
letter-spacing: -0.01em; letter-spacing: -0.01em;
} }
.c21 { .c20 {
-webkit-letter-spacing: -0.01em; -webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em; -moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em; -ms-letter-spacing: -0.01em;
...@@ -751,7 +733,7 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -751,7 +733,7 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
cursor: pointer; cursor: pointer;
} }
.c22 { .c21 {
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
display: -ms-flexbox; display: -ms-flexbox;
...@@ -766,8 +748,8 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -766,8 +748,8 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
line-height: 1; line-height: 1;
} }
.c24 { .c23 {
border-color: #22222212; border-color: #CECECE;
display: inline-block; display: inline-block;
margin-right: 1px; margin-right: 1px;
border-radius: 4px; border-radius: 4px;
...@@ -783,15 +765,15 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -783,15 +765,15 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
transition-duration: 125ms; transition-duration: 125ms;
} }
.c24:hover { .c23:hover {
opacity: 0.6; opacity: 0.6;
} }
.c24:active { .c23:active {
opacity: 0.4; opacity: 0.4;
} }
.c25 { .c24 {
position: absolute; position: absolute;
top: -24px; top: -24px;
-webkit-clip: rect(0 0 0 0); -webkit-clip: rect(0 0 0 0);
...@@ -805,7 +787,7 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -805,7 +787,7 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
width: 1px; width: 1px;
} }
.c26 { .c25 {
display: none; display: none;
height: 18px; height: 18px;
width: 18px; width: 18px;
...@@ -822,7 +804,7 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -822,7 +804,7 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.c23 { .c22 {
opacity: 0; opacity: 0;
} }
...@@ -833,12 +815,6 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -833,12 +815,6 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.c20 {
width: 16px;
height: 16px;
border-radius: 50%;
}
.c1 { .c1 {
width: 100%; width: 100%;
overflow: auto; overflow: auto;
...@@ -1015,9 +991,6 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -1015,9 +991,6 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
<div <div
class="c13 c18 c19" class="c13 c18 c19"
> >
<img
class="c20"
/>
<div <div
class="c5 css-n8z49y" class="c5 css-n8z49y"
> >
...@@ -1046,9 +1019,6 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -1046,9 +1019,6 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
points="12 5 19 12 12 19" points="12 5 19 12 12 19"
/> />
</svg> </svg>
<img
class="c20"
/>
<div <div
class="c5 css-n8z49y" class="c5 css-n8z49y"
> >
...@@ -1058,7 +1028,7 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -1058,7 +1028,7 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
</div> </div>
</div> </div>
<div <div
class="c21 css-142zc9n" class="c20 css-142zc9n"
> >
when 0.00042 WETH/DAI when 0.00042 WETH/DAI
</div> </div>
...@@ -1066,20 +1036,20 @@ exports[`LimitsMenu should render when there is one open order 1`] = ` ...@@ -1066,20 +1036,20 @@ exports[`LimitsMenu should render when there is one open order 1`] = `
</div> </div>
</div> </div>
<label <label
class="c22 c23" class="c21 c22"
> >
<span <span
aria-hidden="true" aria-hidden="true"
class="c24" class="c23"
size="18" size="18"
/> />
<input <input
class="c25" class="c24"
size="18" size="18"
type="checkbox" type="checkbox"
/> />
<svg <svg
class="c26" class="c25"
fill="none" fill="none"
height="16" height="16"
size="18" size="18"
......
...@@ -16,9 +16,9 @@ import { ThemedText } from 'theme/components' ...@@ -16,9 +16,9 @@ import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { ExpandoRow } from '../ExpandoRow' import { ExpandoRow } from '../ExpandoRow'
import { useToggleAccountDrawer } from '../hooks'
import { PortfolioLogo } from '../PortfolioLogo' import { PortfolioLogo } from '../PortfolioLogo'
import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow' import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
import { useToggleAccountDrawer } from '../hooks'
import { PositionInfo } from './cache' import { PositionInfo } from './cache'
import { useFeeValues } from './hooks' import { useFeeValues } from './hooks'
import useMultiChainPositions from './useMultiChainPositions' import useMultiChainPositions from './useMultiChainPositions'
......
import 'test-utils/tokens/mocks'
import { ChainId } from '@uniswap/sdk-core' import { ChainId } from '@uniswap/sdk-core'
import { DAI, DAI_ARBITRUM_ONE, USDC_ARBITRUM, USDC_MAINNET } from 'constants/tokens' import { DAI, DAI_ARBITRUM_ONE, USDC_ARBITRUM, USDC_MAINNET } from 'constants/tokens'
import { render } from 'test-utils/render' import { render } from 'test-utils/render'
......
...@@ -4,7 +4,7 @@ import { ReactComponent as UnknownStatus } from 'assets/svg/contract-interaction ...@@ -4,7 +4,7 @@ import { ReactComponent as UnknownStatus } from 'assets/svg/contract-interaction
import { MissingImageLogo } from 'components/Logo/AssetLogo' import { MissingImageLogo } from 'components/Logo/AssetLogo'
import { ChainLogo, getDefaultBorderRadius } from 'components/Logo/ChainLogo' import { ChainLogo, getDefaultBorderRadius } from 'components/Logo/ChainLogo'
import { Unicon } from 'components/Unicon' import { Unicon } from 'components/Unicon'
import useTokenLogoSource from 'hooks/useAssetLogoSource' import { useCurrencyInfo } from 'hooks/Tokens'
import useENSAvatar from 'hooks/useENSAvatar' import useENSAvatar from 'hooks/useENSAvatar'
import React from 'react' import React from 'react'
import { Loader } from 'react-feather' import { Loader } from 'react-feather'
...@@ -12,8 +12,8 @@ import styled from 'styled-components' ...@@ -12,8 +12,8 @@ import styled from 'styled-components'
import { useIsDarkMode } from 'theme/components/ThemeToggle' import { useIsDarkMode } from 'theme/components/ThemeToggle'
import { UniconV2 } from 'ui/src/components/UniconV2' import { UniconV2 } from 'ui/src/components/UniconV2'
import { useLogolessColorScheme } from 'ui/src/utils/colors' import { useLogolessColorScheme } from 'ui/src/utils/colors'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
const UnknownContract = styled(UnknownStatus)` const UnknownContract = styled(UnknownStatus)`
color: ${({ theme }) => theme.neutral2}; color: ${({ theme }) => theme.neutral2};
...@@ -94,29 +94,19 @@ function DoubleLogo({ logo1, onError1, logo2, onError2, size }: DoubleLogoProps) ...@@ -94,29 +94,19 @@ function DoubleLogo({ logo1, onError1, logo2, onError2, size }: DoubleLogoProps)
interface DoubleCurrencyLogoProps { interface DoubleCurrencyLogoProps {
chainId: ChainId chainId: ChainId
currencies: Array<Currency | undefined> currencies: Array<Currency | undefined>
images?: Array<string | undefined> backupImages?: Array<string | undefined>
size: string size: string
} }
function DoubleCurrencyLogo({ chainId, currencies, images, size }: DoubleCurrencyLogoProps) { function DoubleCurrencyLogo({ currencies, size, backupImages }: DoubleCurrencyLogoProps) {
const [src, nextSrc] = useTokenLogoSource({ const currencyInfos = [useCurrencyInfo(currencies?.[0]), useCurrencyInfo(currencies?.[1])]
address: currencies?.[0]?.wrapped.address, if (currencies.length === 1 && currencyInfos[0]?.logoUrl) {
chainId, return <CircleLogoImage size={size} src={currencyInfos[0].logoUrl} />
isNative: currencies?.[0]?.isNative,
primaryImg: images?.[0],
})
const [src2, nextSrc2] = useTokenLogoSource({
address: currencies?.[1]?.wrapped.address,
chainId,
isNative: currencies?.[1]?.isNative,
primaryImg: images?.[1],
})
if (currencies.length === 1 && src) {
return <CircleLogoImage size={size} src={src} onError={nextSrc} />
} }
if (currencies.length > 1) { const logo1 = currencyInfos[0]?.logoUrl ?? backupImages?.[0]
return <DoubleLogo logo1={src} onError1={nextSrc} logo2={src2} onError2={nextSrc2} size={size} /> const logo2 = currencyInfos[1]?.logoUrl ?? backupImages?.[1]
if (currencies.length > 1 && (logo1 || logo2)) {
return <DoubleLogo logo1={logo1} logo2={logo2} size={size} />
} }
return <LogolessPlaceholder currency={currencies?.[0]} size={size} /> return <LogolessPlaceholder currency={currencies?.[0]} size={size} />
} }
...@@ -191,7 +181,7 @@ function getLogo({ chainId, accountAddress, currencies, images, size = '40px' }: ...@@ -191,7 +181,7 @@ function getLogo({ chainId, accountAddress, currencies, images, size = '40px' }:
return <PortfolioAvatar accountAddress={accountAddress} size={size} /> return <PortfolioAvatar accountAddress={accountAddress} size={size} />
} }
if (currencies && currencies.length) { if (currencies && currencies.length) {
return <DoubleCurrencyLogo chainId={chainId} currencies={currencies} images={images} size={size} /> return <DoubleCurrencyLogo chainId={chainId} currencies={currencies} size={size} />
} }
if (images?.length === 1) { if (images?.length === 1) {
return <CircleLogoImage size={size} src={images[0] ?? blankTokenUrl} /> return <CircleLogoImage size={size} src={images[0] ?? blankTokenUrl} />
......
...@@ -105,14 +105,7 @@ function TokenRow({ ...@@ -105,14 +105,7 @@ function TokenRow({
properties={{ chain_id: currency.chainId, token_name: token?.name, address: token?.address }} properties={{ chain_id: currency.chainId, token_name: token?.name, address: token?.address }}
> >
<PortfolioRow <PortfolioRow
left={ left={<PortfolioLogo chainId={currency.chainId} currencies={[currency]} size="40px" />}
<PortfolioLogo
chainId={currency.chainId}
currencies={[currency]}
images={[tokenProjectMarket?.tokenProject.logoUrl]}
size="40px"
/>
}
title={<TokenNameText>{token?.name}</TokenNameText>} title={<TokenNameText>{token?.name}</TokenNameText>}
descriptor={ descriptor={
<TokenBalanceText> <TokenBalanceText>
......
import { t } from 'i18n' import { t } from 'i18n'
import { TransactionType } from 'state/transactions/types' import { TransactionType } from 'state/transactions/types'
import { UniswapXOrderStatus } from 'types/uniswapx' import { UniswapXOrderStatus } from 'types/uniswapx'
import { import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
SwapOrderStatus,
TransactionStatus,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
// use even number because rows are in groups of 2 // use even number because rows are in groups of 2
export const DEFAULT_NFT_QUERY_AMOUNT = 26 export const DEFAULT_NFT_QUERY_AMOUNT = 26
...@@ -269,13 +266,3 @@ export const MOONPAY_SENDER_ADDRESSES = [ ...@@ -269,13 +266,3 @@ export const MOONPAY_SENDER_ADDRESSES = [
'0xb287eac48ab21c5fb1d3723830d60b4c797555b0', '0xb287eac48ab21c5fb1d3723830d60b4c797555b0',
'0xd108fd0e8c8e71552a167e7a44ff1d345d233ba6', '0xd108fd0e8c8e71552a167e7a44ff1d345d233ba6',
] ]
// Converts GQL backend orderStatus enum to the enum used by the frontend and UniswapX backend
export const OrderStatusTable: { [key in SwapOrderStatus]: UniswapXOrderStatus } = {
[SwapOrderStatus.Open]: UniswapXOrderStatus.OPEN,
[SwapOrderStatus.Expired]: UniswapXOrderStatus.EXPIRED,
[SwapOrderStatus.Error]: UniswapXOrderStatus.ERROR,
[SwapOrderStatus.InsufficientFunds]: UniswapXOrderStatus.INSUFFICIENT_FUNDS,
[SwapOrderStatus.Filled]: UniswapXOrderStatus.FILLED,
[SwapOrderStatus.Cancelled]: UniswapXOrderStatus.CANCELLED,
}
...@@ -9,8 +9,8 @@ import { ChevronRight } from 'react-feather' ...@@ -9,8 +9,8 @@ import { ChevronRight } from 'react-feather'
import styled from 'styled-components' import styled from 'styled-components'
import { ClickableStyle, ThemedText } from 'theme/components' import { ClickableStyle, ThemedText } from 'theme/components'
import ThemeToggle from 'theme/components/ThemeToggle' import ThemeToggle from 'theme/components/ThemeToggle'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { AnalyticsToggle } from './AnalyticsToggle' import { AnalyticsToggle } from './AnalyticsToggle'
import { GitVersionRow } from './GitVersionRow' import { GitVersionRow } from './GitVersionRow'
import { LanguageMenuItems } from './LanguageMenu' import { LanguageMenuItems } from './LanguageMenu'
......
...@@ -2,31 +2,37 @@ import Column from 'components/Column' ...@@ -2,31 +2,37 @@ import Column from 'components/Column'
import { ENS } from 'components/Icons/ENS' import { ENS } from 'components/Icons/ENS'
import { EthMini } from 'components/Icons/EthMini' import { EthMini } from 'components/Icons/EthMini'
import StatusIcon from 'components/Identicon/StatusIcon' import StatusIcon from 'components/Identicon/StatusIcon'
import Popover from 'components/Popover'
import Row from 'components/Row' import Row from 'components/Row'
import { Connection } from 'connection/types' import { Connection } from 'connection/types'
import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useRef, useState } from 'react' import { useRef, useState } from 'react'
import { MoreHorizontal } from 'react-feather' import { MoreHorizontal } from 'react-feather'
import styled from 'styled-components' import styled from 'styled-components'
import { ClickableStyle, CopyHelper, ThemedText } from 'theme/components' import { ClickableStyle, CopyHelper, EllipsisStyle, ThemedText } from 'theme/components'
import { Unitag } from 'ui/src/components/icons/Unitag' import { Unitag } from 'ui/src/components/icons/Unitag'
import { shortenAddress } from 'utilities/src/addresses' import { shortenAddress } from 'utilities/src/addresses'
const Container = styled.div` const Container = styled.div`
display: inline-block; display: flex;
width: 70%;
max-width: 70%;
padding-right: 8px; padding-right: 8px;
display: inline-flex;
` `
const Identifiers = styled.div` const Identifiers = styled.div`
white-space: nowrap; white-space: nowrap;
display: flex; display: flex;
width: 100%;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
margin-left: 8px; margin-left: 8px;
user-select: none; user-select: none;
overflow: hidden;
flex: 1 1 auto;
`
const IdentifierText = styled.span`
${EllipsisStyle}
max-width: 120px;
@media screen and (min-width: 1440px) {
max-width: 180px;
}
` `
const SecondaryIdentifiersContainer = styled(Row)` const SecondaryIdentifiersContainer = styled(Row)`
position: relative; position: relative;
...@@ -35,18 +41,15 @@ const SecondaryIdentifiersContainer = styled(Row)` ...@@ -35,18 +41,15 @@ const SecondaryIdentifiersContainer = styled(Row)`
display: inline-block; display: inline-block;
} }
` `
const MoreIcon = styled(MoreHorizontal)<{ $isActive: boolean }>` const MoreIcon = styled(MoreHorizontal)`
height: 16px; height: 16px;
width: 16px; width: 16px;
color: ${({ theme }) => theme.neutral2}; color: ${({ theme }) => theme.neutral2};
cursor: pointer; cursor: pointer;
display: ${({ $isActive }) => !$isActive && 'none'};
${ClickableStyle} ${ClickableStyle}
` `
const Dropdown = styled(Column)` const Dropdown = styled(Column)`
width: 240px; width: 240px;
position: absolute;
top: 20px;
gap: 2px; gap: 2px;
padding: 8px; padding: 8px;
border-radius: 20px; border-radius: 20px;
...@@ -97,14 +100,19 @@ function SecondaryIdentifiers({ ...@@ -97,14 +100,19 @@ function SecondaryIdentifiers({
<SecondaryIdentifiersContainer data-testid="secondary-identifiers" ref={ref}> <SecondaryIdentifiersContainer data-testid="secondary-identifiers" ref={ref}>
<Row onClick={() => setIsDropdownOpen(!isDropdownOpen)} gap="8px"> <Row onClick={() => setIsDropdownOpen(!isDropdownOpen)} gap="8px">
<ThemedText.BodySmall color="neutral2">{shortenAddress(account)}</ThemedText.BodySmall> <ThemedText.BodySmall color="neutral2">{shortenAddress(account)}</ThemedText.BodySmall>
<MoreIcon id="more-identifiers-icon" $isActive={isDropdownOpen} /> <Popover
show={isDropdownOpen}
placement="bottom"
content={
<Dropdown data-testid="secondary-identifiers-dropdown">
<SecondaryIdentifier Icon={EnsIcon} displayValue={ensUsername} copyValue={ensUsername} />
<SecondaryIdentifier Icon={EthMini} displayValue={shortenAddress(account)} copyValue={account} />
</Dropdown>
}
>
<MoreIcon id="more-identifiers-icon" />
</Popover>
</Row> </Row>
{isDropdownOpen && (
<Dropdown>
<SecondaryIdentifier Icon={EnsIcon} displayValue={ensUsername} copyValue={ensUsername} />
<SecondaryIdentifier Icon={EthMini} displayValue={shortenAddress(account)} copyValue={account} />
</Dropdown>
)}
</SecondaryIdentifiersContainer> </SecondaryIdentifiersContainer>
) )
} }
...@@ -141,7 +149,7 @@ export function Status({ ...@@ -141,7 +149,7 @@ export function Status({
toCopy={uniswapUsername ? uniswapUsername + '.uni.eth' : ensUsername ? ensUsername : account} toCopy={uniswapUsername ? uniswapUsername + '.uni.eth' : ensUsername ? ensUsername : account}
> >
<Row gap="2px"> <Row gap="2px">
{uniswapUsername ?? ensUsername ?? shortenAddress(account)} <IdentifierText>{uniswapUsername ?? ensUsername ?? shortenAddress(account)}</IdentifierText>
{uniswapUsername && <Unitag size={18} />} {uniswapUsername && <Unitag size={18} />}
</Row> </Row>
</CopyHelper> </CopyHelper>
......
...@@ -62,6 +62,7 @@ export const Scrim = (props: ScrimBackgroundProps) => { ...@@ -62,6 +62,7 @@ export const Scrim = (props: ScrimBackgroundProps) => {
const AccountDrawerScrollWrapper = styled.div` const AccountDrawerScrollWrapper = styled.div`
overflow-y: auto; overflow-y: auto;
overflow-x: hidden;
${ScrollBarStyles} ${ScrollBarStyles}
...@@ -111,10 +112,12 @@ const AccountDrawerWrapper = styled.div<{ open: boolean }>` ...@@ -111,10 +112,12 @@ const AccountDrawerWrapper = styled.div<{ open: boolean }>`
@media screen and (min-width: 1440px) { @media screen and (min-width: 1440px) {
margin-right: ${({ open }) => (open ? 0 : `-${DRAWER_WIDTH_XL}`)}; margin-right: ${({ open }) => (open ? 0 : `-${DRAWER_WIDTH_XL}`)};
width: ${DRAWER_WIDTH_XL}; width: ${DRAWER_WIDTH_XL};
max-width: ${DRAWER_WIDTH_XL};
} }
border-radius: 12px; border-radius: 12px;
width: ${DRAWER_WIDTH}; width: ${DRAWER_WIDTH};
max-width: ${DRAWER_WIDTH};
font-size: 16px; font-size: 16px;
background-color: ${({ theme }) => theme.surface1}; background-color: ${({ theme }) => theme.surface1};
border: ${({ theme }) => `1px solid ${theme.surface3}`}; border: ${({ theme }) => `1px solid ${theme.surface3}`};
......
...@@ -16,7 +16,8 @@ const IconContainer = styled.div` ...@@ -16,7 +16,8 @@ const IconContainer = styled.div`
` `
const IconBackground = styled.div` const IconBackground = styled.div`
background-color: #1f1e02; display: flex;
background-color: ${({ theme }) => theme.deprecated_accentWarningSoft};
padding: 10px; padding: 10px;
border-radius: 12px; border-radius: 12px;
` `
......
...@@ -2,6 +2,7 @@ import { formatTickMarks } from 'components/Charts/utils' ...@@ -2,6 +2,7 @@ import { formatTickMarks } from 'components/Charts/utils'
import Row from 'components/Row' import Row from 'components/Row'
import { MissingDataBars } from 'components/Table/icons' import { MissingDataBars } from 'components/Table/icons'
import { useActiveLocale } from 'hooks/useActiveLocale' import { useActiveLocale } from 'hooks/useActiveLocale'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useScreenSize } from 'hooks/useScreenSize' import { useScreenSize } from 'hooks/useScreenSize'
import { Trans } from 'i18n' import { Trans } from 'i18n'
import { useUpdateAtom } from 'jotai/utils' import { useUpdateAtom } from 'jotai/utils'
...@@ -279,6 +280,8 @@ export function Chart<TParamType extends ChartDataParams<TDataType>, TDataType e ...@@ -279,6 +280,8 @@ export function Chart<TParamType extends ChartDataParams<TDataType>, TDataType e
} }
}, [setRefitChartContent]) }, [setRefitChartContent])
useOnClickOutside({ current: chartDivElement } as React.RefObject<HTMLDivElement>, () => setCrosshairData(undefined))
return ( return (
<ChartDiv <ChartDiv
ref={setChartDivElement} ref={setChartDivElement}
......
import 'test-utils/tokens/mocks'
import { LIMIT_ORDER_TRADE, TEST_TRADE_EXACT_INPUT } from 'test-utils/constants' import { LIMIT_ORDER_TRADE, TEST_TRADE_EXACT_INPUT } from 'test-utils/constants'
import { render, screen } from 'test-utils/render' import { render, screen } from 'test-utils/render'
......
import 'test-utils/tokens/mocks'
import { ChainId, WETH9 } from '@uniswap/sdk-core' import { ChainId, WETH9 } from '@uniswap/sdk-core'
import { Pending } from 'components/ConfirmSwapModal/Pending' import { Pending } from 'components/ConfirmSwapModal/Pending'
import { BigNumber } from 'ethers/lib/ethers' import { BigNumber } from 'ethers/lib/ethers'
......
...@@ -69,7 +69,7 @@ describe('LimitPriceInputPanel', () => { ...@@ -69,7 +69,7 @@ describe('LimitPriceInputPanel', () => {
</LimitContext.Provider> </LimitContext.Provider>
</SwapAndLimitContext.Provider> </SwapAndLimitContext.Provider>
) )
expect(screen.getByText('DAI')).toBeVisible() expect(screen.getAllByText('DAI')).toHaveLength(2) // subheader and token symbol
expect(screen.getByPlaceholderText('0')).toBeVisible() expect(screen.getByPlaceholderText('0')).toBeVisible()
}) })
...@@ -90,7 +90,7 @@ describe('LimitPriceInputPanel', () => { ...@@ -90,7 +90,7 @@ describe('LimitPriceInputPanel', () => {
</LimitContext.Provider> </LimitContext.Provider>
</SwapAndLimitContext.Provider> </SwapAndLimitContext.Provider>
) )
expect(screen.getByText('DAI')).toBeVisible() // subheader expect(screen.getAllByText('DAI')).toHaveLength(2) // subheader and token symbol
expect(container.querySelector('.token-symbol-container')).toHaveTextContent('USDC') expect(container.querySelector('.token-symbol-container')).toHaveTextContent('USDC')
expect(screen.getByPlaceholderText('0')).toBeVisible() expect(screen.getByPlaceholderText('0')).toBeVisible()
}) })
......
...@@ -6,13 +6,13 @@ import { PropsWithChildren, ReactNode } from 'react' ...@@ -6,13 +6,13 @@ import { PropsWithChildren, ReactNode } from 'react'
import { X } from 'react-feather' import { X } from 'react-feather'
import { useModalIsOpen, useToggleFeatureFlags } from 'state/application/hooks' import { useModalIsOpen, useToggleFeatureFlags } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer' import { ApplicationModal } from 'state/application/reducer'
import { Statsig } from 'statsig-react'
import styled from 'styled-components' import styled from 'styled-components'
import { BREAKPOINTS } from 'theme' import { BREAKPOINTS } from 'theme'
import { Z_INDEX } from 'theme/zIndex' import { Z_INDEX } from 'theme/zIndex'
import { DynamicConfigs, getConfigName } from 'uniswap/src/features/experiments/configs' import { DynamicConfigs, getConfigName } from 'uniswap/src/features/statsig/configs'
import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/experiments/flags' import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlagWithExposureLoggingDisabled } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlagWithExposureLoggingDisabled } from 'uniswap/src/features/statsig/hooks'
import { Statsig } from 'uniswap/src/features/statsig/sdk/statsig'
const StyledModal = styled.div` const StyledModal = styled.div`
position: fixed; position: fixed;
......
...@@ -7,8 +7,8 @@ import styled from 'styled-components' ...@@ -7,8 +7,8 @@ import styled from 'styled-components'
import { useIsDarkMode } from 'theme/components/ThemeToggle' import { useIsDarkMode } from 'theme/components/ThemeToggle'
import { flexColumnNoWrap } from 'theme/styles' import { flexColumnNoWrap } from 'theme/styles'
import { UniconV2 } from 'ui/src/components/UniconV2' import { UniconV2 } from 'ui/src/components/UniconV2'
import { FeatureFlags } from 'uniswap/src/features/experiments/flags' import { FeatureFlags } from 'uniswap/src/features/statsig/flags'
import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks'
import { useUnitagByAddressWithoutFlag } from 'uniswap/src/features/unitags/hooksWithoutFlags' import { useUnitagByAddressWithoutFlag } from 'uniswap/src/features/unitags/hooksWithoutFlags'
import { getWalletMeta } from 'utils/walletMeta' import { getWalletMeta } from 'utils/walletMeta'
import sockImg from '../../assets/svg/socks.svg' import sockImg from '../../assets/svg/socks.svg'
...@@ -98,7 +98,7 @@ const MiniWalletIcon = ({ connection, side }: { connection: Connection; side: 'l ...@@ -98,7 +98,7 @@ const MiniWalletIcon = ({ connection, side }: { connection: Connection; side: 'l
const MainWalletIcon = ({ account, connection, size }: { account: string; connection: Connection; size: number }) => { const MainWalletIcon = ({ account, connection, size }: { account: string; connection: Connection; size: number }) => {
const { unitag } = useUnitagByAddressWithoutFlag(account, Boolean(account)) const { unitag } = useUnitagByAddressWithoutFlag(account, Boolean(account))
const { avatar } = useENSAvatar(account ?? undefined) const { avatar } = useENSAvatar(account)
const uniconV2Enabled = useFeatureFlag(FeatureFlags.UniconsV2) const uniconV2Enabled = useFeatureFlag(FeatureFlags.UniconsV2)
if (!account) return null if (!account) return null
......
...@@ -18,7 +18,7 @@ const StyledAvatar = styled.img` ...@@ -18,7 +18,7 @@ const StyledAvatar = styled.img`
` `
export default function Identicon({ account, size }: { account: string; size?: number }) { export default function Identicon({ account, size }: { account: string; size?: number }) {
const { avatar } = useENSAvatar(account ?? undefined) const { avatar } = useENSAvatar(account)
const [fetchable, setFetchable] = useState(true) const [fetchable, setFetchable] = useState(true)
const iconSize = size ?? 24 const iconSize = size ?? 24
......
...@@ -36,13 +36,7 @@ const LogoContainer = styled.div` ...@@ -36,13 +36,7 @@ const LogoContainer = styled.div`
/** /**
* Renders an image by prioritizing a list of sources, and then eventually a fallback triangle alert * Renders an image by prioritizing a list of sources, and then eventually a fallback triangle alert
*/ */
export default function AssetLogo({ export default function AssetLogo({ currency, chainId = ChainId.MAINNET, size = '24px', style }: AssetLogoProps) {
currency,
chainId = ChainId.MAINNET,
size = '24px',
style,
}: AssetLogoProps) {
return ( return (
<LogoContainer style={{ height: size, width: size, ...style }}> <LogoContainer style={{ height: size, width: size, ...style }}>
<PortfolioLogo currencies={currency ? [currency] : []} size={size} chainId={chainId} /> <PortfolioLogo currencies={currency ? [currency] : []} size={size} chainId={chainId} />
......
...@@ -4,8 +4,8 @@ import { SearchToken } from 'graphql/data/SearchTokens' ...@@ -4,8 +4,8 @@ import { SearchToken } from 'graphql/data/SearchTokens'
import { TokenQueryData } from 'graphql/data/Token' import { TokenQueryData } from 'graphql/data/Token'
import { TopToken } from 'graphql/data/TopTokens' import { TopToken } from 'graphql/data/TopTokens'
import { gqlToCurrency, supportedChainIdFromGQLChain } from 'graphql/data/util' import { gqlToCurrency, supportedChainIdFromGQLChain } from 'graphql/data/util'
import { useMemo } from 'react'
import { useMemo } from 'react'
import { AssetLogoBaseProps } from './AssetLogo' import { AssetLogoBaseProps } from './AssetLogo'
export default function QueryTokenLogo( export default function QueryTokenLogo(
......
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.
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