ci(release): publish latest release

parent 4fdcca60
This source diff could not be displayed because it is too large. You can view the blob instead.
IPFS hash of the deployment:
- CIDv0: `QmbvpQZnmawF3Fj7MkSmPaiDXGaEgbnHUsHPcdphF2zdQS`
- CIDv1: `bafybeigj5csihxv2gcyb4r2jaceaco7bmkltzh6krxveckivxp7hzto6s4`
- CIDv0: `QmV8dNhZZhwARbFbRwX84c1qD5g1ByxuGSxegFdt4iLGWD`
- CIDv1: `bafybeide5vkgujdy6vmvh2hzpkepm3lvyo3b7ed4vahlsf5nemuzjc3n4i`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
......@@ -10,66 +10,47 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeigj5csihxv2gcyb4r2jaceaco7bmkltzh6krxveckivxp7hzto6s4.ipfs.dweb.link/
- https://bafybeigj5csihxv2gcyb4r2jaceaco7bmkltzh6krxveckivxp7hzto6s4.ipfs.cf-ipfs.com/
- [ipfs://QmbvpQZnmawF3Fj7MkSmPaiDXGaEgbnHUsHPcdphF2zdQS/](ipfs://QmbvpQZnmawF3Fj7MkSmPaiDXGaEgbnHUsHPcdphF2zdQS/)
- https://bafybeide5vkgujdy6vmvh2hzpkepm3lvyo3b7ed4vahlsf5nemuzjc3n4i.ipfs.dweb.link/
- https://bafybeide5vkgujdy6vmvh2hzpkepm3lvyo3b7ed4vahlsf5nemuzjc3n4i.ipfs.cf-ipfs.com/
- [ipfs://QmV8dNhZZhwARbFbRwX84c1qD5g1ByxuGSxegFdt4iLGWD/](ipfs://QmV8dNhZZhwARbFbRwX84c1qD5g1ByxuGSxegFdt4iLGWD/)
## 5.18.0 (2024-03-13)
## 5.19.0 (2024-03-18)
### Features
* **web:** [info] correctly identify buy/sell txs on TDP (#6715) 2c2f316
* **web:** [info] correctly sort 1 day % change at different time intervals (#6703) 28e9f57
* **web:** [info] Unwrap Natives on Info Pages (#6655) 7f4164d
* **web:** [info] wrap error text in charts (#6705) c02ab49
* **web:** add more limits disclaimers (#6737) fb50849
* **web:** add trace.now() (#6782) 4384e96
* **web:** add zone.js (#6697) 8b47c08
* **web:** Hide 1H time option on PDP price chart (#6807) c6f84a3
* **web:** trace permit ops (#6784) 6417afd
* **web:** trace requests (#6698) 37eddbd
* **web:** trace tx ops (#6785) 7dd8d9c
* **web:** trace wallet ops (#6783) b7d6793
* **web:** update Sentry tracing integration (#6696) e3872d2
* **web:** add CurrencyInfo type to web (#6810) ffc3f51
* **web:** add realtime feature flag (#6374) 7038b33
* **web:** Add Unicons V2 to web behind flag (#5968) dc40653
* **web:** gql token lists feature flag and boilerplate (#6857) 539001c
* **web:** link PDP to LP positions page (#6838) 116eaf3
* **web:** reinstate on-chain polling configuration (#6841) b020958
* **web:** remove mobile app promo banner (#6917) (#6954) 0c1b9c3
* **web:** update AppJsonRpcProvider to only use exp decay on fails (#6890) 37862f2
### Bug Fixes
* **web:** [charts] use neutral3 dot grid (#6678) deb3486
* **web:** [info] handle null priceHistory (#6806) 231d331
* **web:** [info] Update PDP TX Table on token order switch (#6772) 371b6b4
* **web:** adjust disconnect button hover state (#6788) 7df2c12
* **web:** avoid navigator.getBattery call on web (#6708) bdd8f91
* **web:** dedupe and use shared version of useAsyncData hook (#6734) 31275df
* **web:** fiatCurrency is undefined (#6811) 3c8bea8
* **web:** fix failing snapshot test for OffchainActivityModal (#6719) dedc87b
* **web:** fix grammar in limits disclaimer (#6704) c197a86
* **web:** fix incorrect label in OffchainActivityModal (#6694) 0215c87
* **web:** fix mouseover remnant (#6725) 7fe49b8
* **web:** fix token explore cypress tests (#6764) 2a5e945
* **web:** fixes for Limits Menu on mobile layout (#6706) dc67454
* **web:** limits cancellation gas estimates (#6716) 0406324
* **web:** navigation cypress test (#6765) 889f5a3
* **web:** patch zone.js to allow wc modal (#6865) c2166a8
* **web:** remove NFT Airdrop claim (#6754) 6235a76
* **web:** swap flow e2e tests (#6767) 8248b9e
* **web:** universal search cypress test (#6766) dbeb2c2
### Documentation
* **web:** Sentry readme (#5501) 1f3f75e
* **web:** avoid polling current block timestamp (#6843) 5c65ef9
* **web:** broken token logos (#6935) 522f176
* **web:** configure Sentry source maps for rebased branches (#6923) e2b0872
* **web:** downgrade zone.js to fix iOS 16 (#6920) e0266b7
* **web:** fetch HMR paths (#6862) 734d772
* **web:** fix isMobile to not pick up desktop, and fix service worker checks (#6936) 2daff12
* **web:** fix miniflare after apollo/client upgrade (#6913) f152713
* **web:** LimitsMenu overflowing cancel button bug (#6854) acf2f01
* **web:** only poll for gas price when needed (#6845) e15f3c5
* **web:** patch zone.js to allow wc modal (#6864) 5d595b2
* **web:** reduce polling for health to 5m (#6842) 49391c8
* **web:** should copy checksummed address (#6757) bbc2aca
* **web:** show correct v2 fee (#6925) bad29f9
* **web:** stop requesting order statuses w/ invalid request body (#6820) 29ea4cd
* **web:** stop updating displayed quote after trade is submitted (#6819) c0ed816
* **web:** use apollo provider w/o realtime (#6914) 47db20e
### Code Refactoring
* **web:** use NATIVE_CHAIN_ID over static string (#6770) 94c020a
### Tests
* **web:** fix token-explore-filter e2e test (#6763) 006dd59
* **web:** squelch flushSync error (#6781) c9eecd7
* **web:** wrap apollo Provider (#6375) 50d4d64
web/5.18.0
\ No newline at end of file
web/5.19.0
\ No newline at end of file
......@@ -4,9 +4,6 @@ jest.config.js
metro.config.js
node_modules
generated*.ts
__generated__/
storybook-static
coverage
ios
android
src/abis/types
generated*.ts
__generated__/
.eslintrc.js
......@@ -131,17 +131,17 @@ android {
dev {
isDefault(true)
applicationIdSuffix ".dev"
versionName "1.23"
versionName "1.24"
dimension "variant"
}
beta {
applicationIdSuffix ".beta"
versionName "1.23"
versionName "1.24"
dimension "variant"
}
prod {
dimension "variant"
versionName "1.23"
versionName "1.24"
}
}
......
import { CreateNewWallet } from './usecases/CreateNewWallet'
import { CreateNewWallet } from 'e2e/usecases/onboarding/CreateNewWallet'
import { ImportWallet } from 'e2e/usecases/onboarding/ImportWallet'
import { WatchWallet } from 'e2e/usecases/onboarding/WatchWallet'
describe('Onboarding', () => {
beforeAll(async () => {
beforeEach(async () => {
await device.launchApp({ newInstance: true })
await device.reloadReactNative()
})
describe(CreateNewWallet, CreateNewWallet)
afterEach(async () => {
await device.uninstallApp()
await device.installApp()
})
it('creates a new wallet', CreateNewWallet)
it('watches wallet', WatchWallet)
it('imports a testing wallet using recovery phrase', ImportWallet)
})
import { WatchWallet } from 'e2e/usecases/onboarding/WatchWallet'
import { SwapBasicInteractions } from 'e2e/usecases/swap/SwapBasicInteractions'
describe('Swap', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true })
await WatchWallet()
})
it('tests swap screen interactions', SwapBasicInteractions)
})
import { by, element, expect } from 'detox'
import { TestWallet } from 'e2e/utils/fixtures'
import { ElementName } from 'wallet/src/telemetry/constants'
export async function CreateNewWallet(): Promise<void> {
// Selects "Create a new wallet" option on the landing screen
await element(by.id(ElementName.CreateAccount)).tap()
// Skips unitag flow
await element(by.id(ElementName.Skip)).tap()
// Taps "Let's keep it safe" on QRAnimation screen
await element(by.id(ElementName.Next)).tap()
// Check is both manual and cloud backup options are available on BackupScreen
await expect(element(by.id(ElementName.AddCloudBackup))).toBeVisible()
await expect(element(by.id(ElementName.AddManualBackup))).toBeVisible()
// Picks "Manual backup" option
await element(by.id(ElementName.AddManualBackup)).tap()
// Checks if ManualBackupScreen warning displays and taps "I'm ready" button
await expect(element(by.id(ElementName.Confirm))).toBeVisible()
await element(by.id(ElementName.Confirm)).tap()
// Taps continue on ManualBackupScreen
await element(by.id(ElementName.Next)).tap()
// Taps continue on manual backup confirmation screen. It is replaced by mock because detox
// can't interact with native screens
await element(by.id(ElementName.Continue)).tap()
// Skips notification setup by tapping "Maybe later" button
await element(by.id(ElementName.Skip)).tap()
// Skips biometrics setup by tapping "Maybe later" button
await element(by.id(ElementName.Skip)).tap()
// Confirms by tapping "Skip" on warning modal
await element(by.id(ElementName.Confirm)).tap()
// Confirms if user successfuly finished create new wallet flow by checking if provided wallet name is
// displayed and other
await expect(element(by.text(TestWallet.name))).toBeVisible()
await expect(element(by.id(ElementName.Swap))).toBeVisible()
await expect(element(by.id(ElementName.SearchTokensAndWallets))).toBeVisible()
}
import { by, element, expect } from 'detox'
import { TestWallet } from 'e2e/utils/fixtures'
import { ElementName } from 'wallet/src/telemetry/constants'
export async function ImportWallet(): Promise<void> {
// Selects "Add an existing wallet" option on the landing screen
await element(by.id(ElementName.ImportAccount)).tap()
// Picks Import a wallet by recovery phase option
await element(by.id(ElementName.OnboardingImportSeedPhrase)).tap()
// Checks if recovery phase input is in focus and types recovery phrase in
await expect(element(by.id(ElementName.ImportAccountInput))).toBeFocused()
await element(by.id(ElementName.ImportAccountInput)).typeText(TestWallet.recoveryPhrase)
// Taps continue navigating to SelectWalletScreen
await element(by.id(ElementName.Continue)).tap()
// Taps continue on SelectWalletScreen
await waitFor(element(by.id(`${ElementName.WalletCard}-1`)))
.toBeVisible()
.withTimeout(10000)
await element(by.id(ElementName.Next)).tap()
// Skips cloud backup step on BackupScreen by clicking "Maybe later"
await expect(element(by.id(ElementName.AddCloudBackup))).toBeVisible()
await element(by.id(ElementName.Next)).tap()
// Skips notification setup by tapping "Maybe later" button
await element(by.id(ElementName.Skip)).tap()
// Skips biometrics setup by tapping "Maybe later" button
await element(by.id(ElementName.Skip)).tap()
// Confirms by tapping "Skip" on warning modal
await element(by.id(ElementName.Confirm)).tap()
// Confirms if user successfuly finished create new wallet flow by checking if provided wallet name is
// displayed and other
await expect(element(by.text(TestWallet.name))).toBeVisible()
await expect(element(by.id(ElementName.Swap))).toBeVisible()
await expect(element(by.id(ElementName.SearchTokensAndWallets))).toBeVisible()
}
import { by, element, expect } from 'detox'
import { TestWatchedWallet } from 'e2e/utils/fixtures'
import { ElementName } from 'wallet/src/telemetry/constants'
export async function WatchWallet(): Promise<void> {
// Selects "Add an existing wallet" option on the landing screen
await element(by.id(ElementName.ImportAccount)).tap()
// Picks Watch a wallet option on ImportMethodScreen
await element(by.id(ElementName.WatchWallet)).tap()
// Checks if wallet name is in focus and types recovery phrase in
await expect(element(by.id(ElementName.ImportAccountInput))).toBeFocused()
await element(by.id(ElementName.ImportAccountInput)).typeText(TestWatchedWallet.ens)
// Confirms the entered wallet name by tapping "continue"
await element(by.id(ElementName.Next)).tap()
// Checks if Home screen is displayed with a proper user name
await expect(element(by.text(TestWatchedWallet.displayName))).toBeVisible()
await expect(element(by.id(ElementName.Swap))).toBeVisible()
await expect(element(by.id(ElementName.SearchTokensAndWallets))).toBeVisible()
}
import { by, element, expect } from 'detox'
import { TestWatchedWallet } from 'e2e/utils/fixtures'
import { ElementName } from 'wallet/src/telemetry/constants'
export async function SwapBasicInteractions(): Promise<void> {
// Navigate to swap screen
await element(by.id(ElementName.Swap)).tap()
// Checks if currency input is selected
await expect(element(by.id(ElementName.AmountInputIn))).toBeFocused()
// Checks if "Max" button is available
await expect(element(by.id(ElementName.SetMaxInput))).toBeVisible()
// Opens token selector modal on Swap screen
await element(by.id(ElementName.ChooseOutputToken)).tap()
// Picks usdc output token
await element(by.text('USDC')).atIndex(0).tap()
// Taps 1234567890 number into swap input
await element(by.id('decimal-pad-1')).tap()
await element(by.id('decimal-pad-2')).tap()
await element(by.id('decimal-pad-3')).tap()
await element(by.id('decimal-pad-4')).tap()
await element(by.id('decimal-pad-5')).tap()
await element(by.id('decimal-pad-6')).tap()
await element(by.id('decimal-pad-7')).tap()
await element(by.id('decimal-pad-8')).tap()
await element(by.id('decimal-pad-.')).tap()
await element(by.id('decimal-pad-0')).tap()
await element(by.id('decimal-pad-9')).tap()
await element(by.id('decimal-pad-1')).tap()
await element(by.id('decimal-pad-backspace')).tap()
// Checks if expected input expected value: "12345678.09"
await expect(element(by.id(ElementName.AmountInputIn))).toHaveValue('12345678.09')
// Checks if expected error is displayed
await expect(element(by.text('You don’t have enough ETH'))).toBeVisible()
// Checks if expected output expected value: "0"
await expect(element(by.id(ElementName.AmountInputOut))).not.toHaveValue('0')
// Swaps input and output currencies
await element(by.id(ElementName.SwitchCurrenciesButton)).tap()
// Checks if expected input expected value: "0"
await expect(element(by.id(ElementName.AmountInputIn))).toHaveValue('0')
// Checks if expected error is displayed
await expect(element(by.text('Not enough liquidity'))).toBeVisible()
// Checks if expected output expected value: "12345678.09"
await expect(element(by.id(ElementName.AmountInputOut))).toHaveValue('12345678.09')
// Swaps input and output currencies
await element(by.id(ElementName.SwitchCurrenciesButton)).tap()
// Selects currency output
await element(by.id(ElementName.AmountInputOut)).tap()
// Clears the output field
await element(by.id(ElementName.AmountInputOut)).clearText()
await element(by.id('decimal-pad-1')).tap()
await element(by.id('decimal-pad-2')).tap()
await element(by.id('decimal-pad-3')).tap()
// Checks if output has expected value: "123"
await expect(element(by.id(ElementName.AmountInputOut))).toHaveValue('123')
// Checks if expected input value to be cleared
await expect(element(by.id(ElementName.AmountInputIn))).not.toHaveValue('0')
// Checks dollar value to be visible
await expect(element(by.text('$123.00'))).toBeVisible()
// Swipes swap modal by dragging down SwapFormHeader
await element(by.id(ElementName.SwapFormHeader)).swipe('down', 'fast', 0.75)
// Checks if Home screen is visible and not covered
await expect(element(by.text(TestWatchedWallet.displayName))).toBeVisible()
await expect(element(by.id(ElementName.Swap))).toBeVisible()
await expect(element(by.id(ElementName.SearchTokensAndWallets))).toBeVisible()
}
export const TestWallet = {
name: 'Wallet 1',
recoveryPhrase:
'oak reduce strong borrow control funny library disagree radio clarify degree pistol',
}
export const TestWatchedWallet = {
ens: 'Spenciefy',
displayName: 'spencer',
}
import { by, device, element } from 'detox'
import { Accounts } from 'src/e2e/utils/fixtures'
import { sleep } from 'utilities/src/time/timing'
import { ElementName } from 'wallet/src/telemetry/constants'
/** Opens Account page and imports a managed account */
export async function quickOnboarding() {
await device.setBiometricEnrollment(true)
// open app, import existing account
await element(by.id(ElementName.OnboardingImportSeedPhrase)).tap()
// enter address / eth
await element(by.id('import_account_form/input')).typeText(Accounts.managed.seedPhrase)
await sleep(500)
await element(by.id(ElementName.Next)).tap()
await element(by.id(ElementName.WalletCard + '-1')).tap()
await element(by.id(ElementName.Next)).tap()
// skip notifs
await element(by.id(ElementName.Skip)).tap()
// Face ID
await element(by.id(ElementName.Enable)).tap()
await device.matchFace()
// Outro
await element(by.id(ElementName.Next)).tap()
}
export async function maybeDismissTokenWarning() {
try {
await element(by.id(ElementName.TokenWarningAccept)).tap()
} catch (e) {
// no-op
}
}
......@@ -2450,7 +2450,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2496,7 +2496,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets;
......@@ -2542,7 +2542,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets;
......@@ -2588,7 +2588,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets;
......@@ -2630,7 +2630,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2673,7 +2673,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension;
......@@ -2716,7 +2716,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension;
......@@ -2759,7 +2759,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension;
......@@ -2795,7 +2795,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -2833,7 +2833,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3003,7 +3003,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -3047,7 +3047,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension;
......@@ -3143,7 +3143,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3214,7 +3214,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension;
......@@ -3310,7 +3310,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3381,7 +3381,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.23;
MARKETING_VERSION = 1.24;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension;
......
......@@ -7,10 +7,10 @@
"operationSearchPaths": [
"../../../apps/mobile/src/components/PriceExplorer/TokenPriceHistory.graphql",
"../../../apps/mobile/src/components/explore/search/SearchPopularTokens.graphql",
"../../../packages/wallet/src/data/queries.graphql"
"../../../packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql"
],
"schemaSearchPaths": [
"../../../packages/wallet/src/data/schema.graphql"
"../../../packages/uniswap/src/data/graphql/uniswap-data-api/schema.graphql"
]
},
"output": {
......
......@@ -100,6 +100,7 @@
"expo-barcode-scanner": "12.7.0",
"expo-blur": "12.6.0",
"expo-camera": "13.4.4",
"expo-clipboard": "4.1.2",
"expo-haptics": "12.0.1",
"expo-linear-gradient": "12.3.0",
"expo-linking": "4.0.1",
......
......@@ -45,18 +45,24 @@ import { flexStyles, useIsDarkMode } from 'ui/src'
import { config } from 'uniswap/src/config'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context'
import { isDetoxBuild } from 'utilities/src/environment'
import { registerConsoleOverrides } from 'utilities/src/logger/console'
import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks'
import { AnalyticsNavigationContextProvider } from 'utilities/src/telemetry/trace/AnalyticsNavigationContext'
import { initFirebaseAppCheck } from 'wallet/src/features/appCheck'
import { useCurrentAppearanceSetting } from 'wallet/src/features/appearance/hooks'
import { EXPERIMENT_NAMES, FEATURE_FLAGS } from 'wallet/src/features/experiments/constants'
import {
DUMMY_STATSIG_SDK_KEY,
EXPERIMENT_NAMES,
FEATURE_FLAGS,
} from 'wallet/src/features/experiments/constants'
import { selectFavoriteTokens } from 'wallet/src/features/favorites/selectors'
import { useAppFiatCurrencyInfo } from 'wallet/src/features/fiatCurrency/hooks'
import { LocalizationContextProvider } from 'wallet/src/features/language/LocalizationContext'
import { useCurrentLanguageInfo } from 'wallet/src/features/language/hooks'
import { updateLanguage } from 'wallet/src/features/language/slice'
import { clearNotificationQueue } from 'wallet/src/features/notifications/slice'
import { TransactionHistoryUpdater } from 'wallet/src/features/transactions/TransactionHistoryUpdater'
import { Account } from 'wallet/src/features/wallet/accounts/types'
import { WalletContextProvider } from 'wallet/src/features/wallet/context'
......@@ -75,10 +81,7 @@ if (__DEV__) {
// Construct a new instrumentation instance. This is needed to communicate between the integration and React
const routingInstrumentation = new Sentry.ReactNavigationInstrumentation()
// Dummy key since we use the reverse proxy will handle the real key
const DUMMY_STATSIG_SDK_KEY = 'client-0000000000000000000000000000000000000000000'
if (!__DEV__ && !process.env.DETOX_MODE) {
if (!__DEV__ && !isDetoxBuild) {
Sentry.init({
environment: getSentryEnvironment(),
dsn: config.sentryDsn,
......@@ -105,7 +108,7 @@ if (!__DEV__ && !process.env.DETOX_MODE) {
// Log boxes on simulators can block detox tap event when they cover buttons placed at
// the bottom of the screen and cause tests to fail.
if (process.env.DETOX_MODE) {
if (isDetoxBuild) {
LogBox.ignoreAllLogs()
}
......@@ -259,6 +262,7 @@ function AppInner(): JSX.Element {
}, [allowAnalytics])
useEffect(() => {
dispatch(clearNotificationQueue()) // clear all in-app toasts on app start
dispatch(updateLanguage(null))
}, [dispatch])
......
......@@ -58,6 +58,7 @@ import {
v56Schema,
v57Schema,
v58Schema,
v59Schema,
v5Schema,
v6Schema,
v7Schema,
......@@ -69,7 +70,7 @@ import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { initialBiometricsSettingsState } from 'src/features/biometrics/slice'
import { initialCloudBackupState } from 'src/features/CloudBackup/cloudBackupSlice'
import { initialPasswordLockoutState } from 'src/features/CloudBackup/passwordLockoutSlice'
import { initialModalState } from 'src/features/modals/modalSlice'
import { initialModalsState } from 'src/features/modals/modalSlice'
import { initialTelemetryState } from 'src/features/telemetry/slice'
import { initialTweaksState } from 'src/features/tweaks/slice'
import { initialWalletConnectState } from 'src/features/walletConnect/walletConnectSlice'
......@@ -177,7 +178,7 @@ describe('Redux state migrations', () => {
favorites: initialFavoritesState,
fiatCurrencySettings: initialFiatCurrencyState,
languageSettings: initialLanguageState,
modals: initialModalState,
modals: initialModalsState,
notifications: initialNotificationsState,
passwordLockout: initialPasswordLockoutState,
behaviorHistory: initialBehaviorHistoryState,
......@@ -1325,4 +1326,11 @@ describe('Redux state migrations', () => {
expect(v59.behaviorHistory.hasCompletedUnitagsIntroModal).toBe(false)
})
it('migrates from v59 to 60', () => {
const v59Stub = { ...v59Schema }
const v60 = migrations[60](v59Stub)
expect(v60.behaviorHistory.hasViewedUniconV2IntroModal).toBe(false)
})
})
......@@ -804,4 +804,15 @@ export const migrations = {
return newState
},
60: function addUniconV2IntroModalBoolean(state: any) {
const newState = { ...state }
newState.behaviorHistory = {
...state.behaviorHistory,
hasViewedUniconV2IntroModal: false,
}
return newState
},
}
import React from 'react'
import { act } from 'react-test-renderer'
import { PreloadedState } from 'redux'
import { AccountSwitcher } from 'src/app/modals/AccountSwitcherModal'
import { MobileState } from 'src/app/reducer'
import { initialModalState } from 'src/features/modals/modalSlice'
import { render } from 'src/test/test-utils'
import { preloadedMobileState, preloadedModalsState } from 'src/test/fixtures'
import { cleanup, render } from 'src/test/test-utils'
import { ModalName } from 'wallet/src/telemetry/constants'
import { ACCOUNT } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState, noOpFunction } from 'wallet/src/test/mocks'
import { noOpFunction } from 'wallet/src/test/mocks'
const preloadedState = {
...mockWalletPreloadedState(ACCOUNT),
modals: {
...initialModalState,
const preloadedState = preloadedMobileState({
account: ACCOUNT,
modals: preloadedModalsState({
[ModalName.AccountSwitcher]: { isOpen: true },
},
} as unknown as PreloadedState<MobileState>
}),
})
// TODO [MOB-259]: Figure out how to do snapshot tests when there is a BottomSheetModal
describe(AccountSwitcher, () => {
it('renders correctly', async () => {
const tree = render(<AccountSwitcher onClose={noOpFunction} />, { preloadedState })
await act(async () => {
// Wait until the component is rendered
})
expect(tree.toJSON()).toMatchSnapshot()
cleanup()
})
})
import React from 'react'
import { useTranslation } from 'react-i18next'
import 'react-native-reanimated'
import { useAppDispatch } from 'src/app/hooks'
import {
Button,
Flex,
Icons,
Text,
Unicon,
UniconV2,
useDeviceInsets,
useIsDarkMode,
useUniconColors,
} from 'ui/src'
import { spacing } from 'ui/src/theme'
import { UniconGradient } from 'wallet/src/components/accounts/AccountIcon'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { setHasViewedUniconV2IntroModal } from 'wallet/src/features/behaviorHistory/slice'
import { ModalName } from 'wallet/src/telemetry/constants'
interface UniconsV2ModalProps {
address: string
}
const UNICON_HEADER_SIZE = 52
const UNICON_SIZE = 42
const UNICON_PADDING = 14
export function UniconsV2Modal({ address }: UniconsV2ModalProps): JSX.Element {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const isDarkMode = useIsDarkMode()
const { gradientEnd: uniconColor } = useUniconColors(address)
const { top } = useDeviceInsets()
const onCloseModal = (): void => {
dispatch(setHasViewedUniconV2IntroModal(true))
}
return (
<>
<BottomSheetModal hideScrim name={ModalName.UniconsV2} onClose={onCloseModal}>
<Flex gap="$spacing24" px="$spacing24" py="$spacing16">
<Flex centered row gap="$spacing8">
<Flex centered height={UNICON_SIZE} width={UNICON_SIZE}>
<Unicon address={address} size={UNICON_SIZE - UNICON_PADDING} />
<UniconGradient color={uniconColor} size={UNICON_SIZE} />
</Flex>
<Icons.RightArrow color="$neutral3" size="$icon.16" />
<UniconV2 address={address} size={UNICON_SIZE} />
</Flex>
<Flex alignItems="center" gap="$spacing12">
<Text variant="subheading1">{t('unicons.banner.title')}</Text>
<Text color="$neutral2" textAlign="center" variant="body2">
{t('unicons.banner.subtitle')}
</Text>
</Flex>
<Button theme="secondary" onPress={onCloseModal}>
{t('unicons.banner.button')}
</Button>
</Flex>
</BottomSheetModal>
<Flex
backgroundColor={isDarkMode ? '$surface3' : '$surface1'}
borderRadius="$roundedFull"
bottom={0}
height={UNICON_HEADER_SIZE}
left={spacing.spacing24}
mt="$spacing8"
position="absolute"
right={0}
top={top}
width={UNICON_HEADER_SIZE}
zIndex="$tooltip">
<UniconV2 address={address} size={UNICON_HEADER_SIZE} />
</Flex>
</>
)
}
......@@ -50,6 +50,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
"position": "relative",
}
}
testID="account-icon"
>
<View
style={
......
import { PreloadedState } from '@reduxjs/toolkit'
import React from 'react'
import { Text } from 'react-native'
import { LazyModalRenderer } from 'src/app/modals/utils'
import { MobileState } from 'src/app/reducer'
import { initialModalState } from 'src/features/modals/modalSlice'
import { preloadedMobileState, preloadedModalsState } from 'src/test/fixtures'
import { renderWithProviders } from 'src/test/render'
import { ModalName } from 'wallet/src/telemetry/constants'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
const preloadedState = {
...mockWalletPreloadedState,
modals: initialModalState,
} as unknown as PreloadedState<MobileState>
describe(LazyModalRenderer, () => {
it('renders null when modal is not open', () => {
......@@ -19,26 +11,24 @@ describe(LazyModalRenderer, () => {
<LazyModalRenderer name={ModalName.Experiments}>
<Text>Rendered</Text>
</LazyModalRenderer>,
{ preloadedState }
{ preloadedState: preloadedMobileState() }
)
expect(tree.toJSON()).toBeNull()
})
it('renders modal when modal is open', () => {
const state = {
...preloadedState,
modals: {
...initialModalState,
[ModalName.Experiments]: { isOpen: true },
},
}
const tree = renderWithProviders(
<LazyModalRenderer name={ModalName.Experiments}>
<Text>Rendered</Text>
</LazyModalRenderer>,
{ preloadedState: state }
{
preloadedState: preloadedMobileState({
modals: preloadedModalsState({
[ModalName.Experiments]: { isOpen: true },
}),
}),
}
)
expect(tree.toJSON()).toMatchInlineSnapshot(`
......
......@@ -4,7 +4,7 @@ import { navigate as rootNavigate } from 'src/app/navigation/rootNavigation'
import { useAppStackNavigation, useExploreStackNavigation } from 'src/app/navigation/types'
import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex'
import { Screens } from 'src/screens/Screens'
import { useTransactionListLazyQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { useTransactionListLazyQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
/**
* Utility hook to simplify navigating to Activity screen.
......
import { PersistState } from 'redux-persist'
import { apolloClient } from 'src/data/usePersistedApolloClient'
import { apolloClientRef } from 'src/data/usePersistedApolloClient'
import { appRatingWatcherSaga } from 'src/features/appRating/saga'
import { cloudBackupsManagerSaga } from 'src/features/CloudBackup/saga'
import { deepLinkWatcher } from 'src/features/deepLinking/handleDeepLinkSaga'
......@@ -9,7 +9,7 @@ import { telemetrySaga } from 'src/features/telemetry/saga'
import { restoreMnemonicCompleteWatcher } from 'src/features/wallet/saga'
import { walletConnectSaga } from 'src/features/walletConnect/saga'
import { signWcRequestSaga } from 'src/features/walletConnect/signWcRequestSaga'
import { delay, select, spawn } from 'typed-redux-saga'
import { call, delay, select, spawn } from 'typed-redux-saga'
import { appLanguageWatcherSaga } from 'wallet/src/features/language/saga'
import {
swapActions,
......@@ -115,6 +115,8 @@ export function* mobileSaga() {
yield* spawn(s)
}
const apolloClient = yield* call(apolloClientRef.onReady)
yield* spawn(transactionWatcher, { apolloClient })
for (const m of Object.values(monitoredSagas)) {
......
......@@ -442,6 +442,14 @@ export const v59Schema = {
hasCompletedUnitagsIntroModal: false,
},
}
export const v60Schema = {
...v59Schema,
behaviorHistory: {
...v59Schema.behaviorHistory,
hasViewedUniconV2IntroModal: false,
},
}
// TODO: [MOB-201] use function with typed output when API reducers are removed from rootReducer
// export const getSchema = (): RootState => v0Schema
export const getSchema = (): typeof v57Schema => v57Schema
export const getSchema = (): typeof v59Schema => v59Schema
......@@ -75,7 +75,7 @@ export const persistConfig = {
key: 'root',
storage: reduxStorage,
whitelist,
version: 59,
version: 60,
migrate: createMigrate(migrations),
}
......
import { Flex, UniconV2 } from 'ui/src'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { ModalName } from 'wallet/src/telemetry/constants'
const generateRandomEthereumAddresses = (numberOfAddresses: number): string[] => {
const addresses = []
for (let i = 0; i < numberOfAddresses; i++) {
const randomHex = [...Array(40)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')
addresses.push(`0x${randomHex}`)
}
return addresses
}
export const UniconSampleSheet = ({ onClose }: { onClose: () => void }): JSX.Element => {
return (
<BottomSheetModal
blurredBackground
backgroundColor="$surface1"
name={ModalName.UniconsDevModal}
onClose={onClose}>
<Flex centered height="100%" width="100%">
<Flex row alignItems="center" flexWrap="wrap" justifyContent="center" width="100%">
{generateRandomEthereumAddresses(80).map((address) => {
return (
<Flex>
<UniconV2 key={address} address={address} size={42} />
</Flex>
)
})}
</Flex>
</Flex>
</BottomSheetModal>
)
}
......@@ -65,7 +65,7 @@ export const AnimatedDecimalNumber = memo(function AnimatedDecimalNumber(
return {
color: number.value.value < decimalThreshold ? wholePartColor : decimalPartColor,
}
}, [decimalThreshold, wholePartColor, decimalPartColor])
}, [number.value, decimalThreshold, wholePartColor, decimalPartColor])
const fontSize = fonts[variant].fontSize * fontScale
// Choose the arbitrary value that looks good for the font used
......
......@@ -14,7 +14,7 @@ import { Loader } from 'src/components/loading'
import { invokeImpact } from 'src/utils/haptic'
import { Flex } from 'ui/src'
import { spacing } from 'ui/src/theme'
import { HistoryDuration } from 'wallet/src/data/__generated__/types-and-hooks'
import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useAppFiatCurrencyInfo } from 'wallet/src/features/fiatCurrency/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { CurrencyId } from 'wallet/src/utils/currencyId'
......
......@@ -135,7 +135,7 @@ const RollNumber = ({
const char = chars.value[index - (commaIndex - decimalPlace.value)]
const number = char ? parseFloat(char) : undefined
return Number.isNaN(number) ? undefined : number
}, [chars])
}, [chars, commaIndex, decimalPlace, index])
const animatedFontStyle = useAnimatedStyle(() => {
return {
......@@ -156,7 +156,7 @@ const RollNumber = ({
restSpeedThreshold: 2,
})
: endValue
}, [shouldAnimate])
}, [animatedDigit, shouldAnimate])
const animatedWrapperStyle = useAnimatedStyle(() => {
const digitWidth =
......@@ -261,7 +261,7 @@ const Numbers = ({
const decimalPlace = useDerivedValue(() => {
return price.formatted.value.indexOf(currency.decimalSeparator)
}, [price])
}, [currency.decimalSeparator, price.formatted])
const commaIndex = numberOfDigits.left + Math.floor((numberOfDigits.left - 1) / 3)
......
......@@ -10,7 +10,7 @@ import { TIME_RANGES } from 'src/components/PriceExplorer/constants'
import { useChartDimensions } from 'src/components/PriceExplorer/useChartDimensions'
import Trace from 'src/components/Trace/Trace'
import { AnimatedFlex, AnimatedText, Flex, TouchableArea, useSporeColors } from 'ui/src'
import { HistoryDuration } from 'wallet/src/data/__generated__/types-and-hooks'
import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
interface Props {
label: string
......@@ -70,7 +70,7 @@ export function TimeRangeGroup({
},
],
}),
[adjustedLabelWidth]
[adjustedLabelWidth, buttonWidth, currentIndex, isRTL]
)
return (
......
import { HistoryDuration } from 'wallet/src/data/__generated__/types-and-hooks'
import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import i18n from 'wallet/src/i18n/i18n'
import { ElementName } from 'wallet/src/telemetry/constants'
......
......@@ -6,7 +6,7 @@ import {
HistoryDuration,
TimestampedAmount,
TokenProject as TokenProjectType,
} from 'wallet/src/data/__generated__/types-and-hooks'
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import {
SAMPLE_CURRENCY_ID_1,
getLatestPrice,
......
......@@ -2,13 +2,13 @@ import { maxBy } from 'lodash'
import { Dispatch, SetStateAction, useCallback, useMemo, useRef, useState } from 'react'
import { SharedValue } from 'react-native-reanimated'
import { TLineChartData } from 'react-native-wagmi-charts'
import { GqlResult } from 'uniswap/src/data/types'
import { PollingInterval } from 'wallet/src/constants/misc'
import {
HistoryDuration,
TimestampedAmount,
useTokenPriceHistoryQuery,
} from 'wallet/src/data/__generated__/types-and-hooks'
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { GqlResult } from 'uniswap/src/data/types'
import { PollingInterval } from 'wallet/src/constants/misc'
import { isError, isNonPollingRequestInFlight } from 'wallet/src/data/utils'
import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
......
......@@ -4,10 +4,10 @@ import QRCode from 'src/components/QRCodeScanner/custom-qr-code-generator'
import {
ColorTokens,
Flex,
getUniconV2Colors,
useIsDarkMode,
useSporeColors,
useUniconColors,
useUniconV2Colors,
} from 'ui/src'
import { borderRadii } from 'ui/src/theme'
......@@ -39,7 +39,7 @@ const useColorProps = (address: Address, color?: string): ColorProps => {
const gradientData = useUniconColors(address)
const isUniconsV2Enabled = useFeatureFlag(FEATURE_FLAGS.UniconsV2)
const isDarkMode = useIsDarkMode()
const uniconV2Color = useUniconV2Colors(address, isDarkMode) as { color: string }
const uniconV2Color = getUniconV2Colors(address, isDarkMode) as { color: string }
const { avatar, loading: avatarLoading } = useAvatar(address)
const { colors: avatarColors } = useExtractedColors(avatar) as { colors: AvatarColors }
const hasAvatar = !!avatar && !avatarLoading
......
import { BarCodeScanner, BarCodeScannerResult } from 'expo-barcode-scanner'
import { Camera, CameraType } from 'expo-camera'
import { BarCodeScanner } from 'expo-barcode-scanner'
import { BarCodeScanningResult, Camera, CameraType } from 'expo-camera'
import { PermissionStatus } from 'expo-modules-core'
import React, { memo, useCallback, useMemo, useState } from 'react'
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, LayoutChangeEvent, LayoutRectangle, StyleSheet } from 'react-native'
import { launchImageLibrary } from 'react-native-image-picker'
......@@ -19,7 +19,7 @@ import {
} from 'ui/src'
import CameraScan from 'ui/src/assets/icons/camera-scan.svg'
import { iconSizes, spacing } from 'ui/src/theme'
import { useAsyncData } from 'utilities/src/react/hooks'
import { Sentry } from 'utilities/src/logger/Sentry'
import PasteButton from 'wallet/src/components/buttons/PasteButton'
import { SpinningLoader } from 'wallet/src/components/loading/SpinningLoader'
import { openSettings } from 'wallet/src/utils/linking'
......@@ -60,7 +60,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
const [bottomLayout, setBottomLayout] = useState<LayoutRectangle | null>()
const handleBarCodeScanned = useCallback(
(result: BarCodeScannerResult): void => {
(result: BarCodeScanningResult): void => {
if (shouldFreezeCamera) {
return
}
......@@ -103,10 +103,18 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
handleBarCodeScanned(result)
}, [handleBarCodeScanned, isReadingImageFile, t])
// Check for camera permissions, handle cases where not granted or undetermined
const getPermissionStatuses = useCallback(async (): Promise<void> => {
useEffect(() => {
Sentry.addBreadCrumb({
level: 'info',
category: 'camera',
message: 'QRCodeScannera camera permission status',
data: {
permissionStatus,
},
})
if (permissionStatus === PermissionStatus.UNDETERMINED) {
await requestPermissionResponse()
requestPermissionResponse().catch(() => {})
}
if (permissionStatus === PermissionStatus.DENIED) {
......@@ -119,8 +127,6 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
}
}, [permissionStatus, requestPermissionResponse, t])
useAsyncData(getPermissionStatuses)
const overlayWidth = (overlayLayout?.height ?? 0) / CAMERA_ASPECT_RATIO
const scannerSize = Math.min(overlayWidth, dimensions.fullWidth) * SCAN_ICON_WIDTH_RATIO
......@@ -143,7 +149,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
}}
style={StyleSheet.absoluteFillObject}
type={CameraType.back}
onBarCodeScanned={shouldFreezeCamera ? undefined : handleBarCodeScanned}
onBarCodeScanned={handleBarCodeScanned}
/>
)}
</Flex>
......
......@@ -2,9 +2,9 @@ import React, { useMemo } from 'react'
import { ScrollView, StyleSheet } from 'react-native'
import { Flex, Text, useDeviceDimensions } from 'ui/src'
import { spacing } from 'ui/src/theme'
import { AccountListQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { NumberType } from 'utilities/src/format/types'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { AccountListQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { useAccountList } from 'wallet/src/features/accounts/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { Account } from 'wallet/src/features/wallet/accounts/types'
......
......@@ -7,7 +7,7 @@ import AlertTriangleIcon from 'ui/src/assets/icons/alert-triangle.svg'
import TrashIcon from 'ui/src/assets/icons/trash.svg'
import WalletIcon from 'ui/src/assets/icons/wallet-filled.svg'
import { ThemeNames } from 'ui/src/theme'
import { getCloudProviderName } from 'uniswap/src/utils/platform'
import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName'
import { Account, AccountType } from 'wallet/src/features/wallet/accounts/types'
import { useDisplayName } from 'wallet/src/features/wallet/hooks'
......
import React from 'react'
import { Flex, flexStyles, Text, TouchableArea } from 'ui/src'
import { iconSizes, imageSizes } from 'ui/src/theme'
import {
SafetyLevel,
TokenDetailsScreenQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo'
import WarningIcon from 'wallet/src/components/icons/WarningIcon'
import { SafetyLevel, TokenDetailsScreenQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
export interface TokenDetailsHeaderProps {
......
......@@ -5,8 +5,8 @@ import { getBlockExplorerIcon } from 'src/components/icons/BlockExplorerIcon'
import { Flex, Text } from 'ui/src'
import GlobeIcon from 'ui/src/assets/icons/globe-filled.svg'
import TwitterIcon from 'ui/src/assets/icons/x-twitter.svg'
import { TokenDetailsScreenQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { CHAIN_INFO, ChainId } from 'wallet/src/constants/chains'
import { TokenDetailsScreenQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { ElementName } from 'wallet/src/telemetry/constants'
import {
currencyIdToAddress,
......
......@@ -4,11 +4,11 @@ import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
import { LongText } from 'src/components/text/LongText'
import { Flex, Icons, Text, TouchableArea, useSporeColors } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { TokenDetailsScreenQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { NumberType } from 'utilities/src/format/types'
import { TokenDetailsScreenQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { Language } from 'wallet/src/features/language/constants'
import { useCurrentLanguage, useCurrentLanguageInfo } from 'wallet/src/features/language/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
function StatsRow({
label,
......
import { useCrossChainBalances, useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks'
import { Screens } from 'src/screens/Screens'
import { preloadedMobileState } from 'src/test/fixtures'
import { act, renderHook, waitFor } from 'src/test/test-utils'
import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils'
import {
......@@ -10,7 +11,6 @@ import {
usdcArbitrumToken,
usdcBaseToken,
} from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
import { queryResolvers } from 'wallet/src/test/utils'
const mockedNavigation = {
......@@ -33,7 +33,7 @@ describe(useCrossChainBalances, () => {
describe('currentChainBalance', () => {
it('returns null if there are no balances for the specified currency', async () => {
const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), {
preloadedState: mockWalletPreloadedState(),
preloadedState: preloadedMobileState(),
})
await act(() => undefined)
......@@ -54,7 +54,7 @@ describe(useCrossChainBalances, () => {
const { result } = renderHook(
() => useCrossChainBalances(currentChainBalance.currencyInfo.currencyId, null),
{
preloadedState: mockWalletPreloadedState(),
preloadedState: preloadedMobileState(),
resolvers,
}
)
......@@ -72,7 +72,7 @@ describe(useCrossChainBalances, () => {
describe('otherChainBalances', () => {
it('returns null if there are no bridged currencies', async () => {
const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), {
preloadedState: mockWalletPreloadedState(),
preloadedState: preloadedMobileState(),
})
await act(() => undefined)
......@@ -105,7 +105,7 @@ describe(useCrossChainBalances, () => {
const { result } = renderHook(
() => useCrossChainBalances(currentChainBalance!.currencyInfo.currencyId, bridgeInfo),
{
preloadedState: mockWalletPreloadedState(),
preloadedState: preloadedMobileState(),
resolvers,
}
)
......
......@@ -5,7 +5,7 @@ import { Screens } from 'src/screens/Screens'
import {
Chain,
useTokenDetailsScreenLazyQuery,
} from 'wallet/src/data/__generated__/types-and-hooks'
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { PortfolioBalance } from 'wallet/src/features/dataApi/types'
import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils'
......
......@@ -28,7 +28,9 @@ export function HeaderText({
return readablePermitAmount ? (
<Text textAlign="center" variant="heading3">
<Trans
components={{ highlight: <Text fontWeight="bold" /> }}
// `variant` prop must be first
// eslint-disable-next-line react/jsx-sort-props
components={{ highlight: <Text variant="heading3" fontWeight="bold" /> }}
i18nKey="qrScanner.request.withAmount"
values={{
dappName: dapp.name,
......@@ -40,7 +42,9 @@ export function HeaderText({
) : (
<Text textAlign="center" variant="heading3">
<Trans
components={{ highlight: <Text fontWeight="bold" /> }}
// `variant` prop must be first
// eslint-disable-next-line react/jsx-sort-props
components={{ highlight: <Text variant="heading3" fontWeight="bold" /> }}
i18nKey="qrScanner.request.withoutAmount"
values={{
dappName: dapp.name,
......@@ -56,30 +60,12 @@ export function HeaderText({
case EthMethod.PersonalSign:
case EthMethod.EthSign:
case EthMethod.SignTypedData:
return (
<Trans
components={{ highlight: <Text fontWeight="bold" /> }}
i18nKey="qrScanner.request.method.signature"
values={{ dappNameOrUrl }}
/>
)
return <Trans i18nKey="qrScanner.request.method.signature" values={{ dappNameOrUrl }} />
case EthMethod.EthSendTransaction:
return (
<Trans
components={{ highlight: <Text fontWeight="bold" /> }}
i18nKey="qrScanner.request.method.transaction"
values={{ dappNameOrUrl }}
/>
)
return <Trans i18nKey="qrScanner.request.method.transaction" values={{ dappNameOrUrl }} />
}
return (
<Trans
components={{ highlight: <Text fontWeight="bold" /> }}
i18nKey="qrScanner.request.method.default"
values={{ dappNameOrUrl }}
/>
)
return <Trans i18nKey="qrScanner.request.method.default" values={{ dappNameOrUrl }} />
}
return (
......
......@@ -178,7 +178,7 @@ function TransactionDetails({
px="$spacing8"
py="$spacing4">
<Text color="$neutral1" loading={isLoading} variant="monospace">
{{ functionName: parsedData ? parsedData.name : t('common.text.unknown') }}
{parsedData ? parsedData.name : t('common.text.unknown')}
</Text>
</Flex>
</Flex>
......
......@@ -44,9 +44,9 @@ export function SpendingDetails({
</Text>
<Flex row alignItems="center" gap="$spacing4">
<CurrencyLogo currencyInfo={nativeCurrencyInfo} size={iconSizes.icon16} />
<Text variant="subheading2">{{ tokenAmountWithSymbol }}</Text>
<Text variant="subheading2">{tokenAmountWithSymbol}</Text>
<Text color="$neutral2" loading={!usdValue} variant="subheading2">
({{ fiatAmount }})
{fiatAmount}
</Text>
</Flex>
</Flex>
......
......@@ -137,7 +137,24 @@ export function WalletConnectModal({
}
if (supportedURI.type === URIType.Scantastic) {
const { pubKey, uuid, vendor, model, browser } = parseScantasticParams(supportedURI.value)
const params = parseScantasticParams(supportedURI.value)
if (!params) {
setShouldFreezeCamera(true)
Alert.alert(
t('walletConnect.error.scantastic.title'),
t('walletConnect.error.scantastic.message'),
[
{
text: t('common.button.ok'),
onPress: (): void => {
setShouldFreezeCamera(false)
},
},
]
)
return
}
setShouldFreezeCamera(true)
dispatch(closeAllModals())
......@@ -145,11 +162,7 @@ export function WalletConnectModal({
openModal({
name: ModalName.Scantastic,
initialState: {
pubKey,
uuid,
vendor,
model,
browser,
params,
},
})
)
......
......@@ -5,7 +5,8 @@ import {
UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM,
UNISWAP_WALLETCONNECT_URL,
} from 'src/features/deepLinking/handleDeepLinkSaga'
import { ScantasticModalState } from 'src/features/scantastic/ScantasticModalState'
import { logger } from 'utilities/src/logger/logger'
import { ScantasticParams, ScantasticParamsSchema } from 'wallet/src/features/scantastic/types'
import { UwULinkRequest } from 'wallet/src/features/walletConnect/types'
import { getValidAddress } from 'wallet/src/utils/addresses'
......@@ -167,12 +168,53 @@ function getScantasticAddress(uri: string): Nullable<string> {
return uriParts[1] || null
}
const PARAM_PUB_KEY = 'pubKey'
const PARAM_UUID = 'uuid'
const PARAM_VENDOR = 'vendor'
const PARAM_MODEL = 'model'
const PARAM_BROWSER = 'browser'
/** parses scantastic params for a valid scantastic URI. */
export function parseScantasticParams(uri: string): ScantasticModalState {
const pubKey = new URLSearchParams(uri).get('pubKey') || ''
const uuid = new URLSearchParams(uri).get('uuid') || ''
const vendor = new URLSearchParams(uri).get('vendor') || ''
const model = new URLSearchParams(uri).get('model') || ''
const browser = new URLSearchParams(uri).get('browser') || ''
return { pubKey, uuid, vendor, model, browser }
export function parseScantasticParams(uri: string): ScantasticParams | undefined {
const uriParams = new URLSearchParams(uri)
const paramKeys = [PARAM_PUB_KEY, PARAM_UUID, PARAM_VENDOR, PARAM_MODEL, PARAM_BROWSER]
// Validate all keys are unique for security
for (const paramKey of paramKeys) {
if (uriParams.getAll(paramKey).length > 1) {
logger.error(new Error('Invalid scantastic params due to duplicate keys'), {
tags: {
file: 'util.ts',
function: 'parseScantasticParams',
},
extra: { uri },
})
return
}
}
const publicKey = uriParams.get(PARAM_PUB_KEY)
const uuid = uriParams.get(PARAM_UUID)
const vendor = uriParams.get(PARAM_VENDOR)
const model = uriParams.get(PARAM_MODEL)
const browser = uriParams.get(PARAM_BROWSER)
try {
return ScantasticParamsSchema.parse({
publicKey: publicKey ? JSON.parse(publicKey) : undefined,
uuid: uuid ? decodeURIComponent(uuid) : undefined,
vendor: vendor ? decodeURIComponent(vendor) : undefined,
model: model ? decodeURIComponent(model) : undefined,
browser: browser ? decodeURIComponent(browser) : undefined,
})
} catch (e) {
const wrappedError = new Error('Invalid scantastic params')
wrappedError.cause = e
logger.error(wrappedError, {
tags: {
file: 'util.ts',
function: 'parseScantasticParams',
},
})
}
}
import { fireEvent, render, screen, waitFor } from 'src/test/test-utils'
import * as hooks from 'wallet/src/features/accounts/hooks'
import {
ON_PRESS_EVENT_PAYLOAD,
SAMPLE_SEED_ADDRESS_1,
amount,
portfolio,
} from 'wallet/src/test/fixtures'
import { queryResolvers } from 'wallet/src/test/utils'
import { AccountCardItem } from './AccountCardItem'
describe(AccountCardItem, () => {
beforeEach(() => {
jest.spyOn(hooks, 'useAccountList').mockReturnValue({
data: undefined,
loading: false,
networkStatus: 7,
refetch: jest.fn(),
startPolling: jest.fn(),
stopPolling: jest.fn(),
})
})
afterEach(() => {
jest.restoreAllMocks()
})
const defaultProps = {
address: SAMPLE_SEED_ADDRESS_1,
isPortfolioValueLoading: false,
portfolioValue: 100,
isViewOnly: false,
onPress: jest.fn(),
}
it('renders correctly', () => {
const tree = render(<AccountCardItem {...defaultProps} />)
expect(tree).toMatchSnapshot()
})
it('calls onPress when address is pressed', () => {
const onPress = jest.fn()
render(<AccountCardItem {...defaultProps} onPress={onPress} />)
const address = screen.getByTestId(`account-item/${SAMPLE_SEED_ADDRESS_1}`)
fireEvent.press(address, ON_PRESS_EVENT_PAYLOAD)
expect(onPress).toHaveBeenCalledTimes(1)
})
describe('portfolio value', () => {
it('displays loading shimmmer when portfolio value is loading', () => {
const { rerender } = render(
<AccountCardItem
{...defaultProps}
isPortfolioValueLoading={true}
portfolioValue={undefined}
/>
)
// Select shimmer placeholder because the actual shimmer is rendered after onLayout
// is fired and this logic is not a part of this test
expect(screen.queryByTestId('shimmer-placeholder')).toBeTruthy()
rerender(
<AccountCardItem
{...defaultProps}
isPortfolioValueLoading={false}
portfolioValue={undefined}
/>
)
expect(screen.queryByTestId('shimmer-placeholder')).toBeFalsy()
})
it('shows current portfolio value when available', () => {
render(<AccountCardItem {...defaultProps} portfolioValue={100} />)
expect(screen.queryByText('$100.00')).toBeTruthy()
})
it('shows placeholder text when portfolio value is not available', () => {
render(<AccountCardItem {...defaultProps} portfolioValue={undefined} />)
expect(screen.queryByText('N/A')).toBeTruthy()
})
it('shows cached portfolio value when not provided explicitly in props', async () => {
// We don't want to use the mocked query response for this test as we want to
// test if the cached value (returned by the query) is used when value is not provided
jest.restoreAllMocks()
const { resolvers: resolversWithPortfolioValue } = queryResolvers({
portfolios: () => [portfolio({ tokensTotalDenominatedValue: amount({ value: 200 }) })],
})
render(<AccountCardItem {...defaultProps} portfolioValue={undefined} />, {
resolvers: resolversWithPortfolioValue,
})
await waitFor(() => {
expect(screen.queryByText('$200.00')).toBeTruthy()
})
})
})
describe('view only accounts', () => {
it('renders view only badge when account is view only', () => {
render(<AccountCardItem {...defaultProps} isViewOnly={true} />)
const badge = screen.queryByTestId('account-icon/view-only-badge')
expect(badge).toBeTruthy()
})
it('does not render view only badge when account is not view only', () => {
render(<AccountCardItem {...defaultProps} isViewOnly={false} />)
const badge = screen.queryByTestId('account-icon/view-only-badge')
expect(badge).toBeFalsy()
})
})
})
......@@ -131,7 +131,7 @@ export function AccountCardItem({
px="$spacing24"
onLongPress={disableOnPress}
onPress={(): void => onPress(address)}>
<Flex row alignItems="flex-start" gap="$spacing16" testID={`account_item/${address}`}>
<Flex row alignItems="flex-start" gap="$spacing16" testID={`account-item/${address}`}>
<Flex fill>
<AddressDisplay
address={address}
......
import React from 'react'
import * as ExpoClipboard from 'expo-clipboard'
import { navigationRef } from 'src/app/navigation/NavigationContainer'
import { MobileState } from 'src/app/reducer'
import { AccountHeader } from 'src/components/accounts/AccountHeader'
import { render } from 'src/test/test-utils'
import { ACCOUNT } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
import { Screens } from 'src/screens/Screens'
import { fireEvent, render, screen, waitFor, within } from 'src/test/test-utils'
import { ModalName } from 'wallet/src/telemetry/constants'
import {
ACCOUNT,
ON_PRESS_EVENT_PAYLOAD,
preloadedSharedState,
signerMnemonicAccount,
} from 'wallet/src/test/fixtures'
import { sanitizeAddressText, shortenAddress } from 'wallet/src/utils/addresses'
const preloadedState = preloadedSharedState({ account: ACCOUNT })
const address = ACCOUNT.address
const shortenedAddress = sanitizeAddressText(shortenAddress(address))!
const isModalOpen = (state: MobileState): boolean => {
const modalState = state.modals[ModalName.AccountSwitcher]
return modalState.isOpen
}
describe(AccountHeader, () => {
it('renders without error', () => {
const tree = render(<AccountHeader />, { preloadedState: mockWalletPreloadedState(ACCOUNT) })
it('renders correctly', () => {
const tree = render(<AccountHeader />, { preloadedState })
expect(tree.toJSON()).toMatchSnapshot()
})
describe('when wallet has no display name', () => {
const accountWithoutName = signerMnemonicAccount({ name: undefined, address })
const stateWithoutName = preloadedSharedState({
account: accountWithoutName,
})
it('renders shortened address within section address without name section', () => {
render(<AccountHeader />, { preloadedState: stateWithoutName })
const addressSection = screen.getByTestId('account-header/address-only')
const addressText = within(addressSection).queryByText(shortenedAddress)
expect(addressText).toBeTruthy()
})
it('copies wallet address to clipboard when address section is pressed', async () => {
const setStringAsync = jest.fn()
jest.spyOn(ExpoClipboard, 'setStringAsync').mockImplementation(setStringAsync)
render(<AccountHeader />, { preloadedState: stateWithoutName })
const addressSection = screen.getByTestId('account-header/address-only')
fireEvent.press(addressSection, ON_PRESS_EVENT_PAYLOAD)
await waitFor(() => {
expect(setStringAsync).toHaveBeenCalledTimes(1)
expect(setStringAsync).toHaveBeenCalledWith(address)
})
})
})
describe('when wallet has a display name', () => {
it('renders section with display name and address', () => {
render(<AccountHeader />, { preloadedState })
const displayNameSection = screen.getByTestId('account-header/display-name')
const displayNameText = within(displayNameSection).queryByText(ACCOUNT.name)
const addressText = within(displayNameSection).queryByText(shortenedAddress)
expect(displayNameText).toBeTruthy()
expect(addressText).toBeTruthy()
})
it('opens account switcher modal when account name is pressed', () => {
const { store } = render(<AccountHeader />, { preloadedState })
const displayNameText = within(screen.getByTestId('account-header/display-name')).getByText(
ACCOUNT.name
)
expect(isModalOpen(store.getState())).toBe(false)
fireEvent.press(displayNameText, ON_PRESS_EVENT_PAYLOAD)
expect(isModalOpen(store.getState())).toBe(true)
})
})
it('opens account switcher modal when account avatar is pressed', () => {
const { store } = render(<AccountHeader />, { preloadedState })
const avatar = screen.getByTestId('account-icon')
expect(isModalOpen(store.getState())).toBe(false)
fireEvent.press(avatar, ON_PRESS_EVENT_PAYLOAD)
expect(isModalOpen(store.getState())).toBe(true)
})
it('opens settings screen when settings button is pressed', async () => {
const navigate = jest.fn()
jest.spyOn(navigationRef, 'isReady').mockImplementation(() => true)
jest.spyOn(navigationRef, 'navigate').mockImplementation(navigate)
render(<AccountHeader />, { preloadedState })
const settingsButton = screen.getByTestId('account-header/settings-button')
fireEvent.press(settingsButton, ON_PRESS_EVENT_PAYLOAD)
await waitFor(() => {
expect(navigate).toHaveBeenCalledTimes(1)
expect(navigate).toHaveBeenCalledWith(Screens.SettingsStack, { screen: Screens.Settings })
})
})
})
......@@ -96,12 +96,21 @@ export function AccountHeader(): JSX.Element {
size={iconSize}
/>
</TouchableArea>
<TouchableArea hapticFeedback hitSlop={20} onPress={onPressSettings}>
<TouchableArea
hapticFeedback
hitSlop={20}
testID="account-header/settings-button"
onPress={onPressSettings}>
<Icons.Settings color="$neutral2" opacity={0.8} size="$icon.28" />
</TouchableArea>
</Flex>
{walletHasName ? (
<Flex row alignItems="center" gap="$spacing8" justifyContent="space-between">
<Flex
row
alignItems="center"
gap="$spacing8"
justifyContent="space-between"
testID="account-header/display-name">
<TouchableArea
hapticFeedback
flexShrink={1}
......@@ -111,7 +120,11 @@ export function AccountHeader(): JSX.Element {
</TouchableArea>
</Flex>
) : (
<TouchableArea hapticFeedback hitSlop={20} onPress={onPressCopyAddress}>
<TouchableArea
hapticFeedback
hitSlop={20}
testID="account-header/address-only"
onPress={onPressCopyAddress}>
<Flex centered row shrink gap="$spacing4">
<Text
adjustsFontSizeToFit
......
import { fireEvent } from '@testing-library/react-native'
import React from 'react'
import { AccountList } from 'src/components/accounts/AccountList'
import { render, screen } from 'src/test/test-utils'
import { cleanup, fireEvent, render, screen } from 'src/test/test-utils'
import { NumberType } from 'utilities/src/format/types'
import { Resolvers } from 'wallet/src/data/__generated__/types-and-hooks'
import { ACCOUNT, ON_PRESS_EVENT_PAYLOAD, amounts } from 'wallet/src/test/fixtures'
import {
ACCOUNT,
ON_PRESS_EVENT_PAYLOAD,
amounts,
portfolio,
readOnlyAccount,
signerMnemonicAccount,
} from 'wallet/src/test/fixtures'
import { mockLocalizedFormatter } from 'wallet/src/test/mocks'
import { createArray, queryResolvers } from 'wallet/src/test/utils'
import { sanitizeAddressText, shortenAddress } from 'wallet/src/utils/addresses'
const resolvers: Resolvers = {
Portfolio: {
tokensTotalDenominatedValue: () => amounts.md(),
},
}
const tokensTotalDenominatedValue = amounts.md()
const { resolvers } = queryResolvers({
portfolios: () => [portfolio({ tokensTotalDenominatedValue })],
})
describe(AccountList, () => {
afterEach(cleanup)
it('renders without error', async () => {
const tree = render(<AccountList accounts={[ACCOUNT]} onPress={jest.fn()} />, { resolvers })
expect(
await screen.findByText(
mockLocalizedFormatter.formatNumberOrString({
value: amounts.md().value,
value: tokensTotalDenominatedValue.value,
type: NumberType.PortfolioBalance,
currencyCode: 'usd',
})
......@@ -38,15 +45,65 @@ describe(AccountList, () => {
expect(
await screen.findByText(
mockLocalizedFormatter.formatNumberOrString({
value: amounts.md().value,
value: tokensTotalDenominatedValue.value,
type: NumberType.PortfolioBalance,
currencyCode: 'usd',
})
)
).toBeDefined()
fireEvent.press(screen.getByTestId(`account_item/${ACCOUNT.address}`), ON_PRESS_EVENT_PAYLOAD)
fireEvent.press(screen.getByTestId(`account-item/${ACCOUNT.address}`), ON_PRESS_EVENT_PAYLOAD)
expect(onPressSpy).toHaveBeenCalledTimes(1)
})
describe('signer accounts', () => {
it('renders signer accounts section if there are signer accounts', () => {
const signerAccounts = createArray(3, signerMnemonicAccount)
render(<AccountList accounts={signerAccounts} onPress={jest.fn()} />, { resolvers })
expect(screen.queryByText('Your other wallets')).toBeTruthy()
signerAccounts.forEach((account) => {
const address = sanitizeAddressText(shortenAddress(account.address))
if (address) {
expect(screen.queryByText(address)).toBeTruthy()
}
})
cleanup()
})
it('does not render signer accounts section if there are no signer accounts', () => {
render(<AccountList accounts={[readOnlyAccount()]} onPress={jest.fn()} />, { resolvers })
expect(screen.queryByText('Your other wallets')).toBeFalsy()
cleanup()
})
})
describe('view only accounts', () => {
it('renders view only accounts section if there are view only accounts', () => {
const viewOnlyAccounts = createArray(3, readOnlyAccount)
render(<AccountList accounts={viewOnlyAccounts} onPress={jest.fn()} />, { resolvers })
expect(screen.queryByText('View only wallets')).toBeTruthy()
viewOnlyAccounts.forEach((account) => {
const address = sanitizeAddressText(shortenAddress(account.address))
if (address) {
expect(screen.queryByText(address)).toBeTruthy()
}
})
cleanup()
})
it('does not render view only accounts section if there are no view only accounts', () => {
render(<AccountList accounts={[signerMnemonicAccount()]} onPress={jest.fn()} />, {
resolvers,
})
expect(screen.queryByText('View only wallets')).toBeFalsy()
// cleanup()
})
})
})
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AccountHeader renders without error 1`] = `
exports[`AccountHeader renders correctly 1`] = `
<View
style={
{
......@@ -81,6 +81,7 @@ exports[`AccountHeader renders without error 1`] = `
"position": "relative",
}
}
testID="account-icon"
>
<View
style={
......@@ -347,6 +348,7 @@ exports[`AccountHeader renders without error 1`] = `
],
}
}
testID="account-header/settings-button"
>
<RNSVGSvgView
align="xMidYMid"
......@@ -418,6 +420,7 @@ exports[`AccountHeader renders without error 1`] = `
"justifyContent": "space-between",
}
}
testID="account-header/display-name"
>
<View
cancelable={true}
......
......@@ -145,7 +145,7 @@ exports[`AccountList renders without error 1`] = `
"gap": 16,
}
}
testID="account_item/0x82D56A352367453f74FC0dC7B071b311da373Fa6"
testID="account-item/0x82D56A352367453f74FC0dC7B071b311da373Fa6"
>
<View
style={
......@@ -193,6 +193,7 @@ exports[`AccountList renders without error 1`] = `
"position": "relative",
}
}
testID="account-icon"
>
<View
style={
......
import React from 'react'
import { BackButton } from 'src/components/buttons/BackButton'
import { fireEvent, render, screen } from 'src/test/test-utils'
import { ON_PRESS_EVENT_PAYLOAD } from 'wallet/src/test/fixtures'
import { BackButton } from './BackButton'
const mockedGoBack = jest.fn()
jest.mock('@react-navigation/native', () => {
......@@ -17,8 +17,9 @@ jest.mock('@react-navigation/native', () => {
describe(BackButton, () => {
it('renders without error', async () => {
render(<BackButton showButtonLabel />)
const tree = render(<BackButton showButtonLabel />)
expect(tree).toMatchSnapshot()
expect(await screen.findByText('Back')).toBeDefined()
})
......
import { fireEvent, render } from 'src/test/test-utils'
import { ON_PRESS_EVENT_PAYLOAD } from 'wallet/src/test/fixtures'
import { CloseButton } from './CloseButton'
describe(CloseButton, () => {
it('renders without error', () => {
const tree = render(<CloseButton onPress={jest.fn()} />)
expect(tree).toMatchSnapshot()
})
it('calls onPress when pressed', async () => {
const onPress = jest.fn()
const { getByTestId } = render(<CloseButton onPress={onPress} />)
const button = getByTestId('buttons/close-button')
fireEvent.press(button, ON_PRESS_EVENT_PAYLOAD)
expect(onPress).toHaveBeenCalledTimes(1)
})
})
......@@ -10,7 +10,7 @@ type Props = {
export function CloseButton({ onPress, size, strokeWidth, color, ...rest }: Props): JSX.Element {
return (
<TouchableArea onPress={onPress} {...rest}>
<TouchableArea onPress={onPress} {...rest} testID="buttons/close-button">
<Icons.X color={color} size={size ?? '$icon.20'} strokeWidth={strokeWidth ?? 2} />
</TouchableArea>
)
......
import { act, fireEvent, render } from 'src/test/test-utils'
import { setClipboard } from 'wallet/src/utils/clipboard'
import { CopyTextButton } from './CopyTextButton'
jest.mock('wallet/src/utils/clipboard')
describe(CopyTextButton, () => {
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.useRealTimers()
})
it('renders without error', () => {
const tree = render(<CopyTextButton copyText="copy text" />)
expect(tree).toMatchSnapshot()
})
it('copies text when pressed', async () => {
const { getByText } = render(<CopyTextButton copyText="copy text" />)
const button = getByText('Copy')
await act(async () => {
fireEvent.press(button)
})
expect(setClipboard).toHaveBeenCalledWith('copy text')
})
it('changes button text when text is copied and brings back original text after timeout', async () => {
const { queryByText, getByText } = render(<CopyTextButton copyText="copy text" />)
const button = getByText('Copy')
await act(async () => {
fireEvent.press(button)
})
expect(queryByText('Copied')).toBeTruthy()
await act(async () => {
jest.advanceTimersByTime(2000)
})
expect(queryByText('Copy')).toBeTruthy()
})
})
import React from 'react'
import { TapGestureHandler, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler'
import {
cancelAnimation,
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import { AnimatedFlex, useIsDarkMode, useSporeColors } from 'ui/src'
import HeartIcon from 'ui/src/assets/icons/heart.svg'
interface FavoriteButtonProps {
isFavorited: boolean
size: number
onPress: () => void
}
export const FavoriteButton = ({
isFavorited,
size,
onPress,
}: FavoriteButtonProps): JSX.Element => {
const colors = useSporeColors()
const isDarkMode = useIsDarkMode()
const unfilledColor = isDarkMode ? colors.neutral3.val : colors.surface3.val
const color = isFavorited ? colors.accent1.val : unfilledColor
const scale = useSharedValue(1)
const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }), [scale])
const animationConfig = { duration: 150 }
const onGestureEvent = useAnimatedGestureHandler<TapGestureHandlerGestureEvent>({
onStart: () => {
cancelAnimation(scale)
scale.value = withTiming(0, animationConfig)
},
onFinish: () => {
runOnJS(onPress)()
scale.value = withTiming(1, animationConfig)
},
})
return (
<TapGestureHandler onGestureEvent={onGestureEvent}>
<AnimatedFlex style={animatedStyle}>
<HeartIcon color={color} height={size} width={size} />
</AnimatedFlex>
</TapGestureHandler>
)
}
import { fireEvent, render } from 'src/test/test-utils'
import { ON_PRESS_EVENT_PAYLOAD } from 'wallet/src/test/fixtures'
import { LinkButton } from './LinkButton'
jest.mock('wallet/src/utils/linking')
describe(LinkButton, () => {
it('renders without error', () => {
const tree = render(<LinkButton label="link text" url="https://example.com" />)
expect(tree).toMatchSnapshot()
})
it('renders button with specified label', async () => {
const { queryByText } = render(<LinkButton label="link text" url="https://example.com" />)
expect(queryByText('link text')).toBeDefined()
})
describe('when pressed', () => {
const cases = [
{ openExternalBrowser: false, isSafeUri: false },
{ openExternalBrowser: true, isSafeUri: false },
{ openExternalBrowser: false, isSafeUri: true },
{ openExternalBrowser: true, isSafeUri: true },
]
it.each(cases)('calls openUri with %p', async ({ openExternalBrowser, isSafeUri }) => {
const { getByText } = render(
<LinkButton
isSafeUri={isSafeUri}
label="link text"
openExternalBrowser={openExternalBrowser}
url="https://example.com"
/>
)
const button = getByText('link text')
fireEvent.press(button, ON_PRESS_EVENT_PAYLOAD)
expect(require('wallet/src/utils/linking').openUri).toHaveBeenCalledWith(
'https://example.com',
openExternalBrowser,
isSafeUri
)
})
})
})
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BackButton renders without error 1`] = `
<View
cancelable={true}
disabled={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
style={
{
"alignItems": "center",
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
testID="buttons/back-button"
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"gap": 8,
}
}
>
<View
style={
{
"alignItems": "center",
"borderBottomLeftRadius": 999999,
"borderBottomRightRadius": 999999,
"borderTopLeftRadius": 999999,
"borderTopRightRadius": 999999,
"flexDirection": "column",
"justifyContent": "center",
"transform": [
{
"rotate": "0deg",
},
],
}
}
>
<RNSVGSvgView
align="xMidYMid"
bbHeight="24"
bbWidth="24"
fill="none"
focusable={false}
meetOrSlice={0}
minX={0}
minY={0}
stroke="currentColor"
strokeWidth={8}
style={
[
{
"backgroundColor": "transparent",
"borderWidth": 0,
},
{
"color": "#7D7D7D",
"height": 24,
"width": 24,
},
{
"flex": 0,
"height": 24,
"width": 24,
},
]
}
tintColor="#7D7D7D"
vbHeight={24}
vbWidth={24}
>
<RNSVGGroup
fill={null}
propList={
[
"fill",
"stroke",
"strokeWidth",
]
}
stroke={
{
"type": 2,
}
}
strokeWidth="8"
>
<RNSVGPath
d="M15 6L9 12L15 18"
fill={
{
"payload": 4278190080,
"type": 0,
}
}
propList={
[
"stroke",
"strokeWidth",
"strokeLinecap",
"strokeLinejoin",
]
}
stroke={
{
"type": 2,
}
}
strokeLinecap={1}
strokeLinejoin={1}
strokeWidth="3"
/>
</RNSVGGroup>
</RNSVGSvgView>
</View>
<Text
allowFontScaling={true}
maxFontSizeMultiplier={1.4}
style={
{
"color": "#7D7D7D",
"fontFamily": "Basel-Book",
"fontSize": 19,
"lineHeight": 24,
}
}
suppressHighlighting={true}
>
Back
</Text>
</View>
</View>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CloseButton renders without error 1`] = `
<View
cancelable={true}
disabled={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
testID="buttons/close-button"
>
<RNSVGSvgView
align="xMidYMid"
bbHeight="20"
bbWidth="20"
fill="none"
focusable={false}
meetOrSlice={0}
minX={0}
minY={0}
strokeWidth={2}
style={
[
{
"backgroundColor": "transparent",
"borderWidth": 0,
},
{
"height": 20,
"width": 20,
},
{
"flex": 0,
"height": 20,
"width": 20,
},
]
}
vbHeight={16}
vbWidth={16}
>
<RNSVGGroup
fill={null}
propList={
[
"fill",
"strokeWidth",
]
}
strokeWidth="2"
>
<RNSVGPath
d="M12.5303 4.53033C12.8232 4.23744 12.8232 3.76256 12.5303 3.46967C12.2374 3.17678 11.7626 3.17678 11.4697 3.46967L12.5303 4.53033ZM3.46967 11.4697C3.17678 11.7626 3.17678 12.2374 3.46967 12.5303C3.76256 12.8232 4.23744 12.8232 4.53033 12.5303L3.46967 11.4697ZM4.53033 3.46967C4.23744 3.17678 3.76256 3.17678 3.46967 3.46967C3.17678 3.76256 3.17678 4.23744 3.46967 4.53033L4.53033 3.46967ZM11.4697 12.5303C11.7626 12.8232 12.2374 12.8232 12.5303 12.5303C12.8232 12.2374 12.8232 11.7626 12.5303 11.4697L11.4697 12.5303ZM11.4697 3.46967L3.46967 11.4697L4.53033 12.5303L12.5303 4.53033L11.4697 3.46967ZM3.46967 4.53033L11.4697 12.5303L12.5303 11.4697L4.53033 3.46967L3.46967 4.53033Z"
fill={
{
"type": 2,
}
}
propList={
[
"fill",
]
}
/>
</RNSVGGroup>
</RNSVGSvgView>
</View>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CopyTextButton renders without error 1`] = `
<View
cancelable={true}
disabled={false}
focusable={true}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#2222220D",
"borderBottomColor": "transparent",
"borderBottomLeftRadius": 16,
"borderBottomRightRadius": 16,
"borderBottomWidth": 1,
"borderLeftColor": "transparent",
"borderLeftWidth": 1,
"borderRightColor": "transparent",
"borderRightWidth": 1,
"borderStyle": "solid",
"borderTopColor": "transparent",
"borderTopLeftRadius": 16,
"borderTopRightRadius": 16,
"borderTopWidth": 1,
"flexDirection": "row",
"gap": 8,
"height": "auto",
"justifyContent": "center",
"opacity": 1,
"paddingBottom": 12,
"paddingLeft": 12,
"paddingRight": 12,
"paddingTop": 12,
}
}
>
<svg
color="#222222"
data-file-name="SvgCopySheets"
height={20}
size={20}
width={20}
/>
<Text
maxFontSizeMultiplier={1.2}
style={
{
"color": "#222222",
"fontFamily": "Basel-Medium",
"fontSize": 19,
"fontWeight": "500",
"lineHeight": 24,
}
}
suppressHighlighting={true}
>
Copy
</Text>
</View>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LinkButton renders without error 1`] = `
<View
cancelable={true}
disabled={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"gap": 4,
"justifyContent": "center",
}
}
>
<Text
allowFontScaling={true}
style={
{
"color": "#222222",
"fontFamily": "Basel-Book",
}
}
suppressHighlighting={true}
>
link text
</Text>
<svg
color="#FC72FF"
data-file-name="SvgExternalLink"
height={20}
strokeWidth={1.5}
width={20}
/>
</View>
</View>
`;
......@@ -7,7 +7,7 @@ import { CloseButton } from 'src/components/buttons/CloseButton'
import { CarouselContext } from 'src/components/carousel/Carousel'
import { OnboardingScreens } from 'src/screens/Screens'
import { Flex, Text, useDeviceDimensions } from 'ui/src'
import { getCloudProviderName } from 'uniswap/src/utils/platform'
import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName'
function Page({
text,
......
......@@ -17,15 +17,15 @@ import {
} from 'src/features/explore/utils'
import { usePollOnFocusOnly } from 'src/utils/hooks'
import { Flex, Loader, Text, useDeviceInsets } from 'ui/src'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { getWrappedNativeAddress } from 'wallet/src/constants/addresses'
import { ChainId } from 'wallet/src/constants/chains'
import { PollingInterval } from 'wallet/src/constants/misc'
import {
Chain,
ExploreTokensTabQuery,
useExploreTokensTabQuery,
} from 'wallet/src/data/__generated__/types-and-hooks'
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { getWrappedNativeAddress } from 'wallet/src/constants/addresses'
import { ChainId } from 'wallet/src/constants/chains'
import { PollingInterval } from 'wallet/src/constants/misc'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { usePersistedError } from 'wallet/src/features/dataApi/utils'
import {
......
......@@ -12,13 +12,13 @@ import { disableOnPress } from 'src/utils/disableOnPress'
import { usePollOnFocusOnly } from 'src/utils/hooks'
import { AnimatedFlex, AnimatedTouchableArea, Flex, Text } from 'ui/src'
import { borderRadii, imageSizes } from 'ui/src/theme'
import { useFavoriteTokenCardQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { NumberType } from 'utilities/src/format/types'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo'
import { RelativeChange } from 'wallet/src/components/text/RelativeChange'
import { ChainId } from 'wallet/src/constants/chains'
import { PollingInterval } from 'wallet/src/constants/misc'
import { useFavoriteTokenCardQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { isNonPollingRequestInFlight } from 'wallet/src/data/utils'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils'
......
......@@ -11,8 +11,8 @@ import { MobileEventName } from 'src/features/telemetry/constants'
import { disableOnPress } from 'src/utils/disableOnPress'
import { Flex, Icons, Text, TouchableArea, useIsDarkMode } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { TokenSortableField } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { logger } from 'utilities/src/logger/logger'
import { TokenSortableField } from 'wallet/src/data/__generated__/types-and-hooks'
import { setTokensOrderBy } from 'wallet/src/features/wallet/slice'
import { ClientTokensOrderBy, TokensOrderBy } from 'wallet/src/features/wallet/types'
interface FilterGroupProps {
......
......@@ -4,7 +4,7 @@ import { act } from 'react-test-renderer'
import configureMockStore from 'redux-mock-store'
import { useExploreTokenContextMenu } from 'src/components/explore/hooks'
import { renderHookWithProviders } from 'src/test/render'
import { Resolvers } from 'wallet/src/data/__generated__/types-and-hooks'
import { Resolvers } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { FavoritesState } from 'wallet/src/features/favorites/slice'
import { CurrencyField } from 'wallet/src/features/transactions/transactionState/types'
import { SectionName } from 'wallet/src/telemetry/constants'
......
......@@ -6,7 +6,7 @@ import {
gqlNFTToNFTCollectionSearchResult,
} from 'src/components/explore/search/utils'
import { Inset, Loader } from 'ui/src'
import { useSearchPopularNftCollectionsQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { useSearchPopularNftCollectionsQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import {
NFTCollectionSearchResult,
SearchResultType,
......
......@@ -17,10 +17,13 @@ import {
getSearchResultId,
} from 'src/components/explore/search/utils'
import { AnimatedFlex, Flex, Text } from 'ui/src'
import {
SafetyLevel,
useExploreSearchQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { logger } from 'utilities/src/logger/logger'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { CHAIN_INFO, ChainId } from 'wallet/src/constants/chains'
import { SafetyLevel, useExploreSearchQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { SearchContext } from 'wallet/src/features/search/SearchContext'
import {
NFTCollectionSearchResult,
......@@ -185,8 +188,9 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }):
<AnimatedFlex entering={FadeIn} exiting={FadeOut} gap="$spacing8" mx="$spacing8">
<Text color="$neutral2" variant="body1">
<Trans
components={{ highlight: <Text color="$neutral1" /> }}
components={{ highlight: <Text color="$neutral1" variant="body1" /> }}
i18nKey="explore.search.empty.full"
values={{ searchQuery }}
/>
</Text>
</AnimatedFlex>
......
......@@ -2,19 +2,19 @@ import { ImpactFeedbackStyle } from 'expo-haptics'
import { default as React } from 'react'
import ContextMenu from 'react-native-context-menu-view'
import { useAppDispatch } from 'src/app/hooks'
import { useExploreTokenContextMenu } from 'src/components/explore/hooks'
import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks'
import { useExploreTokenContextMenu } from 'src/components/explore/hooks'
import { sendMobileAnalyticsEvent } from 'src/features/telemetry'
import { MobileEventName } from 'src/features/telemetry/constants'
import { disableOnPress } from 'src/utils/disableOnPress'
import { Flex, Text, TouchableArea, useIsDarkMode } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo'
import WarningIcon from 'wallet/src/components/icons/WarningIcon'
import { SafetyLevel } from 'wallet/src/data/__generated__/types-and-hooks'
import { SearchContext } from 'wallet/src/features/search/SearchContext'
import { addToSearchHistory } from 'wallet/src/features/search/searchHistorySlice'
import { SearchResultType, TokenSearchResult } from 'wallet/src/features/search/SearchResult'
import { addToSearchHistory } from 'wallet/src/features/search/searchHistorySlice'
import { ElementName, SectionName } from 'wallet/src/telemetry/constants'
import { shortenAddress } from 'wallet/src/utils/addresses'
import { buildCurrencyId, buildNativeCurrencyId } from 'wallet/src/utils/currencyId'
......
......@@ -4,7 +4,10 @@ import {
formatTokenSearchResults,
gqlNFTToNFTCollectionSearchResult,
} from 'src/components/explore/search/utils'
import { Chain, ExploreSearchQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import {
Chain,
ExploreSearchQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { SearchResultType } from 'wallet/src/features/search/SearchResult'
import {
......
import { SEARCH_RESULT_HEADER_KEY } from 'src/components/explore/search/constants'
import { SearchResultOrHeader } from 'src/components/explore/search/types'
import { Chain, ExploreSearchQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import {
Chain,
ExploreSearchQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import {
NFTCollectionSearchResult,
......
......@@ -13,8 +13,8 @@ import { useBiometricAppSettings, useBiometricPrompt } from 'src/features/biomet
import { openModal } from 'src/features/modals/modalSlice'
import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice'
import { Flex, useDeviceInsets, useSporeColors } from 'ui/src'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { isAndroid } from 'uniswap/src/utils/platform'
import { GQLQueries } from 'wallet/src/data/queries'
import { useActivityData } from 'wallet/src/features/activity/useActivityData'
import { ModalName } from 'wallet/src/telemetry/constants'
......
......@@ -12,9 +12,9 @@ import { openModal } from 'src/features/modals/modalSlice'
import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice'
import { Flex, Text, useDeviceInsets, useSporeColors } from 'ui/src'
import { NoTransactions } from 'ui/src/components/icons/NoTransactions'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { isAndroid } from 'uniswap/src/utils/platform'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { GQLQueries } from 'wallet/src/data/queries'
import { useFormattedTransactionDataForFeed } from 'wallet/src/features/activity/hooks'
import { selectWatchedAddressSet } from 'wallet/src/features/favorites/selectors'
import TransactionSummaryLayout from 'wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout'
......
import { FlashList } from '@shopify/flash-list'
import React, { forwardRef, memo, useCallback, useMemo } from 'react'
import { RefreshControl } from 'react-native'
import { useAppDispatch } from 'src/app/hooks'
import { useAppStackNavigation } from 'src/app/navigation/types'
import { NftView } from 'src/components/NFT/NftView'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { useAdaptiveFooter } from 'src/components/home/hooks'
import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers'
import { openModal } from 'src/features/modals/modalSlice'
import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice'
import { Screens } from 'src/screens/Screens'
import { Flex, useDeviceInsets, useSporeColors } from 'ui/src'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { isAndroid } from 'uniswap/src/utils/platform'
import { NftsList } from 'wallet/src/components/nfts/NftsList'
import { GQLQueries } from 'wallet/src/data/queries'
import { NFTItem } from 'wallet/src/features/nfts/types'
import { ModalName } from 'wallet/src/telemetry/constants'
export const NFTS_TAB_DATA_DEPENDENCIES = [GQLQueries.NftsTab]
......@@ -34,7 +29,6 @@ export const NftsTab = memo(
ref
) {
const colors = useSporeColors()
const dispatch = useAppDispatch()
const insets = useDeviceInsets()
const navigation = useAppStackNavigation()
......@@ -42,14 +36,6 @@ export const NftsTab = memo(
containerProps?.contentContainerStyle
)
const onPressScan = (): void => {
// in case we received a pending session from a previous scan after closing modal
dispatch(removePendingSession())
dispatch(
openModal({ name: ModalName.WalletConnectScan, initialState: ScannerModalState.WalletQr })
)
}
const renderNFTItem = useCallback(
(item: NFTItem) => {
const onPressNft = (): void => {
......@@ -95,7 +81,6 @@ export const NftsTab = memo(
renderNFTItem={renderNFTItem}
renderedInModal={renderedInModal}
onContentSizeChange={onContentSizeChange}
onPressEmptyState={onPressScan}
onRefresh={onRefresh}
onScroll={scrollHandler}
{...containerProps}
......
......@@ -3,17 +3,17 @@ import React, { forwardRef, memo, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList } from 'react-native'
import { useAppDispatch } from 'src/app/hooks'
import { WalletEmptyState } from 'src/components/home/WalletEmptyState'
import { NoTokens } from 'src/components/icons/NoTokens'
import { TabContentProps, TabProps } from 'src/components/layout/TabHelpers'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { TokenBalanceList } from 'src/components/TokenBalanceList/TokenBalanceList'
import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks'
import { WalletEmptyState } from 'src/components/home/WalletEmptyState'
import { NoTokens } from 'src/components/icons/NoTokens'
import { TabContentProps, TabProps } from 'src/components/layout/TabHelpers'
import { openModal } from 'src/features/modals/modalSlice'
import { Screens } from 'src/screens/Screens'
import { Flex } from 'ui/src'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { GQLQueries } from 'wallet/src/data/queries'
import { TokenBalanceListRow } from 'wallet/src/features/portfolio/TokenBalanceListContext'
import { ModalName } from 'wallet/src/telemetry/constants'
import { CurrencyId } from 'wallet/src/utils/currencyId'
......
......@@ -36,6 +36,7 @@ export const Favorite = ({ isFavorited, size }: FavoriteButtonProps): JSX.Elemen
const scale = useDerivedValue(() => {
return withSequence(withTiming(0, ANIMATION_CONFIG), withTiming(1, ANIMATION_CONFIG))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isFavorited])
const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }), [scale])
......
import { ImageLibraryOptions, launchImageLibrary } from 'react-native-image-picker'
import { useNftsTabQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { NUM_FIRST_NFTS } from 'wallet/src/components/nfts/NftsList'
import { useNftsTabQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { formatNftItems } from 'wallet/src/features/nfts/utils'
// Selected image will be shrunk to max width/height
......
......@@ -21,7 +21,50 @@ import {
import { FEATURE_FLAGS } from 'wallet/src/features/experiments/constants'
import { useFeatureFlag } from 'wallet/src/features/experiments/hooks'
export let apolloClient: ApolloClient<NormalizedCacheObject> | null = null
type ApolloClientRef = {
current: ApolloClient<NormalizedCacheObject> | null
onReady: () => Promise<ApolloClient<NormalizedCacheObject>>
}
// This object allows us to get access to the apollo client in places outside of React where we can't use hooks.
export const apolloClientRef: ApolloClientRef = ((): ApolloClientRef => {
let apolloClient: ApolloClient<NormalizedCacheObject> | null = null
const listeners: Array<
(
value: ApolloClient<NormalizedCacheObject> | PromiseLike<ApolloClient<NormalizedCacheObject>>
) => void
> = []
const ref: ApolloClientRef = {
get current() {
return apolloClient
},
set current(newApolloClient) {
if (!newApolloClient) {
throw new Error("Can't set `apolloClient` to `null`")
}
if (apolloClient) {
throw new Error('`apolloClient` should not be updated after it has already been set')
}
apolloClient = newApolloClient
listeners.forEach((resolve) => resolve(newApolloClient))
},
onReady: async (): Promise<ApolloClient<NormalizedCacheObject>> => {
if (apolloClient) {
return Promise.resolve(apolloClient)
}
return new Promise<ApolloClient<NormalizedCacheObject>>((resolve) => listeners.push(resolve))
},
}
return ref
})()
const mmkv = new MMKV()
if (isNonJestDev) {
......@@ -80,7 +123,7 @@ export const usePersistedApolloClient = (): ApolloClient<NormalizedCacheObject>
},
})
apolloClient = newClient
apolloClientRef.current = newClient
setClient(newClient)
// Ensure this callback only is computed once even if apolloLink changes,
......
......@@ -9,17 +9,13 @@ import { useDebounce } from 'utilities/src/time/timing'
import { ElementName } from 'wallet/src/telemetry/constants'
import {
PASSWORD_VALIDATION_DEBOUNCE_MS,
PasswordErrors,
PasswordStrength,
getPasswordStrength,
getPasswordStrengthTextAndColor,
isPasswordStrongEnough,
} from 'wallet/src/utils/password'
export enum PasswordErrors {
WeakPassword = 'WeakPassword',
PasswordsDoNotMatch = 'PasswordsDoNotMatch',
}
export type CloudBackupPasswordProps = {
navigateToNextScreen: ({ password }: { password: string }) => void
isConfirmation?: boolean
......
......@@ -9,7 +9,7 @@ import { backupMnemonicToCloudStorage } from 'src/features/CloudBackup/RNCloudSt
import { OnboardingScreens, Screens } from 'src/screens/Screens'
import { Flex, Text, useSporeColors } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { getCloudProviderName } from 'uniswap/src/utils/platform'
import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName'
import { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { promiseMinDelay } from 'utilities/src/time/timing'
......
......@@ -8,14 +8,14 @@ import {
} from 'wallet/src/features/transactions/types'
import { RootState } from 'wallet/src/state'
import { signerMnemonicAccount } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
import { preloadedWalletState } from 'wallet/src/test/fixtures/wallet/redux'
const account = signerMnemonicAccount()
const MOCK_DATE_PROMPTED = Date.now()
const state = {
...mockWalletPreloadedState(),
...preloadedWalletState(),
wallet: {
appRatingProvidedMs: MOCK_DATE_PROMPTED,
},
......
import { preloadedMobileState } from 'src/test/fixtures'
import { act, renderHook, waitFor } from 'src/test/test-utils'
import { SAMPLE_CURRENCY_ID_1, portfolio, portfolioBalances } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
import { queryResolvers } from 'wallet/src/test/utils'
import { useBalances } from './balances'
const preloadedState = mockWalletPreloadedState()
const preloadedState = preloadedMobileState()
describe(useBalances, () => {
it('returns null if no currency was specified', async () => {
......
import { TokenItemData } from 'src/components/explore/TokenItem'
import { AppTFunction } from 'ui/src/i18n/types'
import { TokenSortableField } from 'wallet/src/data/__generated__/types-and-hooks'
import { TokenSortableField } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import {
ClientTokensOrderBy,
TokenMetadataDisplayType,
......
......@@ -19,10 +19,10 @@ import {
ScrollView,
Text,
TouchableArea,
getUniconV2Colors,
useIsDarkMode,
useSporeColors,
useUniconColors,
useUniconV2Colors,
} from 'ui/src'
import { ENS_LOGO } from 'ui/src/assets'
import { iconSizes, imageSizes } from 'ui/src/theme'
......@@ -86,7 +86,7 @@ export const ProfileHeader = memo(function ProfileHeader({
useUniconColors(address)
// UniconV2 colors
const { color } = useUniconV2Colors(address)
const { color } = getUniconV2Colors(address)
// Wait for avatar, then render avatar extracted colors or unicon colors if no avatar
const fixedGradientColors: [string, string] = useMemo(() => {
......
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useAppDispatch } from 'src/app/hooks'
import Trace from 'src/components/Trace/Trace'
import { openModal } from 'src/features/modals/modalSlice'
import { MobileEventName } from 'src/features/telemetry/constants'
import { Flex, Icons, Text, TouchableArea, TouchableAreaProps, useSporeColors } from 'ui/src'
import FiatOnRampBackground from 'ui/src/assets/backgrounds/fiat-onramp-banner.svg'
import { iconSizes } from 'ui/src/theme'
import { ModalName } from 'wallet/src/telemetry/constants'
export function FiatOnRampBanner(props: TouchableAreaProps): JSX.Element {
const { t } = useTranslation()
const colors = useSporeColors()
const dispatch = useAppDispatch()
return (
<Trace logPress pressEvent={MobileEventName.FiatOnRampBannerPressed}>
<TouchableArea
backgroundColor="$DEP_fiatBanner"
borderRadius="$rounded12"
overflow="hidden"
p="$spacing12"
onPress={(): void => {
dispatch(openModal({ name: ModalName.FiatOnRamp }))
}}
{...props}
hapticFeedback>
<Flex fill position="absolute" right={0} top={0}>
<FiatOnRampBackground color={colors.sporeWhite.val} />
</Flex>
<Flex gap="$spacing4">
<Flex row justifyContent="space-between">
<Text color="$sporeWhite" variant="buttonLabel2">
{t('fiatOnRamp.banner.title')}
</Text>
<Icons.RotatableChevron color="$sporeWhite" direction="end" width={iconSizes.icon20} />
</Flex>
<Text color="$sporeWhite" opacity={0.72} variant="subheading2">
{t('fiatOnRamp.banner.subtitle')}
</Text>
</Flex>
</TouchableArea>
</Trace>
)
}
......@@ -22,7 +22,7 @@ import { useDebounce } from 'utilities/src/time/timing'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { useBottomSheetFocusHook } from 'wallet/src/components/modals/hooks'
import { useFiatOnRampAggregatorCountryListQuery } from 'wallet/src/features/fiatOnRamp/api'
import { FORSupportedCountry } from 'wallet/src/features/fiatOnRamp/types'
import { FORCountry } from 'wallet/src/features/fiatOnRamp/types'
import { getCountryFlagSvgUrl } from 'wallet/src/features/fiatOnRamp/utils'
import { SearchTextInput } from 'wallet/src/features/search/SearchTextInput'
import { ModalName } from 'wallet/src/telemetry/constants'
......@@ -30,11 +30,11 @@ import { ModalName } from 'wallet/src/telemetry/constants'
const ICON_SIZE = 32 // design prefers a custom value here
interface CountrySelectorProps {
onSelectCountry: (country: FORSupportedCountry) => void
onSelectCountry: (country: FORCountry) => void
countryCode: string
}
function key(item: FORSupportedCountry): string {
function key(item: FORCountry): string {
return item.countryCode
}
......@@ -52,7 +52,7 @@ function CountrySelectorContent({
const debouncedSearchText = useDebounce(searchText)
const filteredData: FORSupportedCountry[] = useMemo(() => {
const filteredData: FORCountry[] = useMemo(() => {
if (!data) {
return []
}
......@@ -64,7 +64,7 @@ function CountrySelectorContent({
}, [countryCode, data, debouncedSearchText])
const renderItem = useCallback(
({ item }: ListRenderItemInfo<FORSupportedCountry>): JSX.Element => {
({ item }: ListRenderItemInfo<FORCountry>): JSX.Element => {
const countryFlagUrl = getCountryFlagSvgUrl(item.countryCode)
return (
......
......@@ -12,9 +12,6 @@ import {
import { useMoonpayFiatOnRamp, useMoonpaySupportedTokens } from 'src/features/fiatOnRamp/hooks'
import { FiatOnRampCurrency } from 'src/features/fiatOnRamp/types'
import { closeModal } from 'src/features/modals/modalSlice'
import { sendMobileAnalyticsEvent } from 'src/features/telemetry'
import { MobileEventName } from 'src/features/telemetry/constants'
import { MobileEventProperties } from 'src/features/telemetry/types'
import { AnimatedFlex, Flex, Text, useDeviceInsets, useSporeColors } from 'ui/src'
import MoonpayLogo from 'ui/src/assets/logos/svg/moonpay.svg'
import { NumberType } from 'utilities/src/format/types'
......@@ -29,7 +26,9 @@ import { ChainId } from 'wallet/src/constants/chains'
import { useMoonpayFiatCurrencySupportInfo } from 'wallet/src/features/fiatOnRamp/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { useCurrencyInfo } from 'wallet/src/features/tokens/useCurrencyInfo'
import { ModalName } from 'wallet/src/telemetry/constants'
import { sendWalletAnalyticsEvent } from 'wallet/src/telemetry'
import { FiatOnRampEventName, ModalName } from 'wallet/src/telemetry/constants'
import { WalletEventProperties } from 'wallet/src/telemetry/types'
import { buildCurrencyId } from 'wallet/src/utils/currencyId'
import { openUri } from 'wallet/src/utils/linking'
import { FiatOnRampTokenSelectorModal } from './FiatOnRampTokenSelector'
......@@ -126,6 +125,7 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element {
} = useMoonpayFiatOnRamp({
baseCurrencyAmount: value,
quoteCurrencyCode: currency.moonpayCurrencyCode,
quoteChainId: currency.currencyInfo?.currency.chainId ?? ChainId.Mainnet,
})
useTimeout(
......@@ -144,9 +144,9 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element {
!isLoading && (!eligible || (!isError && fiatOnRampHostUrl && quoteCurrencyAmountReady))
const onChangeValue =
(source: MobileEventProperties[MobileEventName.FiatOnRampAmountEntered]['source']) =>
(source: WalletEventProperties[FiatOnRampEventName.FiatOnRampAmountEntered]['source']) =>
(newAmount: string): void => {
sendMobileAnalyticsEvent(MobileEventName.FiatOnRampAmountEntered, {
sendWalletAnalyticsEvent(FiatOnRampEventName.FiatOnRampAmountEntered, {
source,
})
setValue(newAmount)
......@@ -175,6 +175,16 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element {
const insets = useDeviceInsets()
const onSelectCurrency = (newCurrency: FiatOnRampCurrency): void => {
setCurrency(newCurrency)
setShowTokenSelector(false)
if (newCurrency.currencyInfo?.currency.symbol) {
sendWalletAnalyticsEvent(FiatOnRampEventName.FiatOnRampTokenSelected, {
token: newCurrency.currencyInfo.currency.symbol.toLowerCase(),
})
}
}
return (
<Flex grow pt={showConnectingToMoonpayScreen ? undefined : insets.top}>
{!showConnectingToMoonpayScreen && (
......@@ -258,10 +268,7 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element {
loading={supportedTokensLoading}
onClose={(): void => setShowTokenSelector(false)}
onRetry={supportedTokensRefetch}
onSelectCurrency={(newCurrency: FiatOnRampCurrency): void => {
setCurrency(newCurrency)
setShowTokenSelector(false)
}}
onSelectCurrency={onSelectCurrency}
/>
)}
</AnimatedFlex>
......
......@@ -4,11 +4,14 @@ import { useCallback, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { useAppDispatch } from 'src/app/hooks'
import { Delay } from 'src/components/layout/Delayed'
import { FiatOnRampCurrency } from 'src/features/fiatOnRamp/types'
import { ColorTokens, useSporeColors } from 'ui/src'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { isAndroid } from 'uniswap/src/utils/platform'
import { logger } from 'utilities/src/logger/logger'
import { useDebounce } from 'utilities/src/time/timing'
import { useAllCommonBaseCurrencies } from 'wallet/src/components/TokenSelector/hooks'
import { BRIDGED_BASE_ADDRESSES } from 'wallet/src/constants/addresses'
import { ChainId } from 'wallet/src/constants/chains'
import { fromMoonpayNetwork } from 'wallet/src/features/chains/utils'
import { CurrencyInfo } from 'wallet/src/features/dataApi/types'
......@@ -32,9 +35,13 @@ import {
} from 'wallet/src/features/transactions/types'
import { createTransactionId } from 'wallet/src/features/transactions/utils'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
import { areAddressesEqual } from 'wallet/src/utils/addresses'
import { getFormattedCurrencyAmount } from 'wallet/src/utils/currency'
import { ValueType } from 'wallet/src/utils/getCurrencyAmount'
import { FiatOnRampCurrency } from './types'
const ETH_POLYGON_MOONPAY_CODE = 'eth_polygon'
const WETH_POLYGON_MOONPAY_CODE = 'weth_polygon'
const BNB_MAINNET_MOONPAY_CODE = 'bnb'
export function useFormatExactCurrencyAmount(
currencyAmount: string,
......@@ -61,6 +68,7 @@ export function useFormatExactCurrencyAmount(
/** Returns a new externalTransactionId and a callback to store the transaction. */
export function useFiatOnRampTransactionCreator(
ownerAddress: string,
chainId: ChainId,
initialTypeInfo?: Partial<FiatPurchaseTransactionInfo>
): {
externalTransactionId: string
......@@ -74,7 +82,7 @@ export function useFiatOnRampTransactionCreator(
// adds a dummy transaction detail for now
// later, we will attempt to look up information for that id
const transactionDetail: TransactionDetails = {
chainId: ChainId.Mainnet,
chainId,
id: externalTransactionId.current,
from: ownerAddress,
typeInfo: {
......@@ -89,7 +97,7 @@ export function useFiatOnRampTransactionCreator(
}
// use addTransaction action so transactionWatcher picks it up
dispatch(addTransaction(transactionDetail))
}, [dispatch, ownerAddress, initialTypeInfo])
}, [initialTypeInfo, chainId, ownerAddress, dispatch])
return { externalTransactionId: externalTransactionId.current, dispatchAddTransaction }
}
......@@ -102,9 +110,11 @@ const MOONPAY_FEES_INCLUDED = true
export function useMoonpayFiatOnRamp({
baseCurrencyAmount,
quoteCurrencyCode,
quoteChainId,
}: {
baseCurrencyAmount: string
quoteCurrencyCode: string | undefined
quoteChainId: ChainId
}): {
eligible: boolean
quoteAmount: number
......@@ -126,8 +136,10 @@ export function useMoonpayFiatOnRamp({
// for now, always assume the user wants to fund the current account
const activeAccountAddress = useActiveAccountAddressWithThrow()
const { externalTransactionId, dispatchAddTransaction } =
useFiatOnRampTransactionCreator(activeAccountAddress)
const { externalTransactionId, dispatchAddTransaction } = useFiatOnRampTransactionCreator(
activeAccountAddress,
quoteChainId
)
const { moonpaySupportedFiatCurrency: baseCurrency } = useMoonpayFiatCurrencySupportInfo()
const baseCurrencyCode = baseCurrency.code.toLowerCase()
......@@ -307,16 +319,38 @@ function findTokenOptionForMoonpayCurrency(
commonBaseCurrencies: CurrencyInfo[] | undefined = [],
moonpayCurrency: MoonpayCurrency
): Maybe<CurrencyInfo> {
return commonBaseCurrencies.find((item) => {
const [code, network] = moonpayCurrency.code.split('_')
const currencyInfo = commonBaseCurrencies.find((item) => {
// Moonpay uses WETH on Polygon to represent ETH on Polygon
const moonpayCurrencyCode =
moonpayCurrency.code === ETH_POLYGON_MOONPAY_CODE
? WETH_POLYGON_MOONPAY_CODE
: moonpayCurrency.code
const [tokenSymbol, network] = moonpayCurrencyCode.split('_')
const chainId = fromMoonpayNetwork(network)
return (
item &&
code &&
code === item.currency.symbol?.toLowerCase() &&
tokenSymbol &&
tokenSymbol.toLowerCase() === item.currency.symbol?.toLowerCase() &&
chainId === item.currency.chainId
)
})
if (
!currencyInfo &&
!BRIDGED_BASE_ADDRESSES.find((bridgedAddress) =>
areAddressesEqual(bridgedAddress, moonpayCurrency.metadata?.contractAddress)
) &&
// We do not support BNB onboarding and Moonpay does not return an address for it so map it manually
moonpayCurrency.code !== BNB_MAINNET_MOONPAY_CODE
) {
logger.error(`Moonpay currency ${moonpayCurrency.code} cannot be mapped`, {
tags: { file: 'fiatOnRamp/hooks', function: 'useMoonpaySupportedTokens' },
extra: {
chainId: moonpayCurrency.metadata?.chainId,
address: moonpayCurrency.metadata?.contractAddress,
},
})
}
return currencyInfo
}
export function useFiatOnRampSupportedTokens({
......
......@@ -9,6 +9,7 @@ import { ColorTokens, Flex, useSporeColors } from 'ui/src'
import { spacing } from 'ui/src/theme'
import { isAndroid } from 'uniswap/src/utils/platform'
import { TextInput } from 'wallet/src/components/input/TextInput'
import { ElementName } from 'wallet/src/telemetry/constants'
interface Props {
alwaysShowInputSuffix?: boolean
......@@ -122,7 +123,7 @@ function Inputs({
scrollEnabled={false}
selectionColor={colors.neutral1.val}
spellCheck={false}
testID="import_account_form/input"
testID={ElementName.ImportAccountInput}
textAlign={isInputEmpty ? 'left' : backgroundTextAlignment}
textAlignVertical="bottom"
value={value}
......
......@@ -99,7 +99,7 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
"width": 8,
}
}
testID="import_account_form/input"
testID="import-account-input"
textAlignVertical="bottom"
/>
</View>
......
......@@ -2,14 +2,14 @@ import { createStore, Store } from '@reduxjs/toolkit'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import {
closeModal,
initialModalState,
initialModalsState,
modalsReducer,
openModal,
} from 'src/features/modals/modalSlice'
import { ModalName } from 'wallet/src/telemetry/constants'
import { ModalsState } from './ModalsState'
const initialState = { ...initialModalState }
const initialState = { ...initialModalsState }
const modalName = ModalName.WalletConnectScan
describe('modals reducer', () => {
......
......@@ -101,7 +101,7 @@ export type OpenModalParams =
export type CloseModalParams = { name: keyof ModalsState }
export const initialModalState: ModalsState = {
export const initialModalsState: ModalsState = {
[ModalName.ExchangeTransferModal]: {
isOpen: false,
initialState: undefined,
......@@ -174,7 +174,7 @@ export const initialModalState: ModalsState = {
const slice = createSlice({
name: 'modals',
initialState: initialModalState,
initialState: initialModalsState,
reducers: {
openModal: (state, action: PayloadAction<OpenModalParams>) => {
const { name, initialState } = action.payload
......
......@@ -3,9 +3,9 @@ import React from 'react'
import { StyleSheet } from 'react-native'
import { ColorTokens, Flex, FlexProps, Logos, SpaceTokens, Text, useSporeColors } from 'ui/src'
import { TextVariantTokens, borderRadii, iconSizes, spacing } from 'ui/src/theme'
import { IAmount } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { isIOS } from 'uniswap/src/utils/platform'
import { NumberType } from 'utilities/src/format/types'
import { IAmount } from 'wallet/src/data/__generated__/types-and-hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
type ListPriceProps = FlexProps & {
......
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.
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.
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